diff --git a/.changeset/calm-codex-flags.md b/.changeset/calm-codex-flags.md new file mode 100644 index 00000000..ea350353 --- /dev/null +++ b/.changeset/calm-codex-flags.md @@ -0,0 +1,5 @@ +--- +"@stoneforge/smithy": patch +--- + +Update Codex interactive sessions to use the documented workspace-write sandbox flag. diff --git a/packages/smithy/src/providers/codex/interactive.ts b/packages/smithy/src/providers/codex/interactive.ts index f2d28c9a..f022117e 100644 --- a/packages/smithy/src/providers/codex/interactive.ts +++ b/packages/smithy/src/providers/codex/interactive.ts @@ -21,6 +21,31 @@ import { shellQuote } from '../shell-quote.js'; // Helpers // ============================================================================ +type CodexInteractiveArgOptions = Pick< + InteractiveSpawnOptions, + 'resumeSessionId' | 'workingDirectory' | 'model' +>; + +export function buildCodexInteractiveArgs( + options: CodexInteractiveArgOptions, + platform: NodeJS.Platform = process.platform, +): string[] { + const quote = (value: string) => shellQuote(value, platform); + const args: string[] = []; + + if (options.resumeSessionId) { + args.push('resume', quote(options.resumeSessionId), '--sandbox', 'workspace-write'); + } else { + args.push('--sandbox', 'workspace-write', '--cd', quote(options.workingDirectory)); + } + + if (options.model) { + args.push('--model', quote(options.model)); + } + + return args; +} + // ============================================================================ // Codex Interactive Session // ============================================================================ @@ -95,7 +120,7 @@ export class CodexInteractiveProvider implements InteractiveProvider { } async spawn(options: InteractiveSpawnOptions): Promise { - const args = this.buildArgs(options); + const args = buildCodexInteractiveArgs(options); const env: Record = { ...(process.env as Record), @@ -156,20 +181,4 @@ export class CodexInteractiveProvider implements InteractiveProvider { } } - private buildArgs(options: InteractiveSpawnOptions): string[] { - const args: string[] = []; - - if (options.resumeSessionId) { - args.push('resume', shellQuote(options.resumeSessionId), '--full-auto'); - } else { - args.push('--full-auto', '--cd', shellQuote(options.workingDirectory)); - } - - // Add model flag if provided - if (options.model) { - args.push('--model', shellQuote(options.model)); - } - - return args; - } } diff --git a/packages/smithy/src/providers/codex/provider.bun.test.ts b/packages/smithy/src/providers/codex/provider.bun.test.ts index 02ccf979..e2344d8e 100644 --- a/packages/smithy/src/providers/codex/provider.bun.test.ts +++ b/packages/smithy/src/providers/codex/provider.bun.test.ts @@ -6,6 +6,7 @@ import { describe, it, expect, mock, beforeEach, afterEach } from 'bun:test'; import type { CodexClient, CodexModelInfo } from './server-manager.js'; +import { buildCodexInteractiveArgs } from './interactive.js'; import { posixShellQuote, shellQuote } from '../shell-quote.js'; // --------------------------------------------------------------------------- @@ -204,68 +205,53 @@ describe('CodexHeadlessProvider model passthrough', () => { // --------------------------------------------------------------------------- describe('CodexInteractiveProvider model flag', () => { - // Use the POSIX form directly so assertions are deterministic regardless of - // the host OS the tests run on. A separate suite below covers the Windows - // quoter. - function buildArgs(options: { - resumeSessionId?: string; - workingDirectory: string; - model?: string; - }): string[] { - const shellQuote = posixShellQuote; - const args: string[] = []; - - if (options.resumeSessionId) { - args.push('resume', shellQuote(options.resumeSessionId), '--full-auto'); - } else { - args.push('--full-auto', '--cd', shellQuote(options.workingDirectory)); - } - - // Add model flag if provided - if (options.model) { - args.push('--model', shellQuote(options.model)); - } - - return args; - } + it('builds interactive spawn args with the documented workspace-write sandbox', () => { + const args = buildCodexInteractiveArgs({ + workingDirectory: '/workspace', + model: 'gpt-4o', + }, 'linux'); + + expect(args).toEqual(['--sandbox', 'workspace-write', '--cd', "'/workspace'", '--model', "'gpt-4o'"]); + }); it('should include --model flag when model is provided', () => { - const args = buildArgs({ + const args = buildCodexInteractiveArgs({ workingDirectory: '/workspace', model: 'gpt-4o', - }); + }, 'linux'); expect(args).toContain('--model'); expect(args).toContain("'gpt-4o'"); - expect(args).toEqual(['--full-auto', '--cd', "'/workspace'", '--model', "'gpt-4o'"]); + expect(args).toEqual(['--sandbox', 'workspace-write', '--cd', "'/workspace'", '--model', "'gpt-4o'"]); }); it('should not include --model flag when model is undefined', () => { - const args = buildArgs({ + const args = buildCodexInteractiveArgs({ workingDirectory: '/workspace', - }); + }, 'linux'); expect(args).not.toContain('--model'); - expect(args).toEqual(['--full-auto', '--cd', "'/workspace'"]); + expect(args).toEqual(['--sandbox', 'workspace-write', '--cd', "'/workspace'"]); }); it('should include --model flag when resuming with model', () => { - const args = buildArgs({ + const args = buildCodexInteractiveArgs({ resumeSessionId: 'thr_abc123', workingDirectory: '/workspace', model: 'o3-mini', - }); + }, 'linux'); expect(args).toContain('resume'); expect(args).toContain('--model'); expect(args).toContain("'o3-mini'"); + expect(args).toEqual(['resume', "'thr_abc123'", '--sandbox', 'workspace-write', '--model', "'o3-mini'"]); }); it('should properly quote model names with special characters', () => { - const args = buildArgs({ + const args = buildCodexInteractiveArgs({ workingDirectory: '/workspace', model: "model's-name", - }); + }, 'linux'); expect(args).toContain("--model"); expect(args).toContain("'model'\\''s-name'");