<!-- task=cli_real_e2e_failures/02_opencode_second_run plan=0 tag=OCODE -->

# PLAN-cloud-G07 - opencode second message real e2e failure

## 이 파일을 읽는 구현 에이전트에게

실제 `bin/edge.sh` + `bin/node.sh` 사용자 흐름에서 `opencode` 첫 메시지는 통과했지만 같은 session의 두 번째 메시지가 timeout/cancelled로 끝났다. 단순 smoke가 아니라 실제 CLI profile에서 메시지 2번이 정상적으로 주고받아지는지 복구해야 한다.

최종 산출물은 동일 session 연속 실행 회귀 테스트, OpenCode SSE 이벤트 처리 수정, 실제 e2e 재검증이다.

## 배경

- 실행 일자: 2026-05-22
- 실제 e2e 로그 루트: `/tmp/iop-real-cli-e2e-UCUE1C`
- 실패 profile: `opencode`
- 실패 흐름:
  - `/nodes`, `/capabilities node0`, `/transport node0` 정상
  - 첫 메시지: `[node0-msg] IOP_E2E_OPENCODE_ONE`, complete
  - 두 번째 메시지: `[node0-evt] start` 이후 non-empty `[node0-msg]` 없음
  - timeout 후 `[node0-evt] cancelled`
  - node 로그: second run status `cancelled`, error `run cancelled`

