<!-- task=m-codex-app-server-streaming-migration/02+01_session_events plan=0 tag=REFACTOR -->
# Codex App Server Session Events Plan

## 이 파일을 읽는 구현 에이전트에게
이 PLAN을 끝까지 읽은 뒤 구현한다. 먼저 선행 작업 `01_appserver_rpc_client/complete.log`가 있는지 확인한다. 없으면 구현하지 말고 선행 작업을 완료한다. 구현 완료 전 `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 실제 변경 내용과 검증 출력으로 채운다.

## 배경
이 작업은 app-server RPC client 위에 runtime 실행 의미를 올린다. 목표는 IOP의 논리 session을 Codex thread와 매핑하고, `turn/start` 이후 app-server notification을 `RuntimeEvent`로 변환하는 것이다.

## 사용자 리뷰 요청 흐름
사용자 소유 Codex 계정, 외부 서비스, 또는 plan 범위 변경이 필요한 경우에만 `CODE_REVIEW-cloud-G07.md`의 사용자 리뷰 요청 섹션을 실제 내용으로 교체한다. 프로토콜 타입 확인이나 테스트 재실행은 구현 에이전트가 처리한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/codex-app-server-streaming-migration.md`
- Task ids:
  - `session-thread`: IOP `session_id`별 app-server thread를 재사용하고 `/terminate-session`에서 해당 세션을 정리한다.
  - `event-map`: `item/agentMessage/delta`를 `RuntimeEvent{Type: delta}`로 전달하고, `turn/completed`는 complete, app-server error와 failed turn은 error로 매핑한다.
- Completion mode: check-on-pass

## 의존 관계 및 구현 순서
- 현재 subtask: `02+01_session_events`
- 선행 index `01`: `agent-task/m-codex-app-server-streaming-migration/01_appserver_rpc_client/complete.log`가 현재 없음. 구현 전 선행 작업 완료가 필요하다.

## 분석 결과
### 읽은 파일
- 선행 plan 대상: `agent-task/m-codex-app-server-streaming-migration/01_appserver_rpc_client/PLAN-cloud-G07.md`
- 코드: `apps/node/internal/adapters/cli/cli.go`, `apps/node/internal/adapters/cli/codex_exec.go`, `apps/node/internal/adapters/cli/opencode_sse.go`, `apps/node/internal/adapters/cli/emitters.go`, `apps/node/internal/runtime/types.go`
- Node/Edge 흐름: `apps/node/internal/node/node.go`, `apps/edge/internal/opsconsole/events.go`, `apps/edge/internal/opsconsole/events_test.go`
- 임시 타입: `/tmp/iop-codex-appserver-ts/ClientRequest.ts`, `/tmp/iop-codex-appserver-ts/ServerNotification.ts`, `/tmp/iop-codex-appserver-ts/v2/TurnStartParams.ts`, `/tmp/iop-codex-appserver-ts/v2/AgentMessageDeltaNotification.ts`, `/tmp/iop-codex-appserver-ts/v2/ItemCompletedNotification.ts`, `/tmp/iop-codex-appserver-ts/v2/TurnCompletedNotification.ts`

### 테스트 환경 규칙
- 환경 미지정은 local로 본다.
- 선행 완료 확인: `test -f agent-task/m-codex-app-server-streaming-migration/01_appserver_rpc_client/complete.log`
- 기본 검증 후보: `go test ./apps/node/internal/adapters/cli -count=1`, `go test ./apps/node/... -count=1`

### 테스트 커버리지 공백
- `turn/start` 요청 shape, delta notification, completed notification, error notification에 대한 adapter 단위 테스트가 없다.
- session list/terminate가 app-server thread id와 IOP session id를 어떻게 보여주는지 검증이 없다.
- Edge console은 현재 metadata를 직접 출력하지 않으므로 source 증거는 node runtime event 또는 focused test로 확인해야 한다.

### 심볼 참조
- 기존 runtime event type 변경 없음.
- 추가/확장 예정: app-server notification decoder, Codex thread id 저장 필드, event metadata key.
- 호출 지점: `Execute`, `Sessions`, `Terminate`, `handleSessionList`, `sessionSink.Emit`.

### 분할 판단
분할 정책을 평가했고 공유 task group은 `agent-task/m-codex-app-server-streaming-migration/`이다. sibling subtask는 `01_appserver_rpc_client`(선행 없음), `02+01_session_events`(`01` 의존), `03+02_default_profile`(`02` 의존), `04+03_stream_smoke`(`03` 의존)이다. 현재 작업의 predecessor index `01`은 active `complete.log`가 없어서 미충족이다.

### 범위 결정 근거
runtime event mapping은 사용자에게 보이는 동작이므로 RPC lifecycle과 분리해 리뷰한다. default profile 전환은 event mapping 검증 후 별도 작업에서 한다.

### 빌드 등급
`cloud-G07`: 외부 app-server notification shape와 turn lifecycle을 다루므로 fake RPC 단위 테스트와 실제 CLI smoke가 모두 필요하다.

