iop/agent-task/06_cli_codex_explicit_mode/PLAN.md

8.9 KiB

Codex exec 분기를 명시적 mode/resume_args 필드로 전환

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

아래 체크리스트를 순서대로 완료하고, 각 항목의 중간 검증과 최종 검증을 실제로 실행하세요. 구현이 끝나면 agent-task/06_cli_codex_explicit_mode/CODE_REVIEW.md의 모든 섹션을 실제 구현 내용과 명령 출력으로 채우세요. CODE_REVIEW.md이 파일을 읽는 리뷰 에이전트에게 섹션에 있는 아카이브 지시(*.log로 이름 변경, complete.log 작성)는 구현 에이전트가 수행하면 안 되며, 리뷰 스킬 전용입니다.

배경

apps/node/internal/adapters/cli/codex_exec.go:20isCodexExecProfilefilepath.Base(profile.Command) == "codex" && profile.Args[0] == "exec"로 codex exec 프로파일을 추정합니다. command 이름과 첫 인자에 의존하는 sniffing은 사용자 입장에서 우회 동작 여부가 보이지 않고, codex CLI 사용 패턴이 바뀌면 잘못된 분기가 됩니다. 또한 codexResumeOptions(codex_exec.go:79-105)는 codex CLI의 옵션 화이트/블랙리스트를 하드코딩해 새 옵션이 추가될 때마다 깨질 위험이 있습니다. 두 가지 모두 CLIProfileConf에 명시 필드를 추가해 의도를 코드가 아닌 설정에서 표현합니다.

의존 관계 및 구현 순서

  1. [REFACTOR-1] CLIProfileConfMode, ResumeArgs 필드 추가 + 기본값/YAML/proto 동기화.
  2. [REFACTOR-2] cli 어댑터의 Execute 분기와 executeCodexExecMode/ResumeArgs를 사용하도록 변경. command sniffing 제거.
  3. [REFACTOR-3] configs/edge.yamlcodex 프로파일이 새 필드를 사용하도록 갱신. 회귀 테스트 갱신.

주: cli_profile_proto_message 작업이 먼저 머지되면 proto 동기화는 CLIProfileConfig에 두 필드를 추가하는 형태가 된다. 두 작업이 독립으로 진행될 경우, 본 작업은 mapstructure 필드만 추가하고 proto 작업이 들어올 때 함께 갱신.

[REFACTOR-1] 설정 스키마에 Mode/ResumeArgs 필드 추가

문제

packages/config/config.go:106-119CLIProfileConf는 codex exec 동작을 표현할 명시적 필드가 없어 어댑터가 command 이름을 sniff하게 만든다.

해결 방법

type CLIProfileConf struct {
    Command               string   `mapstructure:"command"                  yaml:"command"`
    Args                  []string `mapstructure:"args"                     yaml:"args"`
    Env                   []string `mapstructure:"env"                      yaml:"env"`
    Persistent            bool     `mapstructure:"persistent"               yaml:"persistent"`
    Terminal              bool     `mapstructure:"terminal"                 yaml:"terminal"`
    ResponseIdleTimeoutMS int      `mapstructure:"response_idle_timeout_ms" yaml:"response_idle_timeout_ms"`
    StartupIdleTimeoutMS  int      `mapstructure:"startup_idle_timeout_ms"  yaml:"startup_idle_timeout_ms"`
    OutputFormat          string   `mapstructure:"output_format"            yaml:"output_format"`
    // Mode declares the execution dialect. "" means default (oneshot/persistent
    // based on Persistent). "codex-exec" means use codex exec resume per session.
    Mode                  string   `mapstructure:"mode"        yaml:"mode"`
    // ResumeArgs is the explicit args list to use when reissuing the command
    // for an existing session. Required when Mode == "codex-exec".
    ResumeArgs            []string `mapstructure:"resume_args" yaml:"resume_args"`
}

수정 파일 및 체크리스트

  • packages/config/config.goMode, ResumeArgs 필드를 추가한다.
  • packages/config/config_test.go에 두 필드가 YAML에서 정상 로드되는지 단언하는 케이스를 1개 추가한다.
  • cli_profile_proto_message 작업이 머지된 후라면 proto/iop/runtime.protoCLIProfileConfigmode, resume_args를 추가하고 make proto. (그렇지 않으면 본 작업의 검증은 yaml 경로만 확인하고, 후속 작업에서 동기화한다.)

테스트 작성

packages/config/config_test.goTestLoadEdge_CLIProfileMode (가칭): yaml에 mode: codex-exec, resume_args: ["exec","resume","--json"]이 들어있을 때 unmarshal 결과가 일치하는지 단언.

중간 검증

go test ./packages/config/...

예상 결과: 신규 케이스 포함 PASS.

[REFACTOR-2] CLI 어댑터를 mode/ResumeArgs 기반 분기로 전환