같은 실행에서 `claude`, `antigravity`, `codex`는 메시지 2회 실제 흐름을 통과했다. 따라서 우선 범위는 `opencode-sse` mode의 session 재사용과 SSE 이벤트 처리로 제한한다.

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/rules/domain/node/rules.md`
- `agent-ops/rules/domain/edge/rules.md`
- `agent-ops/rules/domain/testing/rules.md`
- `configs/edge.yaml`
- `apps/node/internal/adapters/cli/cli.go`
- `apps/node/internal/adapters/cli/opencode_sse.go`
- `apps/node/internal/adapters/cli/opencode_sse_blackbox_test.go`
- `apps/node/internal/adapters/cli/opencode_sse_internal_test.go`

### 테스트 커버리지 공백

- `opencode_sse_blackbox_test.go:147`은 단일 `Execute`의 text delta/complete만 검증한다.
- `opencode_sse_blackbox_test.go:258`은 global `message.part.delta` 파싱을 검증하지만, 같은 cached `opencodeSSESession`에서 두 번째 `Execute`를 검증하지 않는다.
- `opencode_sse_blackbox_test.go:586`은 context cancel 시 abort를 검증하지만, 실제 timeout에서 어떤 SSE 이벤트를 봤는지 진단할 수 있는 회귀 테스트가 없다.

### 심볼 참조

- `configs/edge.yaml:121` - `opencode` profile. `mode: opencode-sse`, model `ollama-dgx/qwen3.6:35b-a3b-bf16`.
- `apps/node/internal/adapters/cli/opencode_sse.go:105` - `executeOpencodeSSE` entrypoint.
- `apps/node/internal/adapters/cli/opencode_sse.go:116` - prompt 전 `/global/event` SSE stream open.
- `apps/node/internal/adapters/cli/opencode_sse.go:181` - existing `opencodeSSESession` 재사용.
- `apps/node/internal/adapters/cli/opencode_sse.go:303` - SSE scanner goroutine.
- `apps/node/internal/adapters/cli/opencode_sse.go:338` - context cancel 시 abort/finalize.
- `apps/node/internal/adapters/cli/opencode_sse.go:374` - sessionID mismatch event filter.
- `apps/node/internal/adapters/cli/opencode_sse.go:379` - delta/idle/error event dispatch.
- `apps/node/internal/adapters/cli/cli.go:247` - session list에 `opencode-sse` session 포함.
- `apps/node/internal/adapters/cli/cli.go:290` - opencode session terminate cleanup.

### 분할 판단

`opencode` 실패는 OpenCode HTTP/SSE protocol과 session reuse 경로에 한정된다. `claude-tui` terminal persistent 실패와 원인이 달라 별도 plan으로 분리한다.

### 범위 결정 근거

이 plan은 `opencode` `opencode-sse` mode만 다룬다. `claude-tui` persistent terminal path와 다른 CLI profile은 수정 대상이 아니다. 필요 시 공통 session list/terminate 동작은 기존 test로 회귀 확인만 한다.

### 빌드 등급

- Lane: `cloud`
- Grade: `G07`
- 근거: 실제 외부 CLI와 `bin/edge.sh`/`bin/node.sh` 사용자 흐름에서만 드러난 동일 session 두 번째 실행 실패이며, 최종 판정에 실제 e2e가 필요하다.

## 구현 체크리스트

- [ ] fake OpenCode server로 동일 session `Execute` 2회를 재현하는 blackbox test를 추가한다.
- [ ] 두 번째 실행에서 SSE 이벤트를 놓치거나 잘못 filter하는 경로를 수정한다.
- [ ] timeout/cancelled 경로에 최근 SSE event type/sessionID trace를 bounded diagnostic으로 남긴다.
- [ ] 실제 `bin/edge.sh` + `bin/node.sh` 흐름으로 `opencode` 메시지 2회를 재검증한다.
- [ ] `CODE_REVIEW-cloud-G07.md`를 구현 결과 기준으로 갱신한다.

## 구현 계획

### [OCODE-1] 동일 session 연속 Execute 회귀 테스트를 만든다

문제:

- 실제 실패는 첫 run complete 후 같은 session의 두 번째 run에서만 발생했다.
- 기존 blackbox tests는 cached `opencodeSSESession`을 재사용하는 두 번째 `Execute`를 직접 검증하지 않는다.

수정 방향:

- `opencode_sse_blackbox_test.go`에 `TestCLIExecuteOpencodeSSE_ConsecutiveExecutesReuseSession`을 추가한다.
- fake server가 첫 번째 `prompt_async` 후 `IOP_E2E_OPENCODE_ONE` delta와 idle/status를 보내고, 두 번째 `prompt_async` 후 `IOP_E2E_OPENCODE_TWO` delta와 idle/status를 보내도록 한다.
- `createCalls == 1`, `promptCalls == 2`, 두 run 모두 complete, 두 번째 sink에 first response가 섞이지 않음을 검증한다.
- 가능하면 `/global/event` 연결이 run마다 새로 열렸는지도 fake server counter로 확인한다.

예상 수정 파일:

- `apps/node/internal/adapters/cli/opencode_sse_blackbox_test.go`

### [OCODE-2] 두 번째 run의 SSE 처리와 timeout 진단을 보강한다

문제:

- `executeOpencodeSSE`는 `opencode_sse.go:116`에서 매 run 새 SSE stream을 연 뒤 prompt를 보낸다.
- `resolveOpencodeSession`은 `opencode_sse.go:181`에서 cached session을 재사용한다.
- 실제 두 번째 run은 start 이후 delta/idle이 관측되지 않아 `opencode_sse.go:338`의 cancel/abort 경로로 끝났다.
- 현재 timeout 로그는 최근에 어떤 SSE event가 들어왔고 무엇을 filter했는지 보여주지 않는다.

수정 방향:

- `driveOpencodeSSE`에 bounded event trace를 추가한다.
  - 최근 event type
  - event의 sessionID 존재 여부와 mismatch 여부
  - delta candidate가 비어 있었는지 여부
  - idle/status 관측 여부
- context deadline/cancel로 finalize될 때 trace를 runtime cancelled/error detail 또는 logger warning에 남긴다. runtime protocol과 기존 UX를 깨지 않는 방식을 우선한다.
- trace로 확인한 실제 event shape에 맞춰 parser/filter를 최소 수정한다.
  - OpenCode가 두 번째 run에서 `sessionID`를 다른 field에 넣는다면 해당 field도 인식한다.
  - `message.part.delta` 전에 `message.part.updated`가 없어서 text part type을 알 수 없는 경우, 실제 event payload 근거가 있으면 안전하게 text delta를 허용한다.
  - `session.status` idle shape이 기존 `opencodeStatusIdle`이 놓치는 형태라면 internal test를 추가하고 파서를 확장한다.
- complete는 non-empty assistant delta 없이 너무 이르게 발생하지 않도록 기존 behavior를 유지하거나 회귀 테스트로 고정한다.

예상 수정 파일:

- `apps/node/internal/adapters/cli/opencode_sse.go`
- `apps/node/internal/adapters/cli/opencode_sse_internal_test.go`
- `apps/node/internal/adapters/cli/opencode_sse_blackbox_test.go`

검증:

- `go test ./apps/node/internal/adapters/cli -run 'OpencodeSSE|Opencode'`
- 실제 사용자 흐름:
  - `bin/node.sh --config <temp-config>`
  - `bin/edge.sh --config <temp-config>`
  - `/nodes`
  - `/capabilities node0`
  - `/transport node0`
  - `node0`에 `IOP_E2E_OPENCODE_ONE` 정확 응답 요청
  - 같은 session에서 `IOP_E2E_OPENCODE_TWO` 정확 응답 요청
  - 두 번 모두 non-empty `[node0-msg]`와 complete 확인

## 수정 파일 요약

| 파일 | 수정 의도 |
| --- | --- |
| `apps/node/internal/adapters/cli/opencode_sse.go` | 동일 session 두 번째 run SSE 처리 및 timeout 진단 보강 |
| `apps/node/internal/adapters/cli/opencode_sse_blackbox_test.go` | 연속 Execute/session reuse 회귀 테스트 |
| `apps/node/internal/adapters/cli/opencode_sse_internal_test.go` | 실제 event shape 파서 보강 시 단위 테스트 |
| `agent-task/cli_real_e2e_failures/02_opencode_second_run/CODE_REVIEW-cloud-G07.md` | 구현 후 review 기준 갱신 |

## 최종 검증

- `go test ./apps/node/internal/adapters/cli -run 'OpencodeSSE|Opencode'`
- `go test ./apps/node/internal/adapters/cli`
- 실제 `opencode` e2e 사용자 흐름 2회 메시지 통과 로그 첨부

최종 응답에는 수정 파일, 테스트 명령, 실제 e2e 로그 위치, 남은 위험을 요약한다.
