iop/apps/node/internal/adapters/cli/persistent/codex_exec_test.go
toki 157a8a7076 feat: add console events, codex execution support and update config
- add console_events.go for edge console event handling
- add codex_exec.go for CLI codex execution
- add codex_exec_test.go for persistent CLI tests
- update console.go and oneshot.go with new features
- update config and edge.yaml for new settings
- update README files for edge and node apps
2026-05-04 07:55:08 +09:00

225 lines
5.3 KiB
Go

package persistent_test
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"go.uber.org/zap"
clipkg "iop/apps/node/internal/adapters/cli"
"iop/apps/node/internal/adapters/cli/internal/testutil"
noderuntime "iop/apps/node/internal/runtime"
"iop/packages/config"
)
func TestCLIStartSkipsCodexExecPersistentStartup(t *testing.T) {
testutil.RequireUnixShell(t)
codex := writeFakeCodex(t, `#!/usr/bin/env sh
echo "codex should not be started during adapter startup" >&2
exit 42
`)
cfg := config.CLIConf{
Enabled: true,
Profiles: map[string]config.CLIProfileConf{
"codex": {
Command: codex,
Args: []string{"exec", "--dangerously-bypass-approvals-and-sandbox"},
Persistent: true,
},
},
}
c := clipkg.New(cfg, zap.NewNop())
if err := c.Start(context.Background()); err != nil {
t.Fatalf("Start should not invoke codex exec: %v", err)
}
}
func TestCLIExecuteCodexExecPersistentResumesLogicalSession(t *testing.T) {
testutil.RequireUnixShell(t)
codex := writeFakeCodex(t, `#!/usr/bin/env sh
if [ "$1" != "exec" ]; then
echo "unexpected command: $*" >&2
exit 2
fi
if [ "$2" = "resume" ]; then
shift 2
for arg in "$@"; do
if [ "$arg" = "--color" ]; then
echo "resume received unsupported --color option" >&2
exit 12
fi
done
session=""
prompt=""
for arg in "$@"; do
case "$arg" in
--*) continue ;;
esac
if [ -z "$session" ]; then
session="$arg"
else
prompt="$arg"
fi
done
printf "session id: %s\n" "$session" >&2
printf "reply:resume:%s:%s\n" "$session" "$prompt"
exit 0
fi
last=""
for arg in "$@"; do
last="$arg"
done
printf "session id: codex-session-1\n" >&2
printf "reply:first:%s\n" "$last"
`)
cfg := config.CLIConf{
Enabled: true,
Profiles: map[string]config.CLIProfileConf{
"codex": {
Command: codex,
Args: []string{
"exec",
"--dangerously-bypass-approvals-and-sandbox",
"--color",
"never",
"--skip-git-repo-check",
},
Persistent: true,
},
},
}
c := clipkg.New(cfg, zap.NewNop())
ctx := context.Background()
first := &testutil.FakeSink{}
if err := c.Execute(ctx, noderuntime.ExecutionSpec{
RunID: "run-1",
Model: "codex",
SessionID: "session-a",
Input: map[string]any{"prompt": "first"},
}, first); err != nil {
t.Fatalf("first execute: %v", err)
}
if combined := testutil.CollectDeltas(first.Events()); !strings.Contains(combined, "reply:first:first") {
t.Fatalf("first execute output: %q", combined)
}
second := &testutil.FakeSink{}
if err := c.Execute(ctx, noderuntime.ExecutionSpec{
RunID: "run-2",
Model: "codex",
SessionID: "session-a",
Input: map[string]any{"prompt": "second"},
}, second); err != nil {
t.Fatalf("second execute: %v", err)
}
if combined := testutil.CollectDeltas(second.Events()); !strings.Contains(combined, "reply:resume:codex-session-1:second") {
t.Fatalf("second execute output: %q", combined)
}
}
func TestCLIExecuteCodexExecJSONFormatStreamsAgentMessageAndResumes(t *testing.T) {
testutil.RequireUnixShell(t)
codex := writeFakeCodex(t, `#!/usr/bin/env sh
if [ "$1" != "exec" ]; then
echo "unexpected command: $*" >&2
exit 2
fi
if [ "$2" = "resume" ]; then
shift 2
session=""
prompt=""
for arg in "$@"; do
case "$arg" in
--*) continue ;;
esac
if [ -z "$session" ]; then
session="$arg"
else
prompt="$arg"
fi
done
printf '{"type":"thread.started","thread_id":"%s"}\n' "$session"
printf '{"type":"item.completed","item":{"id":"item_0","type":"agent_message","text":"resume:%s:%s"}}\n' "$session" "$prompt"
exit 0
fi
last=""
for arg in "$@"; do
last="$arg"
done
printf '{"type":"thread.started","thread_id":"codex-session-json-1"}\n'
printf '{"type":"item.completed","item":{"id":"item_0","type":"agent_message","text":"first:%s"}}\n' "$last"
`)
cfg := config.CLIConf{
Enabled: true,
Profiles: map[string]config.CLIProfileConf{
"codex": {
Command: codex,
Args: []string{
"exec",
"--dangerously-bypass-approvals-and-sandbox",
"--color",
"never",
"--skip-git-repo-check",
"--json",
},
Persistent: true,
OutputFormat: "codex-json",
},
},
}
c := clipkg.New(cfg, zap.NewNop())
ctx := context.Background()
first := &testutil.FakeSink{}
if err := c.Execute(ctx, noderuntime.ExecutionSpec{
RunID: "run-1",
Model: "codex",
SessionID: "session-a",
Input: map[string]any{"prompt": "hello"},
}, first); err != nil {
t.Fatalf("first execute: %v", err)
}
if combined := testutil.CollectDeltas(first.Events()); combined != "first:hello" {
t.Fatalf("first deltas: got %q want %q", combined, "first:hello")
}
second := &testutil.FakeSink{}
if err := c.Execute(ctx, noderuntime.ExecutionSpec{
RunID: "run-2",
Model: "codex",
SessionID: "session-a",
Input: map[string]any{"prompt": "again"},
}, second); err != nil {
t.Fatalf("second execute: %v", err)
}
if combined := testutil.CollectDeltas(second.Events()); combined != "resume:codex-session-json-1:again" {
t.Fatalf("second deltas: got %q", combined)
}
}
func writeFakeCodex(t *testing.T, script string) string {
t.Helper()
dir := t.TempDir()
path := filepath.Join(dir, "codex")
if err := os.WriteFile(path, []byte(script), 0o755); err != nil {
t.Fatalf("write fake codex: %v", err)
}
return path
}