문제

  • apps/node/internal/adapters/cli/cli.go:160-167ExecuteisCodexExecProfile을 호출.
  • apps/node/internal/adapters/cli/codex_exec.go:20-22의 sniffing 로직.
  • apps/node/internal/adapters/cli/codex_exec.go:67-105codexExecArgs/codexResumeOptions가 하드코딩 옵션 처리.

해결 방법

Execute의 분기를 다음과 같이 단순화:

if profile.Mode == "codex-exec" {
    return c.executeCodexExec(ctx, spec, profile, sink)
}
if profile.Persistent {
    return c.executePersistent(ctx, spec, profile, sink)
}
return c.executeOneShot(ctx, spec, profile, sink)

Start 루프(cli.go:93-119)에서도 profile.Mode == "codex-exec"인 프로파일은 persistent worker를 미리 띄우지 않도록 동일 분기 적용.

codexExecArgs는 다음과 같이 단순화:

func codexExecArgs(profile config.CLIProfileConf, externalSessionID, prompt string) []string {
    if externalSessionID == "" {
        return append(append([]string{}, profile.Args...), prompt)
    }
    return append(append([]string{}, profile.ResumeArgs...), externalSessionID, prompt)
}

codexResumeOptionsisCodexExecProfile, filepath import는 모두 삭제. executeCodexExecprofile.ResumeArgs == nil인 경우 명시적 에러를 반환:

if profile.ResumeArgs == nil {
    return fmt.Errorf("cli adapter: codex-exec mode requires resume_args in profile %q", agent)
}

수정 파일 및 체크리스트

  • apps/node/internal/adapters/cli/codex_exec.go에서 isCodexExecProfile, codexResumeOptions를 삭제한다.
  • codexExecArgs 시그니처를 새 형태로 바꾸고 호출부를 갱신한다.
  • apps/node/internal/adapters/cli/cli.goExecute/Start/TerminateSession에서 분기 조건을 profile.Mode == "codex-exec"로 통일한다.
  • executeCodexExec 진입부에 ResumeArgs == nil 가드를 추가한다.
  • 더 이상 사용하지 않는 path/filepath import를 제거한다.

테스트 작성

apps/node/internal/adapters/cli/codex_exec_blackbox_test.go(cli_test_layout_cleanup 작업 후 위치) 또는 현재 persistent/codex_exec_test.go에 다음 케이스를 추가/수정:

  • TestCodexExecArgs_NoSessionUsesProfileArgs
  • TestCodexExecArgs_WithSessionUsesResumeArgs
  • TestExecuteCodexExec_MissingResumeArgsErrors

기존 codexResumeOptions 단위 테스트는 모두 제거.

중간 검증

go test ./apps/node/internal/adapters/cli/...

예상 결과: 신규 케이스 포함 PASS, 옵션 화이트리스트 관련 케이스가 모두 사라진 상태.

[REFACTOR-3] edge.yaml codex 프로파일 갱신

문제

configs/edge.yaml:59-73의 codex 프로파일은 persistent: true만 명시하며, mode/resume_args가 없다.

해결 방법

codex:
  command: "codex"
  args:
    - "exec"
    - "--dangerously-bypass-approvals-and-sandbox"
    - "--color"
    - "never"
    - "--skip-git-repo-check"
    - "--json"
  resume_args:
    - "exec"
    - "resume"
    - "--json"
  env: []
  persistent: false           # mode가 분기를 결정하므로 false
  terminal: false
  output_format: "codex-json"
  mode: "codex-exec"

수정 파일 및 체크리스트

  • configs/edge.yaml의 codex 프로파일에 mode: codex-execresume_args 리스트를 추가한다.
  • persistentfalse로 설정해 일반 persistent 분기와 혼동되지 않게 한다.

테스트 작성

별도 단위 테스트는 추가하지 않는다. [REFACTOR-1]/[REFACTOR-2] 테스트와 최종 go test ./...로 회귀 검증.

중간 검증

go run ./apps/edge/cmd/edge --config configs/edge.yaml config print 2>/dev/null || true
rg -n 'mode: "codex-exec"|resume_args:' configs/edge.yaml

예상 결과: mode, resume_args가 codex 프로파일 아래에 보인다.

수정 파일 요약

파일 항목
packages/config/config.go REFACTOR-1
packages/config/config_test.go REFACTOR-1
apps/node/internal/adapters/cli/cli.go REFACTOR-2
apps/node/internal/adapters/cli/codex_exec.go REFACTOR-2
apps/node/internal/adapters/cli/codex_exec_blackbox_test.go (또는 현 위치) REFACTOR-2
configs/edge.yaml REFACTOR-3

최종 검증

go build ./...
go test ./...

예상 결과: 전부 PASS, command sniffing 잔재 없음.