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
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*
* CLI 调用方式:
* codex exec --json --sandbox danger-full-access --add-dir .git --config approval_policy="on-request" "prompt"
* codex exec resume SESSION_ID --json --config approval_policy="on-request" "prompt"
* codex exec resume SESSION_ID --json --config sandbox_mode="danger-full-access" --config approval_policy="on-request" "prompt"
*
* NDJSON 事件格式:
* thread.started → session_init (含 thread_id)
Expand Down Expand Up @@ -437,6 +437,7 @@ export class CodexAgentService implements AgentService {
const approvalPolicy = getCodexApprovalPolicy();
const effortLevel = getCatEffort(this.catId as string, undefined, 'openai');
const reasoningArgs = ['--config', `model_reasoning_effort="${effortLevel}"`];
const sandboxConfigArgs = ['--config', `sandbox_mode=${toTomlString(sandboxMode)}`];
const approvalArgs = ['--config', `approval_policy="${approvalPolicy}"`];
const ctxConfig = getCatContextWindowConfig(this.catId as string);
const contextWindowArgs: string[] = ctxConfig
Expand Down Expand Up @@ -530,10 +531,12 @@ export class CodexAgentService implements AgentService {
}
const developerInstructionsArgs = l0Result.args;

// resume 子命令不接受 --sandbox(sandbox 在创建时已锁定)
// resume 子命令不接受 --sandbox / --add-dir, but it does accept
// sandbox_mode through --config. Replay the configured sandbox there so
// resumed Codex turns cannot drift back to a CLI default sandbox on Windows.
// --add-dir .git: 允许写入 .git/ 目录(index.lock、objects、refs),解锁 git commit
// 注意:旧 session resume 时沿用创建时的沙箱参数,不会带 --add-dir。
// 这是预期行为——新建会话即可获得 .git 写入权限
// 注意:旧 session resume 时仍不会带 --add-dir。这是预期行为——新建会话
// 才能获得额外目录授权
// Incident 2026-05-29 (cross-thread-context-contamination): prompt 正文经 stdin
// 传入(见下方 cliOpts.stdinInput),绝不进 argv —— 否则 `ps -o command=` /
// /proc/<pid>/cmdline 会把完整对话历史(含跨 thread/猫/用户内容)暴露给任何
Expand Down Expand Up @@ -568,6 +571,7 @@ export class CodexAgentService implements AgentService {
...dedup(modelArgs),
...dedup(reasoningArgs),
...dedup(contextWindowArgs),
...dedup(sandboxConfigArgs),
...dedup(approvalArgs),
...dedup(developerInstructionsArgs),
...dedup(customProviderArgs),
Expand Down
32 changes: 30 additions & 2 deletions packages/api/test/codex-agent-service.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,12 +237,13 @@ describe('CodexAgentService Tests (CLI mode)', { concurrency: false }, () => {
// Incident 2026-05-29: prompt 走 stdin,argv 末尾是 '-'(codex 从 stdin 读 PROMPT)
assert.equal(args.at(-1), '-', 'prompt 走 stdin,argv 末尾是 -');
assert.equal(proc.stdinData, 'Continue', 'prompt 经 stdin 传入');
// resume 子命令不接受 --sandboxsandbox 在创建时已锁定)
// resume 子命令不接受 --sandboxsandbox mode is replayed through --config.
assert.ok(!args.includes('--sandbox'), 'resume args must not include --sandbox');
assert.ok(args.includes('--json'), 'resume args must include --json');
const modelFlagIndex = args.indexOf('--model');
assert.ok(modelFlagIndex >= 0, 'resume args must include --model');
assert.equal(args[modelFlagIndex + 1], 'gpt-5.3-codex');
assert.ok(args.includes('sandbox_mode="danger-full-access"'), 'resume args must preserve default sandbox mode');
assert.ok(args.includes('--config'), 'resume args must include approval policy override');
assert.ok(args.includes('approval_policy="on-request"'), 'default approval policy should be on-request');
assert.ok(!args.includes('approval_policy=\\"on-request\\"'), 'argv should not contain literal backslash escapes');
Expand Down Expand Up @@ -559,6 +560,32 @@ describe('CodexAgentService Tests (CLI mode)', { concurrency: false }, () => {
}
});

test('uses env-configured sandbox for resume through config override', async () => {
const oldSandbox = process.env.CAT_CODEX_SANDBOX_MODE;
process.env.CAT_CODEX_SANDBOX_MODE = 'read-only';

try {
const proc = createMockProcess();
const spawnFn = createMockSpawnFn(proc);
const service = new CodexAgentService({ l0CompilerFn: fakeL0Compiler, spawnFn });

const promise = collect(service.invoke('resume configurable', { sessionId: 'thread-config-resume' }));
emitCodexEvents(proc, [{ type: 'thread.started', thread_id: 'thread-config-resume' }]);
await promise;

const args = spawnFn.mock.calls[0].arguments[1];
assert.equal(args[1], 'resume');
assert.ok(!args.includes('--sandbox'), 'resume args must not include --sandbox');
assert.ok(args.includes('sandbox_mode="read-only"'), 'resume sandbox should follow CAT_CODEX_SANDBOX_MODE');
} finally {
if (oldSandbox === undefined) {
delete process.env.CAT_CODEX_SANDBOX_MODE;
} else {
process.env.CAT_CODEX_SANDBOX_MODE = oldSandbox;
}
}
});

test('falls back to defaults for invalid sandbox/approval env values', async () => {
const oldSandbox = process.env.CAT_CODEX_SANDBOX_MODE;
const oldApproval = process.env.CAT_CODEX_APPROVAL_POLICY;
Expand Down Expand Up @@ -607,7 +634,7 @@ describe('CodexAgentService Tests (CLI mode)', { concurrency: false }, () => {
assert.ok(args.includes('--sandbox'), 'new session must still include --sandbox');
});

test('resume session does NOT include --add-dir (sandbox locked at creation)', async () => {
test('resume session does NOT include --add-dir but preserves sandbox mode via config', async () => {
const proc = createMockProcess();
const spawnFn = createMockSpawnFn(proc);
const service = new CodexAgentService({ l0CompilerFn: fakeL0Compiler, spawnFn });
Expand All @@ -619,6 +646,7 @@ describe('CodexAgentService Tests (CLI mode)', { concurrency: false }, () => {
const args = spawnFn.mock.calls[0].arguments[1];
assert.ok(!args.includes('--add-dir'), 'resume args must not include --add-dir');
assert.ok(!args.includes('--sandbox'), 'resume args must not include --sandbox');
assert.ok(args.includes('sandbox_mode="danger-full-access"'), 'resume args must preserve sandbox mode');
});

test('custom provider: model passed via --config as-is', async () => {
Expand Down
Loading