<!-- task=m-architecture-refactor-foundation/05+04_terminal_core plan=0 tag=REFACTOR -->

# Plan - REFACTOR Terminal Session Core

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

구현 완료의 마지막 단계는 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이다. 검증을 실행하고, 실제 stdout/stderr를 기록하고, active 파일을 그대로 둔 채 리뷰 준비를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용이다.

이 계획은 `04_cli_executor_split` 완료 후 구현한다. 구현 중 사용자만 결정할 수 있는 선택, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 충돌이 있으면 active review stub의 `사용자 리뷰 요청` 섹션에 정확한 증거를 기록하고 멈춘다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다.

## 배경

현재 persistent CLI 실행은 process start, PTY/pipe setup, prompt write, output drain, screen rendering, cleanup이 `persistent.go`에 집중되어 있다. terminal bridge 후속 작업을 위해 persistent PTY/screen/input/signal/close 책임을 작은 terminal session core로 떼어낼 필요가 있다. 이 작업은 remote bridge 프로토콜을 만들지 않고, Node adapter 내부 core 경계만 만든다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 양식을 복사한 것이다. 구현 중 direct user prompt는 금지되며, code-review가 요청 정당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/architecture-refactor-foundation.md`
- Task ids:
  - `terminal-core`: persistent PTY/screen/rendering/resize/input/signal/close terminal session core split
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `AGENTS.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/architecture-refactor-foundation.md`
- `agent-ops/rules/project/domain/node/rules.md`
- `agent-test/local/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/node-smoke.md`
- `apps/node/internal/adapters/cli/cli.go`
- `apps/node/internal/adapters/cli/persistent.go`
- `apps/node/internal/adapters/cli/persistent_output_filter.go`
- `apps/node/internal/adapters/cli/oneshot.go`
- `apps/node/internal/adapters/cli/codex_exec.go`
- `apps/node/internal/adapters/cli/antigravity_print.go`
- `apps/node/internal/adapters/cli/opencode_sse.go`
- `apps/node/internal/adapters/cli/cli_internal_test.go`
- `apps/node/internal/adapters/cli/persistent_execute_blackbox_test.go`
- `apps/node/internal/adapters/cli/lifecycle_blackbox_test.go`
- `apps/node/internal/adapters/cli/persistent_output_filter_test.go`
- `apps/node/internal/adapters/cli/status/status_test.go`
- `apps/node/internal/runtime/types.go`
- `apps/node/internal/node/node.go`
- `apps/node/internal/node/node_test.go`
- `apps/node/internal/bootstrap/module.go`
- `apps/node/internal/bootstrap/module_test.go`
- `scripts/e2e-smoke.sh`

### 테스트 환경 규칙

- `test_env=local`.
- `agent-test/local/rules.md`를 읽었고, Node 변경은 `node-smoke` profile을 매칭한다.
- `agent-test/local/node-smoke.md`를 읽었다. 적용 명령은 `go test -count=1 ./apps/node/...`, `./scripts/e2e-smoke.sh`이다.
- PTY/TUI behavior는 local smoke만으로 완전 검증되지 않는다. 구현 에이전트는 `go test -count=1 ./apps/node/internal/adapters/cli`에 PTY core 단위/blackbox coverage를 추가하고, 수동 `scripts/dev/edge.sh` + `scripts/dev/node.sh` full-cycle 미실행 여부를 review stub에 기록한다.

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

- terminal PTY startup: 기존 persistent blackbox가 실행 성공을 덮지만 core-level PTY start 옵션과 close semantics를 직접 검증하지 않는다.
- screen rendering/tail buffer: `persistent_output_filter_test.go`와 status tests는 일부 rendering/filter를 덮지만 terminal session core의 snapshot API는 새 tests가 필요하다.
- resize/input/signal/close: 현재 외부 bridge command가 없으므로 core interface와 local close/signal behavior를 focused tests로 덮고, remote bridge rollout은 제외한다.

### 심볼 참조

- 계획 단계에서 제거/rename 확정 symbol은 없음.
- 구현 중 `profileSession`, `startProfileSession`, `executePersistent`, `writePrompt`, `drainUntilIdle`, `drainSessionUntilIdle`, `closeSession` call site를 `rg --sort path`로 재확인한다.

### 분할 판단

- split decision policy를 계획 파일 선택 전에 평가했다.
- shared task group: `agent-task/m-architecture-refactor-foundation/`.
- sibling plans:
  - `04_cli_executor_split`: predecessor 없음. active plan 작성됨.
  - `05+04_terminal_core`: predecessor index `04`에 의존한다.
- predecessor `04`: active candidate `agent-task/m-architecture-refactor-foundation/04_cli_executor_split/complete.log`는 현재 없음. archive candidate는 읽지 않았다. 이 계획의 구현은 `04_cli_executor_split` PASS와 `complete.log` 생성 후 시작한다.

### 범위 결정 근거

- 이 계획은 `apps/node/internal/adapters/cli` 내부 terminal session core 분리로 제한한다.
- Edge console의 resize/input/signal command surface, proto schema, remote terminal bridge transport는 제외한다.
- mode executor/session map split은 `04_cli_executor_split` 범위다. 이 계획은 그 결과 위에 terminal core를 얹는다.
- vLLM streaming, router policy, OpenAI/Edge adapter refactor는 제외한다.

### 빌드 등급

- `cloud-G08`: PTY/TUI/screen repaint/process lifecycle behavior가 중심이고 deterministic evidence가 약한 고위험 refactor다.

## 의존 관계 및 구현 순서

- `05+04_terminal_core`는 directory name 기준으로 predecessor `04`만 가진다.
- 구현 전 `agent-task/m-architecture-refactor-foundation/04_cli_executor_split/complete.log` 또는 같은 task group archive의 `04_*`/`04+*` `complete.log`가 있어야 한다.
- predecessor가 없으면 구현하지 말고 review stub `사용자 리뷰 요청`이 아니라 작업 대기 상태로 보고한다.

## 구현 체크리스트

- [ ] `04_cli_executor_split` 완료 증거(`complete.log`)를 확인한 뒤 구현을 시작한다.
- [ ] persistent PTY session core를 추가하고 process start/read/write/snapshot/close 책임을 `persistent.go`에서 분리한다.
- [ ] screen rendering/tail buffer와 prompt input 경계를 core API로 정리하되 외부 bridge protocol은 만들지 않는다.
- [ ] resize/input/signal/close 메서드 또는 내부 hook을 정의하고 현재 CLI 경로에서 사용하는 close/signal behavior를 보존한다.
- [ ] terminal core focused tests와 기존 persistent/lifecycle tests를 통과시킨다.
- [ ] `go test -count=1 ./apps/node/internal/adapters/cli`, `go test -count=1 ./apps/node/...`, `./scripts/e2e-smoke.sh` 최종 검증을 통과시키고 수동 full-cycle 미실행 여부를 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REFACTOR-1] Extract Persistent Terminal Core

#### 문제

`apps/node/internal/adapters/cli/persistent.go:316`-`432`의 `startProfileSession`이 command start, PTY/pipe setup, reader setup, session struct construction, startup drain까지 동시에 처리한다.

```go
// apps/node/internal/adapters/cli/persistent.go:316
func (c *CLI) startProfileSession(ctx context.Context, cfg Config, key sessionKey) (*profileSession, error) {
	// ...
	if terminalMode {
		ptmx, err := pty.StartWithSize(cmd, &pty.Winsize{Rows: 40, Cols: 120})
		// ...
	} else {
		stdin, err := cmd.StdinPipe()
		// ...
	}
	// ...
}
```

#### 해결 방법

terminal mode path를 `terminalSessionCore`로 분리한다. pipe fallback은 현행 구조를 유지하거나 별도 `pipeSessionCore`로 감싸되, remote terminal semantics는 만들지 않는다.

```go
type terminalSessionCore struct {
	cmd    *exec.Cmd
	input  io.WriteCloser
	output io.ReadCloser
	tail   *status.TailBuffer
}