## 구현 체크리스트
- [ ] 선행 `01_appserver_rpc_client/complete.log`를 확인한다.
- [ ] `SessionID`를 Codex thread id에 매핑하고 재사용한다.
- [ ] `turn/start` request를 `ExecutionSpec.Input` 기반 prompt로 보낸다.
- [ ] `item/agentMessage/delta`, `item/completed`, `turn/completed`, `error` notification을 runtime event로 변환한다.
- [ ] runtime event metadata에 app-server source와 thread/turn id를 남긴다.
- [ ] session list와 terminate 동작을 app-server session에 맞게 구현한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REFACTOR-1] IOP session과 Codex thread 매핑
문제: 기존 `codex_exec.go`는 external session id만 저장하고, `opencode_sse.go`는 OpenCode session id를 저장한다. app-server는 thread id를 long-lived process/session에 묶어야 한다.

해결 방법: app-server session struct에 `threadID`, `turnID`, `status/tail` 등 session list에 필요한 최소 상태를 저장한다. `normalizeSessionID(spec.SessionID)`를 IOP session key로 유지하고, Codex thread id는 metadata와 session detail로만 노출한다.

수정 파일 및 체크리스트:
- [ ] `apps/node/internal/adapters/cli/codex_app_server.go`
- [ ] `apps/node/internal/adapters/cli/codex_app_server_internal_test.go`

테스트 작성:
- [ ] 같은 IOP session이 같은 thread id를 재사용하는 테스트
- [ ] 서로 다른 session id가 다른 thread id를 갖는 테스트

중간 검증:
```bash
test -f agent-task/m-codex-app-server-streaming-migration/01_appserver_rpc_client/complete.log
go test ./apps/node/internal/adapters/cli -run 'TestCodexAppServerSessionThreadMapping' -count=1
```

### [REFACTOR-2] app-server notification을 RuntimeEvent로 변환
문제: `emitters.go:281`의 `codexJSONEmitter`는 `codex exec --json` line format만 이해한다. app-server notification은 `method`와 `params` 기반이다.

해결 방법: app-server 전용 decoder를 만들고 다음 mapping을 최소 지원한다.
- `item/agentMessage/delta` -> `RuntimeEvent{Type: response.delta, Delta: ...}`
- `item/completed` 중 agent message content -> 중복 없는 final delta 또는 내부 tail update
- `turn/completed` -> `RuntimeEvent{Type: response.completed}`
- `error` -> `RuntimeEvent{Type: response.error, Error: ...}`

수정 파일 및 체크리스트:
- [ ] `apps/node/internal/adapters/cli/codex_app_server.go`
- [ ] `apps/node/internal/adapters/cli/codex_app_server_internal_test.go`

테스트 작성:
- [ ] delta notification이 response delta로 변환되는 테스트
- [ ] turn completed가 response completed로 변환되는 테스트
- [ ] error notification이 error event와 Go error로 surfaced 되는 테스트

중간 검증:
```bash
go test ./apps/node/internal/adapters/cli -run 'TestCodexAppServerEventMap' -count=1
```

### [REFACTOR-3] session list와 terminate 연결
문제: `handleSessionList`는 reporter들의 `Sessions()`를 모아 보여준다. app-server reporter가 없으면 `/sessions`와 `/terminate-session`에서 새 mode가 보이지 않는다.

해결 방법: app-server executor가 `Sessions`, `Terminate`, `Stop`을 구현한다. terminate는 해당 logical session의 process/thread 자원을 닫고 sessions map에서 제거한다. 프로토콜에 적절한 thread close/unsubscribe 요청이 있으면 먼저 호출하고, 없으면 local process close를 정리 계약으로 둔다.

수정 파일 및 체크리스트:
- [ ] `apps/node/internal/adapters/cli/codex_app_server.go`
- [ ] `apps/node/internal/adapters/cli/cli_internal_test.go`

테스트 작성:
- [ ] app-server session이 `/sessions` snapshot에 표시되는 테스트
- [ ] terminate 후 session list에서 사라지는 테스트

중간 검증:
```bash
go test ./apps/node/internal/adapters/cli -run 'TestHandleSessionList.*CodexAppServer|TestTerminateSession.*CodexAppServer' -count=1
```

## 수정 파일 요약
| 파일 | 항목 |
| --- | --- |
| `apps/node/internal/adapters/cli/codex_app_server.go` | REFACTOR-1, REFACTOR-2, REFACTOR-3 |
| `apps/node/internal/adapters/cli/codex_app_server_internal_test.go` | REFACTOR-1, REFACTOR-2 |
| `apps/node/internal/adapters/cli/cli_internal_test.go` | REFACTOR-3 |

## 최종 검증
```bash
test -f agent-task/m-codex-app-server-streaming-migration/01_appserver_rpc_client/complete.log
go test ./apps/node/internal/adapters/cli -count=1
go test ./apps/node/... -count=1
```

**모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.**
