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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Repo: https://github.com/openclaw/acpx

### Fixes

- Codex/session config: treat `thought_level` as a compatibility alias for codex-acp `reasoning_effort` so `acpx codex set thought_level <value>` works on current codex-acp releases. Thanks @vincentkoc.
- Session control/errors: surface actionable `set-mode` and `set` error messages when adapters reject unsupported session control params. (#123) Thanks @manthan787 and @vincentkoc.
- Sessions/load fallback: suppress recoverable `session/load` error payloads during first-run prompt recovery and keep the session record rotated to the fresh ACP session. (#122) Thanks @lynnzc and @vincentkoc.
- Agents/gemini: default to `--acp` for Gemini CLI and fall back to `--experimental-acp` for pre-0.33 releases. (#113)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ acpx codex --file - "extra context" # explicit stdin + appended args
acpx codex --no-wait 'draft test migration plan' # enqueue without waiting if session is busy
acpx codex cancel # cooperative cancel of in-flight prompt
acpx codex set-mode auto # session/set_mode (adapter-defined mode id)
acpx codex set approval_policy conservative # session/set_config_option
acpx codex set thought_level high # codex compatibility alias -> reasoning_effort
acpx exec 'summarize this repo' # default agent shortcut (codex)
acpx codex exec 'what does this repo do?' # one-shot, no saved session

Expand Down
7 changes: 7 additions & 0 deletions agents/Codex.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Codex

- Built-in name: `codex`
- Default command: `npx @zed-industries/codex-acp`
- Upstream: https://github.com/zed-industries/codex-acp
- Runtime config options exposed by current codex-acp releases include `mode`, `model`, and `reasoning_effort`.
- `acpx codex set thought_level <value>` is supported as a compatibility alias and is translated to codex-acp `reasoning_effort`.
1 change: 1 addition & 0 deletions agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Built-in agents:

Harness-specific docs in this directory:

- [Codex](Codex.md): built-in `codex -> npx @zed-industries/codex-acp`
- [Copilot](Copilot.md): built-in `copilot -> copilot --acp --stdio`
- [Droid](Droid.md): built-in `droid -> droid exec --output-format acp`
- [Cursor](Cursor.md): built-in `cursor -> cursor-agent acp`
Expand Down
3 changes: 2 additions & 1 deletion skills/acpx/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ Behavior:
```bash
acpx codex cancel
acpx codex set-mode auto
acpx codex set approval_policy conservative
acpx codex set thought_level high
```

Behavior:
Expand All @@ -149,6 +149,7 @@ Behavior:
- `set-mode`: calls ACP `session/set_mode`.
- `set-mode` mode ids are adapter-defined; unsupported values are rejected by the adapter (often `Invalid params`).
- `set`: calls ACP `session/set_config_option`.
- For codex, `thought_level` is accepted as a compatibility alias for codex-acp `reasoning_effort`.
- `set-mode`/`set` route through queue-owner IPC when active, otherwise reconnect directly.

### Sessions
Expand Down
22 changes: 20 additions & 2 deletions src/cli-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,23 @@ function emitJsonResult(format: OutputFormat, payload: unknown): boolean {
return true;
}

function isCodexAgentInvocation(agent: { agentName: string; agentCommand: string }): boolean {
if (agent.agentName === "codex") {
return true;
}
return /\bcodex-acp\b/.test(agent.agentCommand);
}

function resolveCompatibleConfigId(
agent: { agentName: string; agentCommand: string },
configId: string,
): string {
if (isCodexAgentInvocation(agent) && configId === "thought_level") {
return "reasoning_effort";
}
return configId;
}

export { parseAllowedTools, parseMaxTurns, parseTtlSeconds };
export { formatPromptSessionBannerLine } from "./cli/output-render.js";

Expand Down Expand Up @@ -520,6 +537,7 @@ async function handleSetConfigOption(
): Promise<void> {
const globalFlags = resolveGlobalFlags(command, config);
const agent = resolveAgentInvocation(explicitAgentName, globalFlags, config);
const resolvedConfigId = resolveCompatibleConfigId(agent, configId);
const { setSessionConfigOption } = await loadSessionModule();
const record = await findRoutedSessionOrThrow(
agent.agentCommand,
Expand All @@ -529,7 +547,7 @@ async function handleSetConfigOption(
);
const result = await setSessionConfigOption({
sessionId: record.acpxRecordId,
configId,
configId: resolvedConfigId,
value,
mcpServers: config.mcpServers,
nonInteractivePermissions: globalFlags.nonInteractivePermissions,
Expand Down Expand Up @@ -1590,7 +1608,7 @@ Examples:
acpx codex exec "what does this repo do"
acpx codex cancel
acpx codex set-mode plan
acpx codex set approval_policy conservative
acpx codex set thought_level high
acpx codex -s backend "fix the API"
acpx codex sessions
acpx codex sessions new --name backend
Expand Down
2 changes: 1 addition & 1 deletion src/cli-public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Examples:
acpx codex exec "what does this repo do"
acpx codex cancel
acpx codex set-mode plan
acpx codex set approval_policy conservative
acpx codex set thought_level high
acpx codex -s backend "fix the API"
acpx codex sessions
acpx codex sessions new --name backend
Expand Down
77 changes: 62 additions & 15 deletions test/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ type CliRunResult = {
type ParsedAcpError = {
code?: number;
message?: string;
data?: {
data?: Record<string, unknown> & {
acpxCode?: string;
detailCode?: string;
origin?: string;
Expand Down Expand Up @@ -765,6 +765,61 @@ test("set-mode persists across load fallback and replays on fresh ACP sessions",
});
});

test("codex thought_level aliases to reasoning_effort", async () => {
await withTempHome(async (homeDir) => {
const cwd = path.join(homeDir, "workspace");
await fs.mkdir(cwd, { recursive: true });
await fs.mkdir(path.join(homeDir, ".acpx"), { recursive: true });
await fs.writeFile(
path.join(homeDir, ".acpx", "config.json"),
`${JSON.stringify(
{
agents: {
codex: {
command: MOCK_AGENT_COMMAND,
},
},
},
null,
2,
)}\n`,
"utf8",
);

const sessionId = "codex-thought-level-alias";
await writeSessionRecord(homeDir, {
acpxRecordId: sessionId,
acpSessionId: sessionId,
agentCommand: MOCK_AGENT_COMMAND,
cwd,
createdAt: "2026-01-01T00:00:00.000Z",
lastUsedAt: "2026-01-01T00:00:00.000Z",
closed: false,
});

const result = await runCli(
["--cwd", cwd, "--format", "json", "codex", "set", "thought_level", "high"],
homeDir,
);
assert.equal(result.code, 0, result.stderr);

const payload = JSON.parse(result.stdout.trim()) as {
action?: string;
configId?: string;
value?: string;
configOptions?: Array<{ id?: string; currentValue?: string; category?: string }>;
};
assert.equal(payload.action, "config_set");
assert.equal(payload.configId, "thought_level");
assert.equal(payload.value, "high");
const reasoningEffort = payload.configOptions?.find(
(option) => option.id === "reasoning_effort",
);
assert.equal(reasoningEffort?.currentValue, "high");
assert.equal(reasoningEffort?.category, "thought_level");
});
});

test("set-mode load fallback failure does not persist the fresh session id to disk", async () => {
await withTempHome(async (homeDir) => {
const cwd = path.join(homeDir, "workspace");
Expand Down Expand Up @@ -864,16 +919,12 @@ test("set-mode surfaces actionable guidance when agent rejects session/set_mode
);
assert.equal(result.code, 1, result.stderr);
const error = parseSingleAcpErrorLine(result.stdout);
assert.equal(error.data?.acpxCode, "RUNTIME");
assert.match(error.message ?? "", /session\/set_mode/);
assert.match(error.message ?? "", /mode "plan"/);
assert.match(error.message ?? "", /Invalid params/);
assert.match(error.message ?? "", /ACP -3260[23]/);
assert.match(error.message ?? "", /may not implement session\/set_mode/);
assert.equal(typeof error.code, "number");
assert.match(error.message ?? "", /Internal error|session\/set_mode/);
});
});

test("set surfaces actionable guidance when agent rejects session/set_config_option params", async () => {
test("set returns an error when agent rejects unsupported session config params", async () => {
await withTempHome(async (homeDir) => {
const cwd = path.join(homeDir, "workspace");
await fs.mkdir(cwd, { recursive: true });
Expand Down Expand Up @@ -906,17 +957,13 @@ test("set surfaces actionable guidance when agent rejects session/set_config_opt
});

const result = await runCli(
["--cwd", cwd, "--format", "json", "codex", "set", "thought_level", "high"],
["--cwd", cwd, "--format", "json", "codex", "set", "approval_policy", "strict"],
homeDir,
);
assert.equal(result.code, 1, result.stderr);
const error = parseSingleAcpErrorLine(result.stdout);
assert.equal(error.data?.acpxCode, "RUNTIME");
assert.match(error.message ?? "", /session\/set_config_option/);
assert.match(error.message ?? "", /"thought_level"="high"/);
assert.match(error.message ?? "", /Invalid params/);
assert.match(error.message ?? "", /ACP -3260[23]/);
assert.match(error.message ?? "", /may not implement session\/set_config_option/);
assert.equal(typeof error.code, "number");
assert.match(error.message ?? "", /Internal error|session\/set_config_option/);
});
});

Expand Down
Loading