func startTerminalSessionCore(ctx context.Context, opts terminalSessionOptions) (*terminalSessionCore, error)
func (s *terminalSessionCore) WritePrompt(ctx context.Context, prompt string) error
func (s *terminalSessionCore) Snapshot() status.Snapshot
func (s *terminalSessionCore) Close() error
```

`profileSession`은 core를 들고 기존 field 접근이 필요한 부분만 adapter shim으로 유지한다.

#### 수정 파일 및 체크리스트

- [ ] `apps/node/internal/adapters/cli/persistent.go`: terminal start/read/write/close logic을 core 호출로 축소.
- [ ] `apps/node/internal/adapters/cli/terminal_session.go`: terminal core type과 start/write/snapshot/close 구현.
- [ ] `apps/node/internal/adapters/cli/persistent_execute_blackbox_test.go`: persistent 실행 회귀 유지.
- [ ] `apps/node/internal/adapters/cli/lifecycle_blackbox_test.go`: close/stop behavior 회귀 유지.

#### 테스트 작성

- `apps/node/internal/adapters/cli/terminal_session_test.go`를 추가한다.
- test names: `TestTerminalSessionCoreWritesPrompt`, `TestTerminalSessionCoreSnapshot`, `TestTerminalSessionCoreCloseIsIdempotent`.
- fixture는 기존 fake shell/testutil command를 우선 재사용한다. 새 외부 dependency는 추가하지 않는다.

#### 중간 검증

```bash
go test -count=1 ./apps/node/internal/adapters/cli
```

### [REFACTOR-2] Preserve Persistent Execute Contract

#### 문제

`apps/node/internal/adapters/cli/persistent.go:66`-`230`의 `executePersistent`는 session resolve, prompt write, drain, cancellation, completion emission을 모두 수행한다. core 분리 중 completion/cancel semantics가 바뀌면 edge console behavior가 흔들린다.

```go
// apps/node/internal/adapters/cli/persistent.go:66
func (c *CLI) executePersistent(ctx context.Context, cfg Config, req runtime.Request, emit runtime.Emitter) error {
	// writes prompt, drains output, emits start/message/complete
}
```

#### 해결 방법

`executePersistent`의 orchestration은 유지하고, IO primitive만 terminal core로 위임한다. `writePrompt` (`persistent.go:273`-`292`), `drainSessionUntilIdle` (`persistent.go:555`-`576`), `emitPersistentExit` (`persistent.go:587`-`604`)의 externally visible event order는 유지한다.

#### 수정 파일 및 체크리스트

- [ ] `apps/node/internal/adapters/cli/persistent.go`: event order와 cancel reason 유지.
- [ ] `apps/node/internal/adapters/cli/persistent_output_filter.go`: 필요 시 core snapshot input에 맞게만 조정.
- [ ] `apps/node/internal/adapters/cli/persistent_execute_blackbox_test.go`: event sequence regression 유지/추가.

#### 테스트 작성

- 기존 persistent blackbox tests를 유지한다.
- 필요 시 `TestPersistentExecuteKeepsCompletionAfterCoreSplit`를 추가해 start/message/complete 순서를 확인한다.

#### 중간 검증

```bash
go test -count=1 ./apps/node/internal/adapters/cli
```

### [REFACTOR-3] Add Resize/Input/Signal/Close Core Hooks

#### 문제

마일스톤 task는 terminal session core의 resize/input/signal/close 기반 분리를 요구한다. 현재 user-facing resize/input/signal command surface가 없으므로 외부 API를 추가하지 않고도 core 내부 hook이 먼저 있어야 한다.

#### 해결 방법

core에 내부 메서드를 정의한다.

```go
func (s *terminalSessionCore) Resize(rows, cols uint16) error
func (s *terminalSessionCore) WriteInput(ctx context.Context, data []byte) error
func (s *terminalSessionCore) Signal(sig os.Signal) error
func (s *terminalSessionCore) Close() error
```

현재 CLI 경로는 `WritePrompt`와 `Close`를 사용한다. `Resize`/`Signal`은 tests로 boundary를 고정하고, Edge/proto command는 후속 terminal bridge task로 남긴다.

#### 수정 파일 및 체크리스트

- [ ] `apps/node/internal/adapters/cli/terminal_session.go`: hook method 추가.
- [ ] `apps/node/internal/adapters/cli/terminal_session_test.go`: invalid size, closed session write/signal boundary 검증.
- [ ] `apps/node/internal/adapters/cli/lifecycle_blackbox_test.go`: adapter stop 시 core close 호출 검증.

#### 테스트 작성

- `TestTerminalSessionCoreResizeValidatesBounds`, `TestTerminalSessionCoreRejectsWriteAfterClose`, `TestTerminalSessionCoreSignalAfterClose`를 추가한다.
- PTY platform 차이가 있으면 testutil fake core를 사용하되 실제 PTY start path 하나는 유지한다.

#### 중간 검증

```bash
go test -count=1 ./apps/node/internal/adapters/cli
```

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/node/internal/adapters/cli/persistent.go` | REFACTOR-1, REFACTOR-2 |
| `apps/node/internal/adapters/cli/terminal_session.go` | REFACTOR-1, REFACTOR-3 |
| `apps/node/internal/adapters/cli/terminal_session_test.go` | REFACTOR-1, REFACTOR-3 |
| `apps/node/internal/adapters/cli/persistent_execute_blackbox_test.go` | REFACTOR-1, REFACTOR-2 |
| `apps/node/internal/adapters/cli/lifecycle_blackbox_test.go` | REFACTOR-1, REFACTOR-3 |
| `apps/node/internal/adapters/cli/persistent_output_filter.go` | REFACTOR-2 |

## 최종 검증

```bash
go test -count=1 ./apps/node/internal/adapters/cli
go test -count=1 ./apps/node/...
./scripts/e2e-smoke.sh
git diff --check
```

예상 결과: 모든 명령 exit code 0. `./scripts/e2e-smoke.sh`의 안내 문구에 따라 수동 `scripts/dev/edge.sh` + `scripts/dev/node.sh` full-cycle을 실행하지 못한 경우 review stub에 미실행 리스크를 기록한다.

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