Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions cmd/onecli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ type agentSpec struct {
agentName string
baseDir string // home-relative config dir (skills/hooks/plugins live here)
configDir string // VS Code-style app dir name; non-empty enables Electron proxy-settings injection.
skipHook bool // true for agents that don't support Claude Code-style UserPromptSubmit hooks.
skipHook bool // true when the gateway hook shouldn't be registered — either the agent has no Claude Code-style hooks (Hermes), or it renders injected hook context visibly in the transcript (Codex), where the auto-loaded onecli-gateway skill carries the same guidance without the noise.
pluginGateway bool // true for agents that load the transform_tool_result recovery plugin (e.g. Hermes).
dockerSandbox bool // true for agents that run tools in a Docker sandbox needing TERMINAL_DOCKER_* injection.
needsAnthropicKey bool // true for agents that refuse to start without a provider key in the env (e.g. OpenClaw); a placeholder is ensured, the gateway swaps in the real key per request.
Expand All @@ -472,7 +472,11 @@ var supportedAgents = []struct {
}{
{[]string{"claude"}, agentSpec{agentName: "Claude Code", baseDir: ".claude"}},
{[]string{"cursor", "agent"}, agentSpec{agentName: "Cursor", baseDir: ".cursor", configDir: "Cursor"}},
{[]string{"codex"}, agentSpec{agentName: "Codex", baseDir: ".agents", nativeProxyConfig: ".codex", hooksFile: ".codex/hooks.json"}},
// Codex skips the hook: it echoes injected hook context into the
// transcript (Claude injects it silently), so the hook is pure noise
// there. The onecli-gateway skill installed above auto-loads under the
// gateway and carries the same guidance.
{[]string{"codex"}, agentSpec{agentName: "Codex", baseDir: ".agents", skipHook: true, nativeProxyConfig: ".codex"}},
{[]string{"hermes"}, agentSpec{agentName: "Hermes", baseDir: ".hermes", skipHook: true, pluginGateway: true, dockerSandbox: true}},
{[]string{"opencode"}, agentSpec{agentName: "OpenCode", baseDir: ".opencode"}},
// OpenClaw loads skills from ~/.openclaw/skills; its hook system is its
Expand Down
31 changes: 29 additions & 2 deletions cmd/onecli/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func TestAgentSkillDir(t *testing.T) {
{"claude", agentSpec{agentName: "Claude Code", baseDir: ".claude"}, true},
{"cursor", agentSpec{agentName: "Cursor", baseDir: ".cursor", configDir: "Cursor"}, true},
{"agent", agentSpec{agentName: "Cursor", baseDir: ".cursor", configDir: "Cursor"}, true},
{"codex", agentSpec{agentName: "Codex", baseDir: ".agents", nativeProxyConfig: ".codex", hooksFile: ".codex/hooks.json"}, true},
{"codex", agentSpec{agentName: "Codex", baseDir: ".agents", skipHook: true, nativeProxyConfig: ".codex"}, true},
{"hermes", agentSpec{agentName: "Hermes", baseDir: ".hermes", skipHook: true, pluginGateway: true, dockerSandbox: true}, true},
{"opencode", agentSpec{agentName: "OpenCode", baseDir: ".opencode"}, true},
{"openclaw", agentSpec{agentName: "OpenClaw", baseDir: ".openclaw", skipHook: true, needsAnthropicKey: true}, true},
Expand Down Expand Up @@ -192,6 +192,27 @@ func TestEnsureEnv(t *testing.T) {
})
}

func TestCodexSkipsGatewayHook(t *testing.T) {
// Codex renders injected hook context visibly in its transcript
// (Claude injects it silently), so `onecli run` must NOT register the
// gateway hook for Codex — the auto-loaded onecli-gateway skill carries
// the same guidance without the per-prompt noise.
spec, ok := agentSkillDir("codex")
if !ok {
t.Fatal("codex spec not found")
}
if !spec.skipHook {
t.Error("codex must set skipHook so the gateway hook is not registered")
}
if spec.hooksFile != "" {
t.Errorf("codex hooksFile = %q, want empty (hook is skipped)", spec.hooksFile)
}
// Claude, by contrast, keeps the hook (silent injection there).
if claude, _ := agentSkillDir("claude"); claude.skipHook {
t.Error("claude must keep the gateway hook (skipHook should be false)")
}
}

func TestProxyURLWithHost(t *testing.T) {
tests := []struct{ name, raw, host, want string }{
{"rewrites host keeping port+creds", "http://aoc_tok:x@127.0.0.1:10255", "host.docker.internal", "http://aoc_tok:x@host.docker.internal:10255"},
Expand Down Expand Up @@ -773,7 +794,13 @@ func TestGatewayDetectHook_EmitsJSONEnvelope(t *testing.T) {
})
}

func TestMaybeInstallGatewayHook_CodexHooksFile(t *testing.T) {
// TestMaybeInstallGatewayHook_DedicatedHooksFile covers the dedicated
// hooks-file registration path (a home-relative hooksFile, no matcher, plus
// the one-time trust notice), exercised via a Codex-style ~/.codex/hooks.json.
// Note: `onecli run` no longer registers this hook for Codex itself (Codex
// renders injected hook context visibly; see supportedAgents) — this verifies
// the underlying helper for any agent that opts into a dedicated hooks file.
func TestMaybeInstallGatewayHook_DedicatedHooksFile(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("test overrides HOME, which UserHomeDir ignores on windows")
}
Expand Down
Loading