diff --git a/src/main/agent-launch/agent-launch-executor.test.ts b/src/main/agent-launch/agent-launch-executor.test.ts index c27d84cf0379..f7c1bb9a85b1 100644 --- a/src/main/agent-launch/agent-launch-executor.test.ts +++ b/src/main/agent-launch/agent-launch-executor.test.ts @@ -630,7 +630,9 @@ describe('caller-supplied launch inputs', () => { const result = await h.run({ agent: 'claude', target: EXISTING, agentArgs: '--model opus' }) expect(result.outcome.kind).toBe('structured') - expect(result.warning).toContain('per-launch argument override was ignored') + expect(result.warning).toBe( + "Started a structured chat session, which doesn't use launch arguments; the per-launch override was ignored." + ) }) it('warns when a structured session ignored an explicit "no arguments" too', async () => { @@ -640,7 +642,9 @@ describe('caller-supplied launch inputs', () => { // The structured path reads the bypass-permissions bit from the user's SETTINGS default, so a // caller that asked for no arguments can still get a session with more permission than it asked // for. Staying silent about that is the failure mode worth a test. - expect(result.warning).toContain('per-launch argument override was ignored') + expect(result.warning).toBe( + "Started a structured chat session, which doesn't use launch arguments; the per-launch override was ignored." + ) }) it('leaves a structured launch unwarned when it carried no arguments at all', async () => { diff --git a/src/main/agent-launch/agent-launch-executor.ts b/src/main/agent-launch/agent-launch-executor.ts index 642e59914148..f13c402ee51d 100644 --- a/src/main/agent-launch/agent-launch-executor.ts +++ b/src/main/agent-launch/agent-launch-executor.ts @@ -311,9 +311,7 @@ async function createSurface( return createTerminalSurface(execution, workspace) } -/** - * Structured chat uses saved Arguments, so a per-call override still needs a truthful warning. - */ +/** A structured chat takes no launch arguments, saved or per-call, so an override gets a warning. */ function ignoredStructuredAgentArgsWarning( intent: AgentLaunchIntent ): { warning: string } | undefined { @@ -321,7 +319,7 @@ function ignoredStructuredAgentArgsWarning( ? undefined : { warning: - 'Started a structured chat session using saved agent Arguments; the per-launch argument override was ignored.' + "Started a structured chat session, which doesn't use launch arguments; the per-launch override was ignored." } } diff --git a/src/main/claude/claude-agent-sdk-contract-pins.test.ts b/src/main/claude/claude-agent-sdk-contract-pins.test.ts index acbd988c6141..018a4f97de23 100644 --- a/src/main/claude/claude-agent-sdk-contract-pins.test.ts +++ b/src/main/claude/claude-agent-sdk-contract-pins.test.ts @@ -146,7 +146,6 @@ function resolvedLaunch(permissionMode: PermissionMode, launchArgs: string[] = [ launchArgs } as unknown as AgentSessionRecord return createClaudeStructuredLaunchResolver({ - resolveLaunchArgs: () => launchArgs, store: { getRecord: () => record, pinLaunchDirectory: vi.fn() }, resolveWorkspacePath: async () => '/repos/workspace-1', resolveCommand: () => FAKE_CLI, @@ -329,7 +328,8 @@ describe('Claude Agent SDK contract pins', () => { const scenario = scriptScenario([{ awaitUserMessage: true }, { emit: RESULT_FRAME }]) const spawns: SpawnSeen[] = [] // Driven by the real resolver, so the argv walk covers its option set and merge order, - // not a hand-written options literal. + // not a hand-written options literal. The record carries launch arguments as an older build + // stored them: the launch ignores them, as it ignores saved Arguments. const launch = await resolvedLaunch('bypassPermissions', [ '--model', 'claude-sonnet-4-5', @@ -358,13 +358,8 @@ describe('Claude Agent SDK contract pins', () => { expect(argv.filter((arg) => arg === '--dangerously-skip-permissions')).toHaveLength(1) expect(argv).not.toContain('--allow-dangerously-skip-permissions') expect(argv[argv.indexOf('--permission-mode') + 1]).toBe('default') - expect(argv[argv.indexOf('--model') + 1]).toBe('claude-sonnet-4-5') - expect(argv.flatMap((arg, index) => (arg === '--add-dir' ? [argv[index + 1]] : []))).toEqual([ - '/repo/one', - '/repo/two', - '/repo/three' - ]) - expect(argv.filter((arg) => arg === '--model')).toHaveLength(1) + expect(argv).not.toContain('--model') + expect(argv).not.toContain('--add-dir') expect(argv.filter((arg) => arg === '--output-format')).toHaveLength(1) expect(argv[argv.indexOf('--output-format') + 1]).toBe('stream-json') // Headless print mode is the SDK's only mode; `query()` never passes `-p`, diff --git a/src/main/claude/claude-structured-auth-parity.test.ts b/src/main/claude/claude-structured-auth-parity.test.ts index 2a22fae853ae..b3642d9c41bc 100644 --- a/src/main/claude/claude-structured-auth-parity.test.ts +++ b/src/main/claude/claude-structured-auth-parity.test.ts @@ -29,7 +29,6 @@ function resolverFor(options: { overlay?: Record }): ReturnType { return createClaudeStructuredLaunchResolver({ - resolveLaunchArgs: () => [], store: { getRecord: () => record(), pinLaunchDirectory: vi.fn() }, resolveWorkspacePath: async (id) => `/repos/${id}`, resolveCommand: () => '/usr/local/bin/claude', diff --git a/src/main/claude/claude-structured-launch-account-routing.test.ts b/src/main/claude/claude-structured-launch-account-routing.test.ts index 0d7babfb8f5d..eb0f2c7afa1b 100644 --- a/src/main/claude/claude-structured-launch-account-routing.test.ts +++ b/src/main/claude/claude-structured-launch-account-routing.test.ts @@ -45,7 +45,6 @@ it('launches each acquisition under the current selection, not the account it wa } const resolve = createClaudeStructuredLaunchResolver({ store: { getRecord: () => record, pinLaunchDirectory: vi.fn() }, - resolveLaunchArgs: () => [], resolveWorkspacePath: async (id) => `/repos/${id}`, resolveCommand: () => '/usr/local/bin/claude', resolveInheritedEnv: async () => ({ PATH: '/usr/bin' }), @@ -111,7 +110,6 @@ function routedResumeFixture() { const transcriptHomes = new Set() const resolve = createClaudeStructuredLaunchResolver({ store: { getRecord: () => record, pinLaunchDirectory: vi.fn() }, - resolveLaunchArgs: () => [], resolveWorkspacePath: async (id) => `/repos/${id}`, resolveCommand: () => '/usr/local/bin/claude', resolveInheritedEnv: async () => ({ PATH: '/usr/bin' }), diff --git a/src/main/claude/claude-structured-launch-args.test.ts b/src/main/claude/claude-structured-launch-args.test.ts deleted file mode 100644 index 6a788b836c96..000000000000 --- a/src/main/claude/claude-structured-launch-args.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { claudeStructuredLaunchArgs } from './claude-structured-launch-args' - -describe('Claude structured launch arguments', () => { - it('translates long flags, equals values, and the model short flag', () => { - expect( - claudeStructuredLaunchArgs([ - '-m', - 'opus', - '--effort=high', - '--chrome', - '--add-dir', - '/repo/other' - ]) - ).toEqual({ - extraArgs: { model: 'opus', effort: 'high', chrome: null }, - additionalDirectories: ['/repo/other'] - }) - }) - - it('removes SDK transport, permission, and lifecycle flags with their values', () => { - expect( - claudeStructuredLaunchArgs([ - '-p', - '--input-format', - 'text', - '--output-format=json', - '--json-schema', - '{}', - '--verbose', - '-r', - 'other-session', - '-c', - '--session-id=other-session', - '--fork-session', - '--permission-mode', - 'bypassPermissions', - '--dangerously-skip-permissions', - '--allow-dangerously-skip-permissions', - '--permission-prompt-tool', - 'other', - '--replay-user-messages', - '--include-partial-messages', - '--setting-sources', - 'none', - '--system-prompt', - 'replacement', - '--append-system-prompt-file=other.txt', - '--model', - 'opus' - ]) - ).toEqual({ extraArgs: { model: 'opus' }, additionalDirectories: [] }) - }) - - it('ignores positional prompts and tokens after --', () => { - expect( - claudeStructuredLaunchArgs(['prompt', '--model', 'opus', '--', '--effort', 'high']) - ).toEqual({ - extraArgs: { model: 'opus' }, - additionalDirectories: [] - }) - }) - it('keeps repeated and multi-value directory options in order', () => { - expect( - claudeStructuredLaunchArgs([ - '--add-dir', - '/one', - '/two', - '--model', - 'opus', - '--add-dir=/three', - '/four' - ]) - ).toEqual({ - extraArgs: { model: 'opus' }, - additionalDirectories: ['/one', '/two', '/three', '/four'] - }) - }) - - it.each([ - ['--model', 'opus', '-m', 'sonnet'], - ['--model', 'opus', 'sonnet'], - ['--effort=high', 'medium'], - ['--chrome', '--chrome'], - ['--add-dir'], - ['--add-dir='] - ])('refuses options the SDK cannot preserve: %j', (...args) => { - expect(() => claudeStructuredLaunchArgs(args)).toThrow(/Arguments/) - }) -}) diff --git a/src/main/claude/claude-structured-launch-args.ts b/src/main/claude/claude-structured-launch-args.ts deleted file mode 100644 index 1a4ac11b1da7..000000000000 --- a/src/main/claude/claude-structured-launch-args.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { StructuredAgentArgumentsError } from '../native-chat/structured-agent-arguments-error' - -/** Flags supplied by the SDK or owned by Orca's structured transport. */ -const OWNED_FLAGS = new Set([ - 'print', - 'input-format', - 'output-format', - 'json-schema', - 'verbose', - 'resume', - 'continue', - 'session-id', - 'fork-session', - 'resume-session-at', - 'resume-drops-turn', - 'no-session-persistence', - 'session-mirror', - 'await-initialize', - 'permission-mode', - 'dangerously-skip-permissions', - 'allow-dangerously-skip-permissions', - 'permission-prompt-tool', - 'permission-prompts', - 'allowedTools', - 'disallowedTools', - 'replay-user-messages', - 'include-partial-messages', - 'setting-sources', - 'system-prompt', - 'system-prompt-file', - 'append-system-prompt', - 'append-system-prompt-file' -]) - -const SHORT_FLAGS: Record = { - '-m': 'model', - '-d': 'debug', - '-p': 'print', - '-r': 'resume', - '-c': 'continue', - '-h': 'help', - '-v': 'version' -} - -/** Keep variadic directory options in the SDK array; scalar extraArgs cannot retain repetitions. */ -export function claudeStructuredLaunchArgs(args: readonly string[]): { - extraArgs: Record - additionalDirectories: string[] -} { - const extraArgs: Record = {} - const additionalDirectories: string[] = [] - for (let index = 0; index < args.length; index++) { - const arg = args[index]! - if (arg === '--') { - break - } - - const separator = arg.indexOf('=') - const rawFlag = separator === -1 ? arg : arg.slice(0, separator) - const flag = rawFlag.startsWith('--') ? rawFlag.slice(2) : SHORT_FLAGS[rawFlag] - if (!flag || flag === 'help' || flag === 'version') { - continue - } - - const values = separator === -1 ? [] : [arg.slice(separator + 1)] - while (args[index + 1] !== undefined && !args[index + 1]!.startsWith('-')) { - values.push(args[++index]!) - } - if (OWNED_FLAGS.has(flag)) { - continue - } - if (flag === 'add-dir') { - if (values.length === 0 || values.some((value) => !value)) { - throw new StructuredAgentArgumentsError('Claude', '--add-dir', 'missingValue') - } - additionalDirectories.push(...values) - continue - } - if (Object.hasOwn(extraArgs, flag) || values.length > 1) { - throw new StructuredAgentArgumentsError('Claude', `--${flag}`, 'multipleValues') - } - extraArgs[flag] = values[0] ?? null - } - return { extraArgs, additionalDirectories } -} diff --git a/src/main/claude/claude-structured-launch-resolution.test.ts b/src/main/claude/claude-structured-launch-resolution.test.ts index 19067d65a4c5..8e222c1a11c7 100644 --- a/src/main/claude/claude-structured-launch-resolution.test.ts +++ b/src/main/claude/claude-structured-launch-resolution.test.ts @@ -14,7 +14,7 @@ import { type ClaudeStructuredLaunchResolverDeps } from './claude-structured-launch-resolution' import { claudeStructuredPermissionModeForSettings } from './claude-structured-permission-mode' -import { CLAUDE_THINKING_DISPLAY_FLAG, type ClaudeCliFlag } from './claude-cli-flag-support' +import type { ClaudeCliFlag } from './claude-cli-flag-support' import { claudeProviderHandle } from '../../shared/agent-session-provider-handle-encoding' const SESSION_ID = 'orca-session-1' @@ -60,7 +60,6 @@ function resolverFor( // Manual by default so a test that is not about permissions is not silently about them. agentDefaultArgs: Record = { claude: '' }, hasTranscript: () => Promise = async () => true, - resolveLaunchArgs?: () => string[], attachmentDirectory?: string ) { return createClaudeStructuredLaunchResolver({ @@ -70,7 +69,6 @@ function resolverFor( resolveAuthPolicy: () => ({ stripAuthEnv }), resolvePermissionMode: () => claudeStructuredPermissionModeForSettings({ agentDefaultArgs }), hasTranscript, - resolveLaunchArgs: resolveLaunchArgs ?? (() => value?.launchArgs ?? []), ...(resolveEnv ? { resolveEnv } : {}), ...(attachmentDirectory ? { attachmentDirectory } : {}) }) @@ -278,75 +276,19 @@ describe('claude structured launch resolution', () => { } ) - it('passes configured arguments on start without taking over permission or session flags', async () => { - const launch = await resolverFor( - record({ - launchArgs: [ - '--model', - 'claude-sonnet-4-5', - '--dangerously-skip-permissions', - '--resume=wrong-session', - '--permission-mode', - 'bypassPermissions' - ] - }) - )({ identity: IDENTITY }) + // Saved Arguments are for terminal launches: only the Agent Permissions posture is read from them. + it.each([ + ['a new chat', IDENTITY, record()], + ['a resume', identityAt('leaf-current'), RESUMABLE] + ] as const)('passes nothing from saved Arguments on %s', async (_label, identity, value) => { + const launch = await resolverFor(value, undefined, false, { + claude: '--model opus --effort high --add-dir /extra --settings ./s.json' + })({ identity }) + expect(launch.options.extraArgs).toEqual({ 'replay-user-messages': null }) + expect(launch.options).not.toHaveProperty('additionalDirectories') expect(launch.options.model).toBeUndefined() - expect(launch.options.extraArgs).toEqual({ - model: 'claude-sonnet-4-5', - 'replay-user-messages': null - }) - expect(launch.options.permissionMode).toBeUndefined() - expect(launch.options.sessionId).toBe(launch.providerSessionId) - }) - - it('re-reads saved Arguments after a refusal and on resume instead of using a stale record', async () => { - let args = ['--model', 'one', '--model', 'two'] - const resolve = resolverFor( - record({ ...RESUMABLE, launchArgs: ['--model', 'stale'] }), - undefined, - false, - { claude: '' }, - async () => true, - () => args - ) - await expect(resolve({ identity: identityAt('leaf-current') })).rejects.toThrow(/Arguments/) - args = ['--effort', 'high'] - expect((await resolve({ identity: identityAt('leaf-current') })).options.extraArgs).toEqual({ - effort: 'high', - 'replay-user-messages': null - }) - args = [] - expect((await resolve({ identity: identityAt('leaf-current') })).options.extraArgs).toEqual({ - 'replay-user-messages': null - }) - }) - - it('passes configured arguments when resuming a transcript', async () => { - const launch = await resolverFor( - record({ - ...RESUMABLE, - launchArgs: [ - '--effort', - 'high', - '-r', - 'wrong-session', - '--add-dir', - '/one', - '/two', - '--add-dir', - '/three' - ] - }) - )({ identity: identityAt('leaf-current') }) - - expect(launch.options.resume).toBe('provider-current') - expect(launch.options.additionalDirectories).toEqual(['/one', '/two', '/three']) - expect(launch.options.extraArgs).toEqual({ - effort: 'high', - 'replay-user-messages': null - }) + expect(launch.options.effort).toBeUndefined() }) it('keeps the session launch environment pinned after account settings change', async () => { @@ -403,7 +345,6 @@ describe('claude structured launch resolution', () => { false, { claude: '' }, async () => true, - undefined, '/state/agent-session-attachments' )({ identity: IDENTITY }) @@ -411,26 +352,8 @@ describe('claude structured launch resolution', () => { expect(launch.cwd).toBe('/repos/workspace-1') }) - it('grants the attachment store beside folders the saved Arguments add', async () => { - const launch = await resolverFor( - record(), - undefined, - false, - { claude: '' }, - async () => true, - () => ['--add-dir', '/extra'], - '/state/agent-session-attachments' - )({ identity: IDENTITY }) - - expect(launch.options.additionalDirectories).toEqual([ - '/extra', - '/state/agent-session-attachments' - ]) - }) - it('builds on the supplied inherited env instead of Orca process env', async () => { const launch = await createClaudeStructuredLaunchResolver({ - resolveLaunchArgs: () => [], store: { getRecord: () => record(), pinLaunchDirectory: vi.fn() }, resolveWorkspacePath: async (id) => `/repos/${id}`, resolveCommand: () => '/usr/local/bin/claude', @@ -443,7 +366,6 @@ describe('claude structured launch resolution', () => { it('drops an inherited CLAUDE_CONFIG_DIR so the record stays the only Claude home the pin sees', async () => { const launch = await createClaudeStructuredLaunchResolver({ - resolveLaunchArgs: () => [], store: { getRecord: () => record(), pinLaunchDirectory: vi.fn() }, resolveWorkspacePath: async (id) => `/repos/${id}`, resolveCommand: () => '/usr/local/bin/claude', @@ -462,7 +384,6 @@ describe('claude structured launch resolution', () => { it('keeps a configured overlay CLAUDE_CONFIG_DIR over the dropped inherited one', async () => { const launch = await createClaudeStructuredLaunchResolver({ - resolveLaunchArgs: () => [], store: { getRecord: () => record(), pinLaunchDirectory: vi.fn() }, resolveWorkspacePath: async (id) => `/repos/${id}`, resolveCommand: () => '/usr/local/bin/claude', @@ -476,7 +397,6 @@ describe('claude structured launch resolution', () => { it('still strips an inherited auth key under a managed account', async () => { const launch = await createClaudeStructuredLaunchResolver({ - resolveLaunchArgs: () => [], store: { getRecord: () => record(), pinLaunchDirectory: vi.fn() }, resolveWorkspacePath: async (id) => `/repos/${id}`, resolveCommand: () => '/usr/local/bin/claude', @@ -514,7 +434,6 @@ describe('claude structured launch resolution', () => { makeExecutable(nodeCommand) const launch = await createClaudeStructuredLaunchResolver({ - resolveLaunchArgs: () => [], store: { getRecord: () => record(), pinLaunchDirectory: vi.fn() }, resolveWorkspacePath: async (id) => `/repos/${id}`, resolveCommand: () => claudeCommand, @@ -555,13 +474,11 @@ describe('claude structured launch resolution', () => { describe('readable Claude thinking', () => { const launchWith = ( cliFlags?: ClaudeStructuredLaunchResolverDeps['cliFlags'], - command = '/usr/local/bin/claude', - launchArgs: string[] = [] + command = '/usr/local/bin/claude' ) => createClaudeStructuredLaunchResolver({ store: { getRecord: () => record(), pinLaunchDirectory: vi.fn() }, resolveWorkspacePath: async (id) => `/repos/${id}`, - resolveLaunchArgs: () => launchArgs, resolveCommand: () => command, resolveAuthPolicy: () => ({ stripAuthEnv: false }), resolveEnv: () => ({ PROJECT_SHIM: '1', ANTHROPIC_API_KEY: 'sk-user' }), @@ -616,17 +533,4 @@ describe('readable Claude thinking', () => { expect(launch.options.extraArgs).toEqual({ 'replay-user-messages': null }) expect((await launchWith()).options.extraArgs).toEqual({ 'replay-user-messages': null }) }) - - it('keeps saved Arguments beside readable thinking, with the display left to Orca', async () => { - const launch = await launchWith( - { supports: async (flag) => flag === CLAUDE_THINKING_DISPLAY_FLAG }, - undefined, - ['--effort', 'high', '--thinking-display', 'omitted'] - ) - expect(launch.options.extraArgs).toEqual({ - effort: 'high', - 'replay-user-messages': null, - 'thinking-display': 'summarized' - }) - }) }) diff --git a/src/main/claude/claude-structured-launch-resolution.ts b/src/main/claude/claude-structured-launch-resolution.ts index 98ec3d971add..258d1fb7eb10 100644 --- a/src/main/claude/claude-structured-launch-resolution.ts +++ b/src/main/claude/claude-structured-launch-resolution.ts @@ -25,7 +25,6 @@ import { type ClaudeChildEnvSources, type ClaudeEnvDeps } from './claude-structured-child-env' -import { claudeStructuredLaunchArgs } from './claude-structured-launch-args' import type { ClaudeCliFlagSupport } from './claude-cli-flag-support' import { resolveClaudeLaunchFlags } from './claude-structured-launch-flags' import { @@ -112,7 +111,6 @@ export type ClaudeStructuredLaunch = { export type ClaudeStructuredLaunchResolverDeps = { store: Pick - resolveLaunchArgs: () => Promise | string[] resolveWorkspacePath: (workspaceId: string) => Promise resolveCommand?: () => string resolveEnv?: () => @@ -218,10 +216,8 @@ export function createClaudeStructuredLaunchResolver( // Asked as soon as the spawn's cwd and PATH are known, so it overlaps what is left to resolve. const probeLaunch = { command: sources.command, cwd, env: claudeProbeEnv(sources) } const launchFlags = resolveClaudeLaunchFlags(deps, record.sessionId, probeLaunch) - const configured = claudeStructuredLaunchArgs(await deps.resolveLaunchArgs()) const { thinkingDisplayArgs, visuals } = await launchFlags const additionalDirectories = [ - ...configured.additionalDirectories, ...(deps.attachmentDirectory ? [deps.attachmentDirectory] : []), ...(visuals ? [visuals.visuals.folder] : []) ] @@ -266,7 +262,6 @@ export function createClaudeStructuredLaunchResolver( ...(additionalDirectories.length ? { additionalDirectories } : {}), ...(visuals?.pluginDir ? { plugins: [{ type: 'local', path: visuals.pluginDir }] } : {}), extraArgs: { - ...configured.extraArgs, ...CLAUDE_STRUCTURED_BASE_OPTIONS.extraArgs, ...permission.extraArgs, ...thinkingDisplayArgs diff --git a/src/main/claude/claude-structured-launch-visuals.test.ts b/src/main/claude/claude-structured-launch-visuals.test.ts index 0411523b191c..0cd00c703496 100644 --- a/src/main/claude/claude-structured-launch-visuals.test.ts +++ b/src/main/claude/claude-structured-launch-visuals.test.ts @@ -31,7 +31,6 @@ const IDENTITY: AgentSessionJournalIdentity = { function launch(options: { prepareVisuals?: (sessionId: string) => Promise supports?: (flag: ClaudeCliFlag, launch: unknown, budgetMs?: number) => Promise - launchArgs?: string[] env?: Record attachmentDirectory?: string }) { @@ -40,7 +39,6 @@ function launch(options: { resolveWorkspacePath: async (id) => `/repos/${id}`, resolveCommand: () => '/usr/local/bin/claude', resolveAuthPolicy: () => ({ stripAuthEnv: false }), - resolveLaunchArgs: () => options.launchArgs ?? [], resolveEnv: () => options.env ?? {}, hasTranscript: async () => false, ...(options.attachmentDirectory ? { attachmentDirectory: options.attachmentDirectory } : {}), @@ -50,16 +48,15 @@ function launch(options: { } describe('a Claude chat launch with inline visuals', () => { - it('loads the skill plugin, grants the chat folder beside the user directories, and names it', async () => { + it('loads the skill plugin, grants the chat folder, and names it', async () => { const prepareVisuals = vi.fn(async () => VISUALS) const resolved = await launch({ prepareVisuals, - supports: async (flag) => flag === CLAUDE_PLUGIN_DIR_FLAG, - launchArgs: ['--add-dir', '/shared/notes'] + supports: async (flag) => flag === CLAUDE_PLUGIN_DIR_FLAG }) expect(prepareVisuals).toHaveBeenCalledWith(SESSION_ID) expect(resolved.options.plugins).toEqual([{ type: 'local', path: VISUALS.skill.pluginDir }]) - expect(resolved.options.additionalDirectories).toEqual(['/shared/notes', VISUALS.folder]) + expect(resolved.options.additionalDirectories).toEqual([VISUALS.folder]) expect(resolved.env?.[NATIVE_CHAT_VISUALS_DIR_ENV]).toBe(VISUALS.folder) }) @@ -67,18 +64,17 @@ describe('a Claude chat launch with inline visuals', () => { { name: 'prepared visuals', visuals: VISUALS, - directories: ['/shared/notes', '/state/agent-session-attachments', VISUALS.folder] + directories: ['/state/agent-session-attachments', VISUALS.folder] }, { name: 'unavailable visuals', visuals: null, - directories: ['/shared/notes', '/state/agent-session-attachments'] + directories: ['/state/agent-session-attachments'] } - ])('keeps user folders and attachment access with $name', async ({ visuals, directories }) => { + ])('keeps attachment access with $name', async ({ visuals, directories }) => { const resolved = await launch({ prepareVisuals: async () => visuals, supports: async (flag) => flag === CLAUDE_PLUGIN_DIR_FLAG, - launchArgs: ['--add-dir', '/shared/notes'], attachmentDirectory: '/state/agent-session-attachments' }) expect(resolved.options.additionalDirectories).toEqual(directories) diff --git a/src/main/claude/claude-structured-real-cli-visuals.test.ts b/src/main/claude/claude-structured-real-cli-visuals.test.ts index a74677ddacf6..8e0c767a7038 100644 --- a/src/main/claude/claude-structured-real-cli-visuals.test.ts +++ b/src/main/claude/claude-structured-real-cli-visuals.test.ts @@ -49,7 +49,6 @@ describe.skipIf(!realClaudeAvailable)(suiteTitle, () => { store: { getRecord: () => record, pinLaunchDirectory: vi.fn() }, resolveWorkspacePath: async () => cwd, resolveCommand: () => realClaudeCommand, - resolveLaunchArgs: () => [], resolveEnv: () => realClaudeLaunchHome().env, resolveAuthPolicy: () => ({ stripAuthEnv: false }), hasTranscript: async () => false, diff --git a/src/main/claude/claude-structured-session-acquisition.ts b/src/main/claude/claude-structured-session-acquisition.ts index b10af618808b..4ab5050571ae 100644 --- a/src/main/claude/claude-structured-session-acquisition.ts +++ b/src/main/claude/claude-structured-session-acquisition.ts @@ -271,7 +271,6 @@ export async function acquireClaudeSession({ sessionId, providerSessionId: launch.providerSessionId, startup: session.startup, - resumesTranscript: launch.resumesTranscript, requestTimeoutMs: deps.requestTimeoutMs, emit }), diff --git a/src/main/claude/claude-structured-session-startup.ts b/src/main/claude/claude-structured-session-startup.ts index e7e0c12f338e..cb1b72e6bab9 100644 --- a/src/main/claude/claude-structured-session-startup.ts +++ b/src/main/claude/claude-structured-session-startup.ts @@ -108,7 +108,6 @@ export type ClaudeStartupFacts = { initialization: unknown settings: unknown prepared: ReturnType - resumesTranscript: boolean requestTimeoutMs: number | undefined } @@ -119,7 +118,6 @@ export async function readClaudeStartupFacts(input: { sessionId: string providerSessionId: string startup: Pick - resumesTranscript: boolean requestTimeoutMs: number | undefined emit: (event: ClaudeStructuredSessionEvent) => void }): Promise { @@ -161,7 +159,6 @@ export async function readClaudeStartupFacts(input: { initialization, settings, prepared: prepareClaudeStructuredSessionAcquisitionOptions({ settings, initialization }), - resumesTranscript: input.resumesTranscript, requestTimeoutMs: input.requestTimeoutMs } } @@ -270,8 +267,8 @@ export async function settleClaudeSessionStartup(input: { if (session.startup.state === 'pending') { session.startup.state = 'proven' } - if (startFastMode !== null) { - void applyClaudeStartFastMode(session, facts, startFastMode, (event) => { + if (startFastMode) { + void applyClaudeStartFastMode(session, facts, (event) => { if (input.isCurrent()) { input.report(event) } diff --git a/src/main/claude/claude-structured-spawn-options.test.ts b/src/main/claude/claude-structured-spawn-options.test.ts index 42780ae49986..e1f7f9dbcb34 100644 --- a/src/main/claude/claude-structured-spawn-options.test.ts +++ b/src/main/claude/claude-structured-spawn-options.test.ts @@ -91,43 +91,6 @@ describe('a Claude chat launched with its saved options', () => { expect(spawn.fastModeAtStart).toBe(false) }) - // The SDK writes the agent Arguments after its own options, so left in, the Arguments' flag would - // reach the CLI as a second `--model` after the chat's pick. - it("replaces the agent Arguments' model and effort with the chat's saved ones", () => { - const base = { - ...CLAUDE_STRUCTURED_BASE_OPTIONS, - extraArgs: { ...CLAUDE_STRUCTURED_BASE_OPTIONS.extraArgs, model: 'opus', effort: 'max' } - } - - expect(launched({ model: 'sonnet', effort: 'low' }, { base }).sdkOptions).toMatchObject({ - model: 'sonnet', - effort: 'low', - extraArgs: { 'replay-user-messages': null } - }) - expect(launched({ model: 'sonnet', effort: 'low' }, { base }).sdkOptions.extraArgs).toEqual({ - 'replay-user-messages': null - }) - // With nothing saved, the Arguments decide. - expect(launched({}, { base }).sdkOptions.extraArgs).toEqual(base.extraArgs) - }) - - // One `--settings` reaches the CLI: the Arguments' file is kept, and the start applies Fast. - it.each(['true', 'false'])( - "keeps the agent Arguments' settings and leaves a saved Fast %s to the start", - (fastMode) => { - const base = { - ...CLAUDE_STRUCTURED_BASE_OPTIONS, - extraArgs: { ...CLAUDE_STRUCTURED_BASE_OPTIONS.extraArgs, settings: '/repo/claude.json' } - } - const spawn = launched({ fastMode }, { base }) - - expect(spawn.sdkOptions).not.toHaveProperty('settings') - expect(spawn.sdkOptions.extraArgs).toEqual(base.extraArgs) - expect(spawn.options.get('fastMode')).toBe(fastMode) - expect(spawn.fastModeAtStart).toBe(true) - } - ) - it('launches a saved bypass under an Agent Permissions bypass with the owned bypass flag', () => { const spawn = launched({ permissionMode: 'bypassPermissions' }, { base: BYPASS_LAUNCH }) diff --git a/src/main/claude/claude-structured-spawn-options.ts b/src/main/claude/claude-structured-spawn-options.ts index be3414358646..aeb78550b578 100644 --- a/src/main/claude/claude-structured-spawn-options.ts +++ b/src/main/claude/claude-structured-spawn-options.ts @@ -45,19 +45,6 @@ function claudeStructuredOptionsWithPermissionMode( return { ...options, permissionMode: mode, extraArgs } } -/** The saved value stands in for the agent Arguments' own flag for it: the chat's pick wins, and - * the CLI is never handed the flag twice. */ -function withoutConfiguredFlag( - options: ClaudeStructuredSdkOptions, - flag: string -): ClaudeStructuredSdkOptions { - if (!options.extraArgs || !Object.hasOwn(options.extraArgs, flag)) { - return options - } - const { [flag]: _configured, ...extraArgs } = options.extraArgs - return { ...options, extraArgs } -} - function isEffortLevel(value: string): value is EffortLevel { return EFFORT_LEVELS.has(value) } @@ -93,13 +80,13 @@ export function claudeStructuredSpawnOptions(input: { const model = saved.get('model') if (model !== undefined) { options.set('model', model) - sdkOptions = { ...withoutConfiguredFlag(sdkOptions, 'model'), model } + sdkOptions = { ...sdkOptions, model } } const effort = saved.get('effort') if (effort !== undefined) { if (isEffortLevel(effort)) { options.set('effort', effort) - sdkOptions = { ...withoutConfiguredFlag(sdkOptions, 'effort'), effort } + sdkOptions = { ...sdkOptions, effort } } else { skipped.push('effort') } @@ -111,13 +98,9 @@ export function claudeStructuredSpawnOptions(input: { skipped.push('fastMode') } else { options.set('fastMode', fastMode) - // A new CLI session may opt in to Fast per session, which only its settings say, and the - // agent Arguments' own `--settings` would be replaced by one carrying Fast: either way the - // start applies the saved Fast once it has read them (`applyClaudeStartFastMode`). - if ( - (!decoded || input.launch.resumesTranscript) && - !Object.hasOwn(sdkOptions.extraArgs ?? {}, 'settings') - ) { + // A new CLI session may opt in to Fast per session, which only its settings say: the start + // applies the saved Fast once it has read them (`applyClaudeStartFastMode`). + if (!decoded || input.launch.resumesTranscript) { sdkOptions.settings = { fastMode: decoded } } else { fastModeAtStart = true diff --git a/src/main/claude/claude-structured-start-fast-mode.test.ts b/src/main/claude/claude-structured-start-fast-mode.test.ts deleted file mode 100644 index 24057e865fca..000000000000 --- a/src/main/claude/claude-structured-start-fast-mode.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -// A saved Fast the launch leaves out beside the agent Arguments' own `--settings`, applied by the -// start once the child answers. - -import { expect, it, vi } from 'vitest' -import { adapterFor, fakeClaude, identityFor } from './claude-structured-session-test-support' - -it("applies a resumed conversation's saved Fast off after start beside the Arguments' settings", async () => { - const claude = fakeClaude({ - settings: { effective: { fastMode: true, fastModePerSessionOptIn: false } } - }) - const adapter = adapterFor(claude, { - resumesTranscript: true, - continuesChain: true, - options: { extraArgs: { settings: '/repo/claude.json' } } - }) - - await adapter.acquire({ - identity: identityFor(), - fence: 7, - spawnToken: 'spawn-9', - options: { fastMode: 'false' } - }) - - expect(claude.connections[0].launch.options).not.toHaveProperty('settings') - expect(claude.connections[0].launch.options.extraArgs).toEqual({ - settings: '/repo/claude.json' - }) - await vi.waitFor(() => - expect(claude.connections[0].calls.at(-1)).toEqual({ - subtype: 'apply_flag_settings', - params: { settings: { fastMode: false } } - }) - ) -}) diff --git a/src/main/claude/claude-structured-start-fast-mode.ts b/src/main/claude/claude-structured-start-fast-mode.ts index df997380f1a7..83cd4ae98b12 100644 --- a/src/main/claude/claude-structured-start-fast-mode.ts +++ b/src/main/claude/claude-structured-start-fast-mode.ts @@ -8,24 +8,19 @@ import { claudeModelFastModeSupport } from './claude-structured-session-options' import type { ClaudeStartupFacts, ClaudeStartupReport } from './claude-structured-session-startup' import type { ClaudeSession } from './claude-structured-session-state' -/** A saved Fast the launch left out (`fastModeAtStart`): on for a new conversation, whose settings - * may opt in to it per session, or either value beside the agent Arguments' own `--settings`. - * Those settings now read: a new conversation's opt-in drops it, as before, and so do the guards a - * live Fast write takes. The value still to be applied, or null. */ +/** A saved Fast on that a new conversation's launch left out (`fastModeAtStart`), since its settings + * may opt in to it per session. Those settings now read: that opt-in drops it, as before, and so + * do the guards a live Fast write takes. Whether Fast on is still to be applied. */ export function admitClaudeStartFastMode( session: ClaudeSession, facts: ClaudeStartupFacts -): string | null { - const fastMode = session.options.get('fastMode') - if (!session.fastModeAtStart || fastMode === undefined) { - return null +): boolean { + if (!session.fastModeAtStart || session.options.get('fastMode') === undefined) { + return false } - if (fastMode !== 'true') { - return fastMode - } - if (!facts.resumesTranscript && facts.prepared.fastModePerSessionOptIn === true) { + if (facts.prepared.fastModePerSessionOptIn === true) { session.options.delete('fastMode') - return null + return false } // Over the listing this start already holds. const listed = listedModels({ models: readClaudeModels(facts.initialization) }) @@ -38,24 +33,23 @@ export function admitClaudeStartFastMode( ) { session.options.delete('fastMode') session.restoreSkippedOptions.add('fastMode') - return null + return false } - return fastMode + return true } -/** Applies a saved Fast the launch left out after `started`, so nothing waits on it. A refusal +/** Applies the saved Fast on the launch left out after `started`, so nothing waits on it. A refusal * drops it as main's refused restore did, from the record too; silence keeps it wanted and * unconfirmed. A write the user made meanwhile owns the option. */ export async function applyClaudeStartFastMode( session: ClaudeSession, facts: ClaudeStartupFacts, - fastMode: string, report: (event: ClaudeStartupReport) => void ): Promise { const sequence = session.optionMutationSequence try { await session.connection.applyFlagSettings( - { fastMode: fastMode === 'true' }, + { fastMode: true }, { timeoutMs: facts.requestTimeoutMs } ) } catch (error) { @@ -66,7 +60,7 @@ export async function applyClaudeStartFastMode( if (error instanceof ClaudeControlRequestError) { session.options.delete('fastMode') session.restoreSkippedOptions.add('fastMode') - report({ type: 'options-skipped', options: { fastMode } }) + report({ type: 'options-skipped', options: { fastMode: 'true' } }) } } } diff --git a/src/main/claude/claude-structured-turn-resume-point.test.ts b/src/main/claude/claude-structured-turn-resume-point.test.ts index f4538da4b07b..3ffd74aff14f 100644 --- a/src/main/claude/claude-structured-turn-resume-point.test.ts +++ b/src/main/claude/claude-structured-turn-resume-point.test.ts @@ -138,7 +138,6 @@ describe('Claude durable resume point at turn end', () => { expect(head).toMatchObject({ resumeCursor: 'a2' }) const resolve = createClaudeStructuredLaunchResolver({ - resolveLaunchArgs: () => [], // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the resolver reads only getRecord from its store. store: { getRecord: () => store.record } as unknown as AgentSessionRecordStore, resolveWorkspacePath: async (id) => `/repos/${id}`, diff --git a/src/main/codex/codex-model-catalog-probe.test.ts b/src/main/codex/codex-model-catalog-probe.test.ts index a3aeeebc4af0..e871709ab89b 100644 --- a/src/main/codex/codex-model-catalog-probe.test.ts +++ b/src/main/codex/codex-model-catalog-probe.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it, vi } from 'vitest' import { createCodexModelCatalogProbe } from './codex-model-catalog-probe' +import { CODEX_SHORT_LIVED_PROBE_APP_SERVER_ARGS } from '../codex-cli/codex-read-only-app-server-args' import { resolveCodexStructuredInvocation } from './codex-structured-launch-resolution' import { runCodexAppServerSession, type CodexAppServerInvocation } from './codex-app-server-session' import { resolveStructuredAgentCommand } from '../native-chat/structured-agent-command-resolution' @@ -64,6 +65,8 @@ describe('codex model catalog probe', () => { }) // A short-lived probe must not start plugin marketplace clones that outlive its teardown. expect(invocations[0]!.args.join(' ')).toContain('features.plugins=false') + // Exactly Orca's own probe arguments: saved Arguments are for terminal launches. + expect(invocations[0]!.args).toEqual([...CODEX_SHORT_LIVED_PROBE_APP_SERVER_ARGS]) } ) diff --git a/src/main/codex/codex-structured-launch-args.test.ts b/src/main/codex/codex-structured-launch-args.test.ts deleted file mode 100644 index 0b156acdd703..000000000000 --- a/src/main/codex/codex-structured-launch-args.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { codexStructuredLaunchArgs } from './codex-structured-launch-args' -import { StructuredAgentArgumentsError } from '../native-chat/structured-agent-arguments-error' - -describe('codexStructuredLaunchArgs', () => { - it('preserves root config and feature option order', () => { - expect( - codexStructuredLaunchArgs([ - '-c', - 'model_reasoning_effort=high', - '--enable', - 'unified_exec', - '--config=web_search="live"', - '--disable=some_feature', - '-m', - 'gpt-5.6-sol' - ]) - ).toEqual([ - '-c', - 'model_reasoning_effort=high', - '--enable', - 'unified_exec', - '--config=web_search="live"', - '--disable=some_feature', - '-m', - 'gpt-5.6-sol' - ]) - }) - - it('drops permission options and config overrides owned by Orca', () => { - expect( - codexStructuredLaunchArgs([ - '--dangerously-bypass-approvals-and-sandbox', - '-a', - 'never', - '--sandbox=danger-full-access', - '--approve-for-me', - '-c', - 'approval_policy="never"', - '--config=sandbox_mode="danger-full-access"', - '-c', - 'sandbox_workspace_write.writable_roots=["/tmp"]', - '-c', - '"sandbox_workspace_write".writable_roots=["/tmp"]', - '-c', - '\'approval_policy\'="never"', - '-c', - 'model_reasoning_effort=high' - ]) - ).toEqual(['-c', 'model_reasoning_effort=high']) - }) - - it('drops a profile and TUI-only path options without leaving their values as prompts', () => { - expect( - codexStructuredLaunchArgs([ - '--profile', - 'review', - '-pother', - '--cd', - '/another/repo', - '--worktree', - '-c', - 'model_reasoning_effort=high' - ]) - ).toEqual(['-c', 'model_reasoning_effort=high']) - }) - - it('discards operands after the option terminator', () => { - expect( - codexStructuredLaunchArgs(['-c', 'model_reasoning_effort=high', '--', 'a prompt']) - ).toEqual(['-c', 'model_reasoning_effort=high']) - }) - - it.each([ - { tokens: ['a private prompt'], option: 'prompt', problem: 'positionalPrompt' }, - { tokens: ['app-server'], option: 'prompt', problem: 'positionalPrompt' }, - { - tokens: ['--remote', 'wss://private-host'], - option: '--remote', - problem: 'unsupportedOption' - }, - { tokens: ['--remote=wss://private-host'], option: '--remote', problem: 'unsupportedOption' }, - { - tokens: ['--remote-auth-token-env', 'PRIVATE_TOKEN'], - option: '--remote-auth-token-env', - problem: 'unsupportedOption' - }, - { tokens: ['--unknown-flag=secret'], option: '--unknown-flag', problem: 'unsupportedOption' }, - { tokens: ['--enable'], option: '--enable', problem: 'missingValue' } - ] as const)('rejects unsafe or incomplete arguments: %j', ({ tokens, option, problem }) => { - const thrown = () => codexStructuredLaunchArgs(tokens) - expect(thrown).toThrow(StructuredAgentArgumentsError) - try { - thrown() - } catch (error) { - expect(error).toMatchObject({ - argumentProblem: { agent: 'Codex', option, problem } - }) - expect(JSON.stringify(error)).not.toContain('secret') - expect(JSON.stringify(error)).not.toContain('private') - } - }) -}) diff --git a/src/main/codex/codex-structured-launch-args.ts b/src/main/codex/codex-structured-launch-args.ts deleted file mode 100644 index 90b39fffec81..000000000000 --- a/src/main/codex/codex-structured-launch-args.ts +++ /dev/null @@ -1,115 +0,0 @@ -// Only root CLI options can precede `app-server`. A prompt or another subcommand -// would change the process Orca starts, and permission options belong to Orca. -import { parseTomlKeyPath } from './config-toml-key-path' -import { StructuredAgentArgumentsError } from '../native-chat/structured-agent-arguments-error' - -const VALUE_OPTIONS = new Set([ - '-c', - '--config', - '--enable', - '--disable', - '-m', - '--model', - '--local-provider' -]) - -const DROPPED_VALUE_OPTIONS = new Set([ - '-a', - '--ask-for-approval', - '-s', - '--sandbox', - '-p', - '--profile', - '-C', - '--cd', - '--add-dir', - '-i', - '--image' -]) - -const DROPPED_SWITCHES = new Set([ - '--dangerously-bypass-approvals-and-sandbox', - '--yolo', - '--approve-for-me', - '--not-so-yolo', - '--dangerously-bypass-hook-trust', - '--worktree', - '--strict-config', - '--help', - '-h', - '--version', - '-V' -]) - -const PASSTHROUGH_SWITCHES = new Set(['--search', '--oss', '--no-alt-screen', '--no-daemon']) - -function optionName(token: string): string { - if (token.startsWith('--')) { - return token.split('=', 1)[0] - } - if (/^-[acmspiC].+/.test(token)) { - return token.slice(0, 2) - } - return token -} - -function inlineValue(token: string, option: string): string | undefined { - if (token === option) { - return undefined - } - return token.startsWith('--') ? token.slice(option.length + 1) : token.slice(2) -} - -function isPermissionConfig(value: string): boolean { - const key = parseTomlKeyPath(value)?.segments[0] - return ( - key === 'approval_policy' || - key === 'sandbox_mode' || - key === 'approvals_reviewer' || - key === 'sandbox_workspace_write' - ) -} - -/** Turns saved terminal Arguments into root options for a structured app-server launch. */ -export function codexStructuredLaunchArgs(tokens: readonly string[]): string[] { - const args: string[] = [] - for (let index = 0; index < tokens.length; index += 1) { - const token = tokens[index] - if (token === '--') { - break - } - const option = optionName(token) - if (option === '--remote' || option === '--remote-auth-token-env') { - throw new StructuredAgentArgumentsError('Codex', option, 'unsupportedOption') - } - if (DROPPED_SWITCHES.has(option)) { - continue - } - if (PASSTHROUGH_SWITCHES.has(option) && token === option) { - args.push(token) - continue - } - if (VALUE_OPTIONS.has(option) || DROPPED_VALUE_OPTIONS.has(option)) { - const value = inlineValue(token, option) ?? tokens[++index] - if (!value || value === '--' || (value.startsWith('-') && !token.includes('='))) { - throw new StructuredAgentArgumentsError('Codex', option, 'missingValue') - } - if ( - DROPPED_VALUE_OPTIONS.has(option) || - (['-c', '--config'].includes(option) && isPermissionConfig(value)) - ) { - continue - } - args.push(token) - if (token === option) { - args.push(value) - } - continue - } - if (!token.startsWith('-')) { - throw new StructuredAgentArgumentsError('Codex', token, 'positionalPrompt') - } - throw new StructuredAgentArgumentsError('Codex', token, 'unsupportedOption') - } - return args -} diff --git a/src/main/codex/codex-structured-launch-resolution.test.ts b/src/main/codex/codex-structured-launch-resolution.test.ts index b47599113281..9e36cd4a1d08 100644 --- a/src/main/codex/codex-structured-launch-resolution.test.ts +++ b/src/main/codex/codex-structured-launch-resolution.test.ts @@ -54,27 +54,24 @@ function resolverFor( value: AgentSessionRecord | null, resolveWorkspacePath: (workspaceId: string) => Promise = async (id) => `/repos/${id}`, resolveRollout: () => Promise = async () => null, - agentDefaultArgs: Record = { codex: '' }, - resolveLaunchArgs?: () => string[] + agentDefaultArgs: Record = { codex: '' } ) { return createCodexStructuredLaunchResolver({ store: { getRecord: () => value, pinLaunchDirectory: vi.fn() }, resolveWorkspacePath, resolveCommand: () => '/usr/local/bin/codex', resolveRollout, - resolveLaunchArgs: resolveLaunchArgs ?? (() => value?.launchArgs ?? []), resolvePermissionPolicy: () => codexStructuredPermissionPolicyForSettings({ agentDefaultArgs }) }) } describe('codex structured launch resolution', () => { + // Saved Arguments are for terminal launches: only the Agent Permissions posture is read from them. it.each(['start', 'resume'] as const)( - 're-reads saved Arguments after a refusal on %s', + 'runs plain app-server on %s whatever Arguments are saved', async (mode) => { - let args = ['--remote', 'wss://host'] - const resolve = resolverFor( + const launch = await resolverFor( record({ - launchArgs: ['--enable', 'stale'], providerHandleChain: mode === 'resume' ? [ @@ -90,18 +87,16 @@ describe('codex structured launch resolution', () => { }), undefined, undefined, - { codex: '' }, - () => args - ) - await expect(resolve({ identity: IDENTITY })).rejects.toThrow(/Arguments/) - args = ['--enable', 'unified_exec'] - expect((await resolve({ identity: IDENTITY })).args).toEqual([ - '--enable', - 'unified_exec', - 'app-server' - ]) - args = [] - expect((await resolve({ identity: IDENTITY })).args).toEqual(['app-server']) + { + codex: + '--dangerously-bypass-approvals-and-sandbox -c model_reasoning_effort=high --profile review' + } + )({ identity: IDENTITY }) + expect(launch.args).toEqual(['app-server']) + expect(launch.permissionPolicy).toEqual({ + approvalPolicy: 'never', + sandbox: 'danger-full-access' + }) } ) @@ -129,7 +124,6 @@ describe('codex structured launch resolution', () => { }), pinLaunchDirectory }, - resolveLaunchArgs: () => [], resolveWorkspacePath: async () => '/floating/start-folder', resolveCommand: () => '/usr/local/bin/codex' }) @@ -159,7 +153,6 @@ describe('codex structured launch resolution', () => { await withPlatform('win32', async () => { const resolveLaunch = createCodexStructuredLaunchResolver({ - resolveLaunchArgs: () => [], store: { getRecord: () => record(), pinLaunchDirectory: vi.fn() }, resolveWorkspacePath: async () => String.raw`C:\workspaces\orca`, resolveCommand: () => command @@ -176,7 +169,6 @@ describe('codex structured launch resolution', () => { isWindowsProcessStartTimeAvailable.mockReturnValue(false) await withPlatform('win32', async () => { const resolveLaunch = createCodexStructuredLaunchResolver({ - resolveLaunchArgs: () => [], store: { getRecord: () => record(), pinLaunchDirectory: vi.fn() }, resolveWorkspacePath: async () => String.raw`C:\workspaces\orca`, resolveCommand: () => 'codex.exe' @@ -277,35 +269,11 @@ describe('codex structured launch resolution', () => { expect(launch.model).toBe('gpt-chosen') }) - it('uses saved arguments before app-server on a fresh launch', async () => { - const launch = await resolverFor( - record({ - launchArgs: [ - '--profile', - 'review', - '-c', - 'model_reasoning_effort=high', - '--model', - 'gpt-5.6-sol' - ] - }) - )({ identity: IDENTITY }) - - expect(launch.args).toEqual([ - '-c', - 'model_reasoning_effort=high', - '--model', - 'gpt-5.6-sol', - 'app-server' - ]) - }) - it('pins resume to the rollout file that proved the durable thread', async () => { const resolveRollout = vi.fn(async () => '/home/work/.codex/sessions/rollout.jsonl') const launch = await resolverFor( record({ // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the resolver reads only each link's handle, so the link's other fields stay unset. - launchArgs: ['--enable', 'unified_exec', '-c', 'model_reasoning_effort=high'], providerHandleChain: [ { handle: codexProviderHandle('thread-current') } ] as AgentSessionRecord['providerHandleChain'] @@ -316,13 +284,7 @@ describe('codex structured launch resolution', () => { expect(resolveRollout).toHaveBeenCalledWith('/home/work/.codex', 'thread-current') expect(launch.resumePath).toBe('/home/work/.codex/sessions/rollout.jsonl') - expect(launch.args).toEqual([ - '--enable', - 'unified_exec', - '-c', - 'model_reasoning_effort=high', - 'app-server' - ]) + expect(launch.args).toEqual(['app-server']) }) it('refuses a session pinned to another host rather than starting a second writer here', async () => { diff --git a/src/main/codex/codex-structured-launch-resolution.ts b/src/main/codex/codex-structured-launch-resolution.ts index 8016c938ae83..af6247f4a189 100644 --- a/src/main/codex/codex-structured-launch-resolution.ts +++ b/src/main/codex/codex-structured-launch-resolution.ts @@ -16,13 +16,11 @@ import { resolveAgentSessionLaunchDirectory } from '../runtime/agent-session-lau import type { CodexStructuredLaunch } from './codex-structured-session-adapter' import type { CodexStructuredPermissionPolicy } from './codex-structured-permission-policy' import { resolvePinnedCodexRolloutProof } from './codex-pinned-rollout-proof' -import { codexStructuredLaunchArgs } from './codex-structured-launch-args' import { CODEX_STRUCTURED_AGENT } from './codex-structured-agent-definition' import type { PrepareNativeChatVisuals } from '../native-chat/native-chat-visuals-delivery' export type CodexStructuredLaunchResolverDeps = { store: Pick - resolveLaunchArgs: () => Promise | string[] /** Absolute path of a workspace on this host. Rejects when the workspace no * longer resolves, which is the case a stale mobile client hits. */ resolveWorkspacePath: (workspaceId: string) => Promise @@ -89,7 +87,6 @@ export function createCodexStructuredLaunchResolver( throw new Error(`codex sessions pin ${pinned}, not ${accountHome.variable}`) } const { command, environment } = await resolveCodexStructuredInvocation(deps) - const args = codexStructuredLaunchArgs(await deps.resolveLaunchArgs()) const permissionPolicy = deps.resolvePermissionPolicy?.() const head = agentSessionProviderHandleChainHead(record.providerHandleChain) // A Codex record's chain holds only Codex handles; the attach admission refuses anything else. @@ -99,7 +96,8 @@ export function createCodexStructuredLaunchResolver( const visuals = (await deps.prepareVisuals?.(record.sessionId)) ?? null return { command, - args: [...args, 'app-server'], + // Saved Arguments are for terminal launches; a chat runs app-server as Orca starts it. + args: ['app-server'], cwd: await resolveAgentSessionLaunchDirectory(deps, record), codexHome: accountHome.path, ...(environment ? { env: { ...environment } as Record } : {}), diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-accept-then-deliver.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-accept-then-deliver.test.ts index 263de102f532..8f639f4479b2 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-accept-then-deliver.test.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-accept-then-deliver.test.ts @@ -47,7 +47,6 @@ import { agentSessionFailureWords } from '../../../shared/agent-session-failure- import { openTestJournalHostDatabase } from '../agent-session-journal/journal-host-database-test-support' import { createStructuredAgentSessionLogger } from './structured-agent-session-logger' import { codexProviderHandle } from '../../../shared/agent-session-provider-handle-encoding' -import { StructuredAgentArgumentsError } from '../structured-agent-arguments-error' import { USER_MESSAGE_SOURCE } from '../../../shared/agent-session-message-source' import { NO_STRUCTURED_AGENTS } from './structured-agent-session-adapter-router-test-support' @@ -326,28 +325,6 @@ describe('a send is answered at acceptance', () => { }) describe('a start the chat needed and did not get', () => { - it('shows a saved Arguments refusal on the resumed chat row and rejected message', async () => { - await host.close(SESSION, 'evict') - acquire.mockRejectedValueOnce( - new AgentSessionPreSpawnError( - new StructuredAgentArgumentsError('Codex', '--unsafe=private', 'unsupportedOption') - ) - ) - const id = await accept('first') - await eventually(async () => expect((await submission(id))?.dispatchState).toBe('rejected')) - const sentence = - "Codex couldn't restart. Saved Arguments contain an unsupported option (--unsafe). Edit them in Settings > Agents > Arguments. Send your message to try again." - expect(await errorRows()).toEqual([sentence]) - expect(await submission(id)).toMatchObject({ - reason: sentence, - rejection: { - kind: 'restartFailed', - argumentProblem: { agent: 'Codex', option: '--unsafe', problem: 'unsupportedOption' } - } - }) - expect(JSON.stringify(await host.journalSnapshot(SESSION))).not.toContain('private') - }) - it('writes one error row and rejects every queued message with it; the next send starts (W3)', async () => { await host.close(SESSION, 'evict') acquire.mockRejectedValueOnce(new Error('spawn codex ENOENT')) diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-agent-start.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-agent-start.ts index 9046e40dbbe8..30a3a2bde7e2 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-agent-start.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-agent-start.ts @@ -7,10 +7,8 @@ import { providerDiagnosticOf, - type AgentSessionArgumentProblem, type ProviderDiagnostic } from '../../../shared/agent-session-failure' -import { argumentProblemOf } from '../structured-agent-arguments-error' import { agentSessionRefusalFromReference, readAgentSessionRefusalReference, @@ -52,7 +50,6 @@ export type StructuredAgentSessionResumeOutcome = diagnostic?: ProviderDiagnostic /** The host's own close, Stop or quit aborted the start, so it failed nothing it was for. */ aborted?: true - argumentProblem?: AgentSessionArgumentProblem } /** The attach's caller key: the ledger row a start settles is Orca's own. */ @@ -203,13 +200,11 @@ function withDiagnostic( aborted: boolean ): StructuredAgentSessionResumeOutcome { const diagnostic = providerDiagnosticOf(error) - const argumentProblem = argumentProblemOf(error) return { ok: false, refusal, ...(diagnostic ? { diagnostic } : {}), - ...(aborted ? { aborted: true as const } : {}), - ...(argumentProblem ? { argumentProblem } : {}) + ...(aborted ? { aborted: true as const } : {}) } } diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-attach-orchestration.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-attach-orchestration.ts index e801a99042da..36ec7410e932 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-attach-orchestration.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-attach-orchestration.ts @@ -17,10 +17,7 @@ import type { import type { AgentSessionAttachParams } from './structured-agent-session-attach' import { performAttach, type AttachFlowInput } from './structured-agent-session-attach-flow' import { stampFailedCreateOwnerVerdict } from './structured-agent-session-failed-create-refusal' -import { - pinnedAgentSessionLaunchArgs, - pinnedAgentSessionLaunchEnv -} from './structured-agent-session-launch-env' +import { pinnedAgentSessionLaunchEnv } from './structured-agent-session-launch-env' import { refuseAgentSessionMutation } from './structured-agent-session-mutation-admission' import { settleStaleStructuredAgentSessionState } from './structured-agent-session-dead-generation-settlement' import { structuredAgentSessionFailureWordsContext } from './structured-agent-session-send-preparation' @@ -185,7 +182,6 @@ async function runAttachUnderAbort( claimKeyId: context.deps.claimKeyId, handoffOperationId: params.envelope.clientOperationId, probe, - ...(await pinnedAgentSessionLaunchArgs(context.deps.resolveLaunchArgs, params)), ...(await pinnedAgentSessionLaunchEnv(context.deps.resolveLaunchEnv, params)) }, callerKey, diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-attach.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-attach.ts index a4e5c0cfdd4b..80067ef58bc0 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-attach.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-attach.ts @@ -21,7 +21,6 @@ import { codexProviderHandleLink } from '../../codex/codex-structured-owner-iden import type { AgentSessionAccountHome, AgentSessionExecutionLocation, - AgentSessionLaunchArgs, AgentSessionLaunchEnv, AgentSessionRecord } from '../../../shared/agent-session-record' @@ -66,7 +65,6 @@ export type AgentSessionAttachParams = { /** The tab id a create reserves for this chat, taken when its tab is published. Never on the * attach fingerprint: which tab shows the chat is not which conversation it attaches to. */ surfaceTabId?: string - launchArgs?: string[] /** Omitted only for create-by-intent; the adapter proves the durable handle. In the wire's * form, because the attach fingerprint covers it as the client sent it. */ providerHandle?: AgentSessionWireProviderHandle @@ -92,7 +90,6 @@ export type AgentSessionAttachAuthority = { claimKeyId: string handoffOperationId: string | null probe: AgentSessionOwnerProbe - launchArgs?: AgentSessionLaunchArgs launchEnv?: AgentSessionLaunchEnv } @@ -318,7 +315,6 @@ export function reserveRequestFor(input: { ...(params.envelope.expectedRuntimeFence === null && params.surfaceTabId ? { surfaceTabId: params.surfaceTabId } : {}), - ...(authority.launchArgs ? { launchArgs: authority.launchArgs } : {}), ...(authority.launchEnv ? { launchEnv: authority.launchEnv } : {}), ...(authority.launchDirectory ? { launchDirectory: authority.launchDirectory } : {}), ...(params.adopt diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-delivery-loop.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-delivery-loop.ts index 380742f0e235..c3a9e21ee6a6 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-delivery-loop.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-delivery-loop.ts @@ -265,10 +265,7 @@ export class StructuredAgentSessionDeliveryLoop { } /** A start the session refused, as the failure every queued message it was for is rejected with. */ - private refusedStart( - sessionId: string, - { refusal, diagnostic, argumentProblem }: RefusedStart - ): StartFailure { + private refusedStart(sessionId: string, { refusal, diagnostic }: RefusedStart): StartFailure { // A conversation no agent ever ran, such as a cleared chat's, failed to start, not restart. const newSession = this.deps.record(sessionId)?.providerHandleChain.length === 0 return { @@ -276,7 +273,6 @@ export class StructuredAgentSessionDeliveryLoop { cause: { refusal, ...(diagnostic ? { diagnostic } : {}), - ...(argumentProblem ? { argumentProblem } : {}), ...(newSession ? { newSession: true as const } : {}) } } diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-failed-create-refusal.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-failed-create-refusal.ts index 3244ef1e07fb..66272025282a 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-failed-create-refusal.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-failed-create-refusal.ts @@ -28,7 +28,6 @@ import { structuredAgentSessionStartFailure } from './structured-agent-session-failure-text' import { structuredAgentSessionFailureWordsContext } from './structured-agent-session-send-preparation' -import { argumentProblemOf } from '../structured-agent-arguments-error' /** Who a failed acquisition's sentence names, and whether it was the session's first start. */ export type FailedAcquisitionWording = { @@ -40,15 +39,13 @@ export type FailedAcquisitionWording = { * error text is Orca's or the provider's log wording, so it goes to the log, never the wire. */ function failedAcquisitionWireRefusal( details: AgentSessionRefusalDetailsByCode['agent_session_operation_invalid'] | undefined, - wording: FailedAcquisitionWording, - error: unknown + wording: FailedAcquisitionWording ): AgentSessionWireRefusal { const code = 'agent_session_operation_invalid' const refusal = details ? refuse(code, details, code) : refuseUnclassified(code, code) const { reason } = structuredAgentSessionStartFailure( { refusal, - argumentProblem: argumentProblemOf(error), ...(wording.newSession ? { newSession: true as const } : {}) }, structuredAgentSessionFailureWordsContext(wording.record) @@ -84,7 +81,7 @@ export function failedAcquisitionSettlement( // A store refusal's message is its code, which its replay has always carried. (isAgentSessionWireRefusalCode(raw) ? raw - : failedAcquisitionWireRefusal(details, wording, error).message) + : failedAcquisitionWireRefusal(details, wording).message) return { exitProof, outcome: { @@ -110,10 +107,6 @@ function isExitProvenAcquisitionFailure(error: unknown): error is Error { function failedAcquisitionDetails( error: unknown ): AgentSessionRefusalDetailsByCode['agent_session_operation_invalid'] | undefined { - const argumentProblem = argumentProblemOf(error) - if (argumentProblem) { - return { reason: 'attachFailed', argumentProblem } - } if (error instanceof AgentSessionAcquisitionRefusal) { return { reason: error.reason } } @@ -133,14 +126,10 @@ export function failedAcquisitionRefusal( wording: FailedAcquisitionWording ): { ok: false; refusal: AgentSessionWireRefusal } | null { // A proven exit is a settled failure, answered in the shape its ledger row replays. - if ( - error instanceof AgentSessionAcquisitionRefusal || - isExitProvenAcquisitionFailure(error) || - (isAgentSessionPreSpawnError(error) && argumentProblemOf(error)) - ) { + if (error instanceof AgentSessionAcquisitionRefusal || isExitProvenAcquisitionFailure(error)) { return { ok: false, - refusal: failedAcquisitionWireRefusal(failedAcquisitionDetails(error), wording, error) + refusal: failedAcquisitionWireRefusal(failedAcquisitionDetails(error), wording) } } return null diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-failure-text.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-failure-text.test.ts index c49b79b06e64..f88be4b6cb73 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-failure-text.test.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-failure-text.test.ts @@ -9,12 +9,8 @@ import { refuse, refuseUnclassified } from '../../../shared/agent-session-wire-refusals' -import { - AgentSessionAcquisitionRefusal, - AgentSessionPreSpawnError -} from './structured-agent-session-adapter' +import { AgentSessionAcquisitionRefusal } from './structured-agent-session-adapter' import { MAX_UNEXPECTED_EXIT_REASON_CHARS } from './structured-agent-session-dead-generation-settlement' -import { StructuredAgentArgumentsError } from '../structured-agent-arguments-error' import { structuredAgentSessionStartFailure, withObservedProviderExit @@ -25,36 +21,6 @@ const ORCA_INTERNAL = /agent_session_|execution_owner|provider_[a-z_]+|[0-9a-f]{8}-[0-9a-f]{4}-|[/\\][\w.-]+[/\\]|Error:|ENOENT/ describe('structuredAgentSessionStartFailure', () => { - it('names a saved Arguments option in start and restart failures without exposing its value', () => { - const error = new StructuredAgentArgumentsError( - 'Codex', - '--unknown=private', - 'unsupportedOption' - ) - const wrapped = new AgentSessionPreSpawnError(error) - const argumentProblem = { - agent: 'Codex', - option: '--unknown', - problem: 'unsupportedOption' - } as const - expect(structuredAgentSessionStartFailure({ error: wrapped }, { agentName: 'Codex' })).toEqual({ - reason: - "Codex couldn't start. Saved Arguments contain an unsupported option (--unknown). Edit them in Settings > Agents > Arguments. Send your message to try again.", - rejection: { kind: 'startFailed', argumentProblem } - }) - const refusal = refuseUnclassified('agent_session_operation_invalid', 'generic start failure') - expect( - structuredAgentSessionStartFailure({ refusal, argumentProblem }, { agentName: 'Codex' }) - ).toEqual({ - reason: - "Codex couldn't restart. Saved Arguments contain an unsupported option (--unknown). Edit them in Settings > Agents > Arguments. Send your message to try again.", - rejection: { - kind: 'restartFailed', - argumentProblem, - refusal: { code: 'agent_session_operation_invalid' } - } - }) - }) it('keeps a provider diagnostic only when the error carried one', () => { const carried = withProviderDiagnostic( new Error('claude stream-json exited (code 1): boom'), diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-failure-text.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-failure-text.ts index 79bf84fb6d43..2afa8a009987 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-failure-text.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-failure-text.ts @@ -4,7 +4,6 @@ import { agentSessionFailureFact, providerDiagnosticOf, - type AgentSessionArgumentProblem, type SubmissionRejectionFact, type AgentSessionFailureKind, type ProviderDiagnostic @@ -20,7 +19,6 @@ import { type AgentSessionWireRefusal } from '../../../shared/agent-session-wire-refusals' import { AgentSessionAcquisitionRefusal } from './structured-agent-session-adapter' -import { argumentProblemOf } from '../structured-agent-arguments-error' /** Start refusals whose situation is itself what the person reads, with its own next step. */ const TYPED_START_REFUSALS = [ @@ -78,7 +76,7 @@ export function providerStartupFailureFact(cause?: unknown): SubmissionRejection } return agentSessionFailureFact( providerExitObserved(cause) ? 'providerStartFailed' : 'startFailed', - { detail: providerDiagnosticOf(cause), argumentProblem: argumentProblemOf(cause) } + { detail: providerDiagnosticOf(cause) } ) } @@ -99,7 +97,7 @@ function startupFailureFromExit( function refusedStartFailureFact( cause: Extract ): SubmissionRejectionFact { - const { refusal, diagnostic, argumentProblem } = cause + const { refusal, diagnostic } = cause const reason = refusal.details?.reason const typed = typedStartRefusal(reason) if (typed) { @@ -110,7 +108,6 @@ function refusedStartFailureFact( } return agentSessionFailureFact(cause.newSession ? 'startFailed' : 'restartFailed', { detail: diagnostic, - argumentProblem, refusal: agentSessionRefusalReference(refusal) }) } @@ -122,7 +119,6 @@ export type StructuredAgentSessionStartFailureCause = | { refusal: AgentSessionWireRefusal diagnostic?: ProviderDiagnostic - argumentProblem?: AgentSessionArgumentProblem newSession?: true } /** A start that threw, or an adapter's own startup failure; any diagnostic it carries. */ diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-host-types.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-host-types.ts index 2230efa15c9b..e784f36147ad 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-host-types.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-host-types.ts @@ -130,9 +130,6 @@ export type StructuredAgentSessionHostDeps = { /** Host spawn-token process scan; null means the platform cannot enumerate, never "none". */ scanSpawnTokenProcesses?: () => Promise mintSpawnToken?: () => string - resolveLaunchArgs?: ( - provider: AgentSessionRecord['provider'] - ) => Promise | string[] | undefined resolveLaunchEnv?: ( provider: AgentSessionRecord['provider'] ) => Promise | undefined> | Record | undefined diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-launch-env.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-launch-env.test.ts index c3c89fbf09da..feab90d7b8f4 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-launch-env.test.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-launch-env.test.ts @@ -1,9 +1,6 @@ import { describe, expect, it } from 'vitest' import { hostTestAttachParams } from './structured-agent-session-host-test-data' -import { - pinnedAgentSessionLaunchArgs, - pinnedAgentSessionLaunchEnv -} from './structured-agent-session-launch-env' +import { pinnedAgentSessionLaunchEnv } from './structured-agent-session-launch-env' describe('pinnedAgentSessionLaunchEnv', () => { it('layers the pinned account home over the shell environment', async () => { @@ -19,10 +16,4 @@ describe('pinnedAgentSessionLaunchEnv', () => { } }) }) - - it('copies the host-resolved provider arguments into reservation authority', async () => { - await expect( - pinnedAgentSessionLaunchArgs(async () => ['--profile', 'review'], hostTestAttachParams(null)) - ).resolves.toEqual({ launchArgs: ['--profile', 'review'] }) - }) }) diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-launch-env.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-launch-env.ts index 58510a7231b0..9c1dbeeb7222 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-launch-env.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-launch-env.ts @@ -6,10 +6,6 @@ type LaunchEnvResolver = ( provider: AgentSessionRecord['provider'] ) => Promise | undefined> | Record | undefined -type LaunchArgsResolver = ( - provider: AgentSessionRecord['provider'] -) => Promise | string[] | undefined - export async function pinnedAgentSessionLaunchEnv( resolver: LaunchEnvResolver | undefined, params: AgentSessionAttachParams @@ -26,11 +22,3 @@ export async function pinnedAgentSessionLaunchEnv( } } } - -export async function pinnedAgentSessionLaunchArgs( - resolver: LaunchArgsResolver | undefined, - params: AgentSessionAttachParams -): Promise<{ launchArgs: string[] } | Record> { - const launchArgs = params.launchArgs ?? (await resolver?.(params.provider)) - return launchArgs ? { launchArgs: [...launchArgs] } : {} -} diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-pre-spawn-first-answer.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-pre-spawn-first-answer.test.ts index 0a04d6cddfa5..13ab450cf1b8 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-pre-spawn-first-answer.test.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-pre-spawn-first-answer.test.ts @@ -22,7 +22,6 @@ import { openTestAttachConversation } from './structured-agent-session-attach-te import { performAttach } from './structured-agent-session-attach-flow' import { openTestJournalHostDatabase } from '../agent-session-journal/journal-host-database-test-support' import { createStructuredAgentSessionLogger } from './structured-agent-session-logger' -import { StructuredAgentArgumentsError } from '../structured-agent-arguments-error' import { NO_STRUCTURED_AGENTS } from './structured-agent-session-adapter-router-test-support' const NOW = 1_800_000_000_000 @@ -119,29 +118,6 @@ async function firstAnswerAndReplay(thrown: AgentSessionPreSpawnError) { } describe('a create that fails before any process spawns', () => { - it('answers a safe saved Arguments problem on the first call and replay', async () => { - const { first, replay } = await firstAnswerAndReplay( - new AgentSessionPreSpawnError( - new StructuredAgentArgumentsError('Claude', '--model=private', 'multipleValues') - ) - ) - const sentence = - "Claude couldn't start. Saved Arguments give --model more than one value. Edit them in Settings > Agents > Arguments. Send your message to try again." - for (const result of [first, replay]) { - expect(result).toMatchObject({ - ok: false, - refusal: { - message: sentence, - details: { - reason: 'attachFailed', - argumentProblem: { agent: 'Claude', option: '--model', problem: 'multipleValues' } - } - } - }) - } - expect(JSON.stringify([first, replay])).not.toContain('private') - }) - it.each<[string, string, AgentSessionPreSpawnReason | undefined, string]>([ [ 'the managed account env override', diff --git a/src/main/native-chat/agent-session-wire/structured-conversation-command.test.ts b/src/main/native-chat/agent-session-wire/structured-conversation-command.test.ts index d2cb969bf1d3..336abf2dd582 100644 --- a/src/main/native-chat/agent-session-wire/structured-conversation-command.test.ts +++ b/src/main/native-chat/agent-session-wire/structured-conversation-command.test.ts @@ -295,38 +295,6 @@ describe('/clear starts nothing', () => { expect(adapter.dispatch).not.toHaveBeenCalled() }) - it('copies the launch arguments the source was pinned to', async () => { - const pinned = 'session-pinned' - expect( - await host.attach( - caller, - hostTestAttachParams(null, { - envelope: { - sessionId: pinned, - clientOperationId: hostTestOperationId(), - expectedRuntimeFence: null, - payloadFingerprint: '' - }, - launchArgs: ['--flag'] - }) - ) - ).toMatchObject({ ok: true }) - const clear = { ...commandParams('clear') } - clear.envelope = { - sessionId: pinned, - clientOperationId: hostTestOperationId(), - expectedRuntimeFence: store.getRecord(pinned)!.lease.runtimeFence, - payloadFingerprint: computeAgentSessionPayloadFingerprint({ - method: 'agentSession.conversationCommand', - sessionId: pinned, - fields: { command: 'clear' } - }) - } - notFromAClear.add(pinned) - const replacement = await clearCommits(clear) - expect(store.getRecord(replacement)?.launchArgs).toEqual(['--flag']) - }) - it("does not start an at-rest source's agent", async () => { await sourceAtRest() const starts = vi.mocked(adapter.acquire).mock.calls.length diff --git a/src/main/native-chat/structured-agent-arguments-error.ts b/src/main/native-chat/structured-agent-arguments-error.ts deleted file mode 100644 index f6fca945307d..000000000000 --- a/src/main/native-chat/structured-agent-arguments-error.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { AgentSessionArgumentProblem } from '../../shared/agent-session-failure' - -/** A saved Arguments refusal with only the option name, never its value. */ -export class StructuredAgentArgumentsError extends Error { - readonly argumentProblem: AgentSessionArgumentProblem - - constructor( - agent: AgentSessionArgumentProblem['agent'], - option: string, - problem: AgentSessionArgumentProblem['problem'] - ) { - const normalizedOption = - problem === 'positionalPrompt' - ? 'prompt' - : (option.match(/^--[a-zA-Z][a-zA-Z0-9-]{0,63}/)?.[0] ?? - option.match(/^-[a-zA-Z]/)?.[0] ?? - '--?') - super(`${agent} structured chat cannot use ${normalizedOption} in saved Arguments`) - this.name = 'StructuredAgentArgumentsError' - this.argumentProblem = { agent, option: normalizedOption, problem } - } -} - -/** Finds a typed Arguments refusal through acquisition wrappers. */ -export function argumentProblemOf( - error: unknown, - depth = 0 -): AgentSessionArgumentProblem | undefined { - if (depth >= 6 || !(error instanceof Error)) { - return undefined - } - if (error instanceof StructuredAgentArgumentsError) { - return error.argumentProblem - } - if (error instanceof AggregateError) { - for (const inner of error.errors) { - const problem = argumentProblemOf(inner, depth + 1) - if (problem) { - return problem - } - } - } - return argumentProblemOf(error.cause, depth + 1) -} diff --git a/src/main/native-chat/structured-agent-configured-args.test.ts b/src/main/native-chat/structured-agent-configured-args.test.ts deleted file mode 100644 index 58823a406088..000000000000 --- a/src/main/native-chat/structured-agent-configured-args.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { structuredAgentConfiguredArgs } from './structured-agent-configured-args' - -describe('structured chat configured Arguments', () => { - it.each(['claude', 'codex'] as const)( - 'reads the existing %s setting and preserves quoted values', - (agent) => { - expect( - structuredAgentConfiguredArgs( - agent, - { - agentDefaultArgs: { [agent]: '--model "model with spaces"' } - }, - 'darwin' - ) - ).toEqual(['--model', 'model with spaces']) - expect(structuredAgentConfiguredArgs(agent, { agentDefaultArgs: { [agent]: '' } })).toEqual( - [] - ) - } - ) - - it('uses the existing default when the Arguments key is absent', () => { - expect(structuredAgentConfiguredArgs('claude', {})).toEqual(['--dangerously-skip-permissions']) - }) - - it('preserves Windows paths under the configured shell', () => { - expect( - structuredAgentConfiguredArgs( - 'claude', - { - agentDefaultArgs: { claude: String.raw`--plugin-dir "C:\My Plugins"` }, - terminalWindowsShell: 'powershell' - }, - 'win32' - ) - ).toEqual(['--plugin-dir', String.raw`C:\My Plugins`]) - }) - - it('refuses unclosed quotes without spawning', () => { - expect(() => - structuredAgentConfiguredArgs('claude', { - agentDefaultArgs: { claude: '--model "unfinished' } - }) - ).toThrow('Arguments are invalid') - }) -}) diff --git a/src/main/native-chat/structured-agent-configured-args.ts b/src/main/native-chat/structured-agent-configured-args.ts deleted file mode 100644 index 3b60c7642680..000000000000 --- a/src/main/native-chat/structured-agent-configured-args.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { AgentSessionHandleProvider } from '../../shared/agent-session-provider-handle' -import type { GlobalSettings } from '../../shared/global-settings-types' -import { resolveTuiAgentLaunchArgs } from '../../shared/tui-agent-launch-defaults' -import { tokenizeStartupCommand } from '../../shared/tui-agent-startup-shell' -import { resolveLocalWindowsAgentStartupShell } from '../../shared/windows-terminal-shell' - -/** Use the same grouping rules as terminal launches before the provider filters its owned flags. */ -export function structuredAgentConfiguredArgs( - agent: AgentSessionHandleProvider, - settings: Partial>, - platform: NodeJS.Platform = process.platform -): string[] { - const shell = - resolveLocalWindowsAgentStartupShell({ - platform, - isRemote: false, - terminalWindowsShell: settings.terminalWindowsShell - }) ?? 'posix' - const parsed = tokenizeStartupCommand( - resolveTuiAgentLaunchArgs(agent, settings.agentDefaultArgs), - shell - ) - if (!parsed.ok) { - throw new Error(`${agent} Arguments are invalid: ${parsed.error}`) - } - return parsed.tokens -} diff --git a/src/main/runtime/agent-session-conversation-clear-commit.test.ts b/src/main/runtime/agent-session-conversation-clear-commit.test.ts index 3e7c63a41c34..a176d48604fd 100644 --- a/src/main/runtime/agent-session-conversation-clear-commit.test.ts +++ b/src/main/runtime/agent-session-conversation-clear-commit.test.ts @@ -39,7 +39,6 @@ async function storeWithSource(): Promise { }, provider: 'claude', accountHome: { variable: 'CLAUDE_CONFIG_DIR', path: '/home/dev/.claude-work' }, - launchArgs: ['--flag'], options: { model: 'sonnet' }, expectedFence: null, spawnToken: 'spawn-a', @@ -91,7 +90,6 @@ describe("a /clear's commit", () => { provider: 'claude', accountHome: source.accountHome, options: { model: 'sonnet' }, - launchArgs: ['--flag'], providerHandleChain: [], createdAt: NOW + 5, updatedAt: NOW + 5, diff --git a/src/main/runtime/agent-session-record-founding.ts b/src/main/runtime/agent-session-record-founding.ts index 41386401351e..3b71aa0784e0 100644 --- a/src/main/runtime/agent-session-record-founding.ts +++ b/src/main/runtime/agent-session-record-founding.ts @@ -6,13 +6,7 @@ import { /** Who a conversation's agent is and how it launches: the same whether it is created or founded. */ export type AgentSessionRecordIdentity = Pick< AgentSessionRecord, - | 'sessionId' - | 'location' - | 'provider' - | 'accountHome' - | 'options' - | 'launchArgs' - | 'launchDirectory' + 'sessionId' | 'location' | 'provider' | 'accountHome' | 'options' | 'launchDirectory' > export function agentSessionRecordIdentityFields( @@ -26,7 +20,6 @@ export function agentSessionRecordIdentityFields( provider: identity.provider, accountHome: identity.accountHome, ...(identity.options ? { options: { ...identity.options } } : {}), - ...(identity.launchArgs ? { launchArgs: [...identity.launchArgs] } : {}), // A /clear continues in the same tab, so it stays in the folder the chat ran in. ...(identity.launchDirectory ? { launchDirectory: identity.launchDirectory } : {}), createdAt: now, diff --git a/src/main/runtime/agent-session-record-store-slot.test.ts b/src/main/runtime/agent-session-record-store-slot.test.ts index 35005c3badfc..12be381a787f 100644 --- a/src/main/runtime/agent-session-record-store-slot.test.ts +++ b/src/main/runtime/agent-session-record-store-slot.test.ts @@ -55,8 +55,7 @@ describe('the record store slot', () => { claimKeyId: 'key-1', resolveWorkspacePath: async () => stateDirectory, resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), - resolveEnvironment: async () => ({}), - resolveLaunchArgs: () => [] + resolveEnvironment: async () => ({}) }) expect(host.deps.store).toBe(store) diff --git a/src/main/runtime/agent-session-record-store.test.ts b/src/main/runtime/agent-session-record-store.test.ts index cc0a83cc55f2..eecf4ea8af2b 100644 --- a/src/main/runtime/agent-session-record-store.test.ts +++ b/src/main/runtime/agent-session-record-store.test.ts @@ -743,6 +743,18 @@ describe('claim keys and unreadable rows', () => { expect((await open()).isSessionUnreadable('session-alpha')).toBe(true) }) + // Builds before #20944 stored the chat's launch arguments; no current build reads them. + it('loads a record an older build stored with launchArgs', async () => { + const first = await open() + await establishOwner(first) + await editPersistedTestAgentSessionStore(directory, (persisted) => { + Object.assign(persisted.records['session-alpha'], { launchArgs: ['--model', 'opus'] }) + }) + const reopened = await open() + expect(reopened.isSessionUnreadable('session-alpha')).toBe(false) + expect(reopened.getRecord('session-alpha')?.sessionId).toBe('session-alpha') + }) + it('sets aside a row whose handle is in both stored forms and never rewrites it', async () => { const first = await open() await establishOwner(first) diff --git a/src/main/runtime/agent-session-reservation-admission.ts b/src/main/runtime/agent-session-reservation-admission.ts index ff840c755b35..4cd26bfd9a5c 100644 --- a/src/main/runtime/agent-session-reservation-admission.ts +++ b/src/main/runtime/agent-session-reservation-admission.ts @@ -30,11 +30,9 @@ import { isAgentSessionOptions, type AgentSessionAccountHome, type AgentSessionExecutionLocation, - type AgentSessionLaunchArgs, type AgentSessionLaunchEnv, type AgentSessionRecord } from '../../shared/agent-session-record' -import { isAgentSessionLaunchArgs } from '../../shared/agent-session-launch-args' import { isAgentSessionSurfaceTabId } from '../../shared/agent-session-surface-tab-id' import { agentSessionProviderHandleRoot, @@ -56,8 +54,6 @@ export type AgentSessionReserveRequest = { location: AgentSessionExecutionLocation provider: StructuredAgentId accountHome: AgentSessionAccountHome - /** Arguments pinned on first reservation so owner replacement repeats the same launch. */ - launchArgs?: AgentSessionLaunchArgs /** Current launch input validated here but never written to the durable record. */ launchEnv?: AgentSessionLaunchEnv /** Initial provider options persisted before the first process is acquired. */ @@ -154,9 +150,6 @@ export function applyAgentSessionReservation( if (request.launchEnv && !isAgentSessionLaunchEnv(request.launchEnv)) { throw new Error('agent_session_launch_env_invalid') } - if (request.launchArgs && !isAgentSessionLaunchArgs(request.launchArgs)) { - throw new Error('agent_session_launch_args_invalid') - } if (request.options && !isAgentSessionOptions(request.options)) { throw new Error('agent_session_options_invalid') } @@ -202,13 +195,8 @@ export function applyAgentSessionReservation( throw agentSessionRefusalError('agent_session_conflict', { reason: 'sessionExists' }) } assertReservedTabUnheld(state, request) - const pinned = { - ...existing, - ...(!existing.launchArgs && request.launchArgs ? { launchArgs: [...request.launchArgs] } : {}), - ...(!existing.launchArgs && request.launchArgs ? { updatedAt: request.now } : {}) - } return reserveAgentSessionOwner({ - record: pinned, + record: existing, expectedFence: request.expectedFence ?? existing.lease.runtimeFence, probe: request.probe, reservation diff --git a/src/main/runtime/claude-structured-session-integration.test.ts b/src/main/runtime/claude-structured-session-integration.test.ts index 628f3f4f6917..ea54539d4f91 100644 --- a/src/main/runtime/claude-structured-session-integration.test.ts +++ b/src/main/runtime/claude-structured-session-integration.test.ts @@ -249,7 +249,6 @@ beforeEach(async () => { // Hermetic: never the developer's real login shell. resolveEnvironment: async () => shellEnv, resolveShellEnvironmentPolicy: () => shellEnvironmentPolicy, - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => claudeAuthPolicy, openClaudeConnection: claude.openConnection, claudeCliFlags: { diff --git a/src/main/runtime/orca-runtime-get-worktree-ps.ts b/src/main/runtime/orca-runtime-get-worktree-ps.ts index b73d018c211f..aa6327f62199 100644 --- a/src/main/runtime/orca-runtime-get-worktree-ps.ts +++ b/src/main/runtime/orca-runtime-get-worktree-ps.ts @@ -43,7 +43,6 @@ import { claudeStructuredPermissionModeForSettings } from '../claude/claude-stru import { codexStructuredPermissionPolicyForSettings } from '../codex/codex-structured-permission-policy' import { claudeStructuredAuthPolicyForSettings } from '../claude-accounts/claude-structured-auth-policy' import { resolveStructuredAgentCommand } from '../native-chat/structured-agent-command-resolution' -import { structuredAgentConfiguredArgs } from '../native-chat/structured-agent-configured-args' import { claudeCliFlagSupport } from '../claude/claude-cli-flag-support' import { createNativeChatVisualsWorkspaceVerdicts, @@ -220,8 +219,6 @@ export class OrcaRuntimeWithGetWorktreePs extends OrcaRuntimeWithStartTuiIdleVis resolveStructuredAgentCommand('claude', this.requireStore().getSettings()), resolveCodexCommand: (options) => resolveStructuredAgentCommand('codex', this.requireStore().getSettings(), options), - resolveLaunchArgs: (agent) => - structuredAgentConfiguredArgs(agent, this.requireStore().getSettings()), resolveLaunchEnvOverlay: () => resolveTuiAgentLaunchEnv('codex', this.requireStore().getSettings().agentDefaultEnv), resolveClaudeLaunchEnv: () => diff --git a/src/main/runtime/orca-runtime-structured-launch-settings-wiring.test.ts b/src/main/runtime/orca-runtime-structured-launch-settings-wiring.test.ts index dd81e303b41d..854055257f52 100644 --- a/src/main/runtime/orca-runtime-structured-launch-settings-wiring.test.ts +++ b/src/main/runtime/orca-runtime-structured-launch-settings-wiring.test.ts @@ -19,7 +19,7 @@ vi.mock('electron', () => ({ import { OrcaRuntimeService } from './orca-runtime' describe('execution host structured launch settings wiring', () => { - it('reads saved Command and Arguments from the host settings and rereads changes', async () => { + it('reads the saved Command from the host settings and rereads changes', async () => { installed.mockClear() const settings: Partial = { agentCmdOverrides: { claude: `"${process.execPath}"`, codex: `"${process.execPath}"` }, @@ -32,11 +32,8 @@ describe('execution host structured launch settings wiring', () => { expect(deps).toBeDefined() expect(deps?.resolveClaudeCommand?.()).toBe(process.execPath) expect(deps?.resolveCodexCommand?.()).toBe(process.execPath) - expect(await deps?.resolveLaunchArgs?.('claude')).toEqual(['--model', 'model one']) - expect(await deps?.resolveLaunchArgs?.('codex')).toEqual(['-c', 'model_reasoning_effort=high']) - settings.agentDefaultArgs = { claude: '--model second', codex: '' } - expect(await deps?.resolveLaunchArgs?.('claude')).toEqual(['--model', 'second']) - expect(await deps?.resolveLaunchArgs?.('codex')).toEqual([]) + // Saved Arguments are for terminal launches; the chat host is given no reader for them. + expect(deps).not.toHaveProperty('resolveLaunchArgs') const notRunnable = expect.objectContaining({ reason: 'agentCommandNotRunnable' }) settings.agentCmdOverrides = { claude: 'wrapper --arg' } expect(() => deps?.resolveClaudeCommand?.()).toThrow(notRunnable) diff --git a/src/main/runtime/rpc/methods/agent-launch-surfaces.ts b/src/main/runtime/rpc/methods/agent-launch-surfaces.ts index 0727f31bef0b..10f45b83a2f4 100644 --- a/src/main/runtime/rpc/methods/agent-launch-surfaces.ts +++ b/src/main/runtime/rpc/methods/agent-launch-surfaces.ts @@ -152,7 +152,7 @@ export function agentLaunchSurfaceFactory( : {}), ...(agentArgs !== undefined ? { agentArgs } : {}), ...(cwd ? { cwd } : {}), - // The model the user picked outranks configured args here too, as it does on a chat. + // The model the user picked outranks configured args. ...(launchPreferences ? { launchPreferences } : {}), // A live reserved pane would be attached, not launched into, so the runtime refuses it. ...(paneKey ? { ...paneIdentity(paneKey), requireFreshPane: true } : {}), diff --git a/src/main/runtime/structured-agent-launch-args-wiring.test.ts b/src/main/runtime/structured-agent-launch-args-wiring.test.ts deleted file mode 100644 index 6037c26e5732..000000000000 --- a/src/main/runtime/structured-agent-launch-args-wiring.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { join } from 'node:path' -import { mkdtemp, rm } from 'node:fs/promises' -import { tmpdir } from 'node:os' -import { afterEach, expect, it } from 'vitest' -import { createStructuredAgentSessionLogger } from '../native-chat/agent-session-wire/structured-agent-session-logger' -import { - ensureStructuredAgentSessionHost, - stopStructuredAgentSessionRuntime, - STRUCTURED_AGENT_LAUNCH_ARGS_REQUIRED -} from './structured-agent-session-runtime' - -let stateDirectory: string | undefined - -afterEach(async () => { - await stopStructuredAgentSessionRuntime() - if (stateDirectory) { - await rm(stateDirectory, { recursive: true, force: true }) - stateDirectory = undefined - } -}) - -it('refuses installation when the host omits the saved Arguments source', async () => { - const root = await mkdtemp(join(tmpdir(), 'orca-launch-args-wiring-')) - stateDirectory = root - - await expect( - // @ts-expect-error Exercise an unchecked caller that dropped the required resolver. - ensureStructuredAgentSessionHost({ - stateDirectory: root, - hostId: 'local', - claimKeyId: 'key-1', - resolveWorkspacePath: async () => root, - resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), - logger: createStructuredAgentSessionLogger() - }) - ).rejects.toThrow(STRUCTURED_AGENT_LAUNCH_ARGS_REQUIRED) -}) diff --git a/src/main/runtime/structured-agent-runtime-registrations.ts b/src/main/runtime/structured-agent-runtime-registrations.ts index 2787ee62245a..606396649d18 100644 --- a/src/main/runtime/structured-agent-runtime-registrations.ts +++ b/src/main/runtime/structured-agent-runtime-registrations.ts @@ -129,7 +129,6 @@ function createCodexAdapter(context: StructuredAgentAdapterContext): StructuredA store, resolveWorkspacePath: deps.resolveWorkspacePath, resolveEnvironment: context.environment.resolveCodexEnvironment, - resolveLaunchArgs: () => deps.resolveLaunchArgs('codex'), ...(deps.resolveCodexPermissionPolicy ? { resolvePermissionPolicy: deps.resolveCodexPermissionPolicy } : {}), @@ -165,7 +164,6 @@ function createClaudeAdapter( ...nativeChatVisualsFor(deps), ...(deps.resolveClaudeLaunchEnv ? { resolveClaudeLaunchEnv: deps.resolveClaudeLaunchEnv } : {}), resolveClaudeInheritedEnv: context.environment.resolveClaudeInheritedEnv, - resolveClaudeLaunchArgs: () => deps.resolveLaunchArgs('claude'), resolveClaudeAuthPolicy: deps.resolveClaudeAuthPolicy, ...(deps.resolveClaudePermissionMode ? { resolveClaudePermissionMode: deps.resolveClaudePermissionMode } diff --git a/src/main/runtime/structured-agent-session-codex-interrupt-order.test.ts b/src/main/runtime/structured-agent-session-codex-interrupt-order.test.ts index e28f7f689006..9c5927797570 100644 --- a/src/main/runtime/structured-agent-session-codex-interrupt-order.test.ts +++ b/src/main/runtime/structured-agent-session-codex-interrupt-order.test.ts @@ -216,7 +216,6 @@ beforeEach(async () => { hostId: 'local', claimKeyId: 'key-1', resolveWorkspacePath: async () => root, - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveCodexCommand: () => 'codex', resolveEnvironment: async () => ({ PATH: process.env.PATH }), diff --git a/src/main/runtime/structured-agent-session-codex-opening-send-hold.test.ts b/src/main/runtime/structured-agent-session-codex-opening-send-hold.test.ts index 31dff2777569..821dcafba6ba 100644 --- a/src/main/runtime/structured-agent-session-codex-opening-send-hold.test.ts +++ b/src/main/runtime/structured-agent-session-codex-opening-send-hold.test.ts @@ -235,7 +235,6 @@ beforeEach(async () => { resolveWorkspacePath: async () => root, resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveCodexCommand: () => 'codex', - resolveLaunchArgs: () => [], resolveEnvironment: async () => ({ PATH: process.env.PATH }), openCodexConnection: openConnection, readProcessStartTime: async () => 1_700_000_000_000 diff --git a/src/main/runtime/structured-agent-session-codex-settlement-order.test.ts b/src/main/runtime/structured-agent-session-codex-settlement-order.test.ts index 13b70638b95a..47a8c609db20 100644 --- a/src/main/runtime/structured-agent-session-codex-settlement-order.test.ts +++ b/src/main/runtime/structured-agent-session-codex-settlement-order.test.ts @@ -160,7 +160,6 @@ beforeEach(async () => { hostId: 'local', claimKeyId: 'key-1', resolveWorkspacePath: async () => root, - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveCodexCommand: () => 'codex', resolveEnvironment: async () => ({ PATH: process.env.PATH }), diff --git a/src/main/runtime/structured-agent-session-codex-stopped-send-order.test.ts b/src/main/runtime/structured-agent-session-codex-stopped-send-order.test.ts index 3824f6edb6de..80432e8399c8 100644 --- a/src/main/runtime/structured-agent-session-codex-stopped-send-order.test.ts +++ b/src/main/runtime/structured-agent-session-codex-stopped-send-order.test.ts @@ -173,7 +173,6 @@ async function startHost(): Promise { hostId: 'local', claimKeyId: 'key-1', resolveWorkspacePath: async () => root, - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveCodexCommand: () => 'codex', resolveEnvironment: async () => ({ PATH: process.env.PATH }), diff --git a/src/main/runtime/structured-agent-session-codex-turn-end-settlement.test.ts b/src/main/runtime/structured-agent-session-codex-turn-end-settlement.test.ts index 9db9b37287ca..4b65df2d25ce 100644 --- a/src/main/runtime/structured-agent-session-codex-turn-end-settlement.test.ts +++ b/src/main/runtime/structured-agent-session-codex-turn-end-settlement.test.ts @@ -276,7 +276,6 @@ beforeEach(async () => { hostId: 'local', claimKeyId: 'key-1', resolveWorkspacePath: async () => root, - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveCodexCommand: () => 'codex', resolveEnvironment: async () => ({ PATH: process.env.PATH }), diff --git a/src/main/runtime/structured-agent-session-failure-logging.test.ts b/src/main/runtime/structured-agent-session-failure-logging.test.ts index 9825a2b7aee6..239ff65f11c5 100644 --- a/src/main/runtime/structured-agent-session-failure-logging.test.ts +++ b/src/main/runtime/structured-agent-session-failure-logging.test.ts @@ -307,7 +307,6 @@ describe('installing the runtime', () => { hostId: 'local', claimKeyId: 'key-1', resolveWorkspacePath: async () => root, - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }) } as unknown as Parameters[0] ) @@ -324,7 +323,6 @@ describe('installing the runtime', () => { claimKeyId: 'key-1', resolveWorkspacePath: async () => root, resolveEnvironment: async () => ({}), - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), logger: { warn: () => { diff --git a/src/main/runtime/structured-agent-session-held-signal.test.ts b/src/main/runtime/structured-agent-session-held-signal.test.ts index 3187b44fa84b..c0c6a2a8db37 100644 --- a/src/main/runtime/structured-agent-session-held-signal.test.ts +++ b/src/main/runtime/structured-agent-session-held-signal.test.ts @@ -43,7 +43,6 @@ function installHost(): Promise { hostId: 'local', claimKeyId: 'key-1', resolveWorkspacePath: async () => stateDirectory, - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveEnvironment: async () => ({}), logger: createStructuredAgentSessionLogger() diff --git a/src/main/runtime/structured-agent-session-host-refusal.test.ts b/src/main/runtime/structured-agent-session-host-refusal.test.ts index 50c22f15b7dd..e8296141b3be 100644 --- a/src/main/runtime/structured-agent-session-host-refusal.test.ts +++ b/src/main/runtime/structured-agent-session-host-refusal.test.ts @@ -44,7 +44,6 @@ function install(): ReturnType { claimKeyId: 'key-1', resolveWorkspacePath: async () => root, resolveEnvironment: async () => ({}), - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }) }) } diff --git a/src/main/runtime/structured-agent-session-install-failure.test.ts b/src/main/runtime/structured-agent-session-install-failure.test.ts index ecc32d4f3837..6628f06c13f3 100644 --- a/src/main/runtime/structured-agent-session-install-failure.test.ts +++ b/src/main/runtime/structured-agent-session-install-failure.test.ts @@ -40,7 +40,6 @@ function install(): ReturnType { claimKeyId: 'key-1', resolveWorkspacePath: async () => root, resolveEnvironment: async () => ({}), - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }) }) } diff --git a/src/main/runtime/structured-agent-session-integration.test.ts b/src/main/runtime/structured-agent-session-integration.test.ts index 92510aa3241a..5ff7cb3029f7 100644 --- a/src/main/runtime/structured-agent-session-integration.test.ts +++ b/src/main/runtime/structured-agent-session-integration.test.ts @@ -346,7 +346,6 @@ beforeEach(async () => { claimKeyId: 'key-1', resolveWorkspacePath: async (workspaceId) => `/repos/${workspaceId}`, resolveCodexCommand: () => '/usr/local/bin/codex', - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveEnvironment: async () => { bootEnvironmentReads += 1 diff --git a/src/main/runtime/structured-agent-session-runtime-exit.test.ts b/src/main/runtime/structured-agent-session-runtime-exit.test.ts index d9562e2ffefe..706b4de5bce5 100644 --- a/src/main/runtime/structured-agent-session-runtime-exit.test.ts +++ b/src/main/runtime/structured-agent-session-runtime-exit.test.ts @@ -87,7 +87,6 @@ describe('structured session runtime provider-exit wiring', () => { hostId: 'local', claimKeyId: 'key-1', resolveWorkspacePath: async () => root!, - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveCodexCommand: () => 'codex', resolveEnvironment: async () => ({ PATH: process.env.PATH }), @@ -193,7 +192,6 @@ describe('structured session runtime provider-exit wiring', () => { hostId: 'local', claimKeyId: 'key-1', resolveWorkspacePath: async () => root!, - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveCodexCommand: () => 'codex', resolveEnvironment: async () => ({ PATH: process.env.PATH }), @@ -225,7 +223,6 @@ describe('structured session runtime provider-exit wiring', () => { hostId: 'local', claimKeyId: 'key-1', resolveWorkspacePath: async () => root!, - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveCodexCommand: () => 'codex', resolveEnvironment: async () => ({ PATH: process.env.PATH }), @@ -300,7 +297,6 @@ describe('structured session runtime provider-exit wiring', () => { hostId: 'local', claimKeyId: 'key-1', resolveWorkspacePath: async () => root!, - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveCodexCommand: () => 'codex', resolveEnvironment: async () => ({ PATH: process.env.PATH }), @@ -408,7 +404,6 @@ describe('structured session runtime provider-exit wiring', () => { hostId: 'local', claimKeyId: 'key-1', resolveWorkspacePath: async () => root!, - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveCodexCommand: () => 'codex', resolveEnvironment: async () => ({ PATH: process.env.PATH }), diff --git a/src/main/runtime/structured-agent-session-runtime.test.ts b/src/main/runtime/structured-agent-session-runtime.test.ts index c6b663bf0c14..4a879095b815 100644 --- a/src/main/runtime/structured-agent-session-runtime.test.ts +++ b/src/main/runtime/structured-agent-session-runtime.test.ts @@ -228,7 +228,6 @@ describe('structured agent-session runtime install', () => { hostId: HOST_ID, claimKeyId: 'key-1', resolveWorkspacePath: async () => stateDirectory!, - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveEnvironment: async () => ({}) }) @@ -270,7 +269,6 @@ describe('structured agent-session runtime install', () => { claimKeyId: 'key-1', resolveWorkspacePath: async () => stateDirectory!, resolveEnvironment: async () => ({}), - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), readProcessStartTime: async () => 1_700_000_000_000 }) diff --git a/src/main/runtime/structured-agent-session-runtime.ts b/src/main/runtime/structured-agent-session-runtime.ts index 9c28b4b8ad24..465b60947072 100644 --- a/src/main/runtime/structured-agent-session-runtime.ts +++ b/src/main/runtime/structured-agent-session-runtime.ts @@ -13,7 +13,6 @@ import type { PiRpcSessionDeps } from '../pi/rpc-session' import type { PermissionMode } from '@anthropic-ai/claude-agent-sdk' import { existsSync } from 'node:fs' -import type { AgentSessionRecord } from '../../shared/agent-session-record' import type { AgentSessionResumeTrigger } from '../../shared/agent-session-resume-marker' import type { StructuredAttentionMobileDelivery } from './structured-agent-session-mobile-attention' import { @@ -113,8 +112,6 @@ export type StructuredAgentSessionRuntimeDeps = { openClaudeConnection?: ClaudeStructuredSessionAdapterDeps['openConnection'] /** Scripted app-servers carry fake pids the real start-time read cannot answer for. */ readProcessStartTime?: CodexStructuredSessionAdapterDeps['readProcessStartTime'] - /** Required, and asserted at install time — saved Arguments must never be silently omitted. */ - resolveLaunchArgs: (provider: AgentSessionRecord['provider']) => Promise | string[] resolveLaunchEnv?: () => Promise resolveLaunchEnvOverlay?: () => Promise> | Record resolveClaudeLaunchEnv?: () => Promise> | Record @@ -159,9 +156,6 @@ let installing: Promise | null = null export const CLAUDE_STRUCTURED_AUTH_POLICY_REQUIRED = 'structured agent-session host requires a Claude auth policy resolver' -export const STRUCTURED_AGENT_LAUNCH_ARGS_REQUIRED = - 'structured agent-session host requires a launch arguments resolver' - /** Thrown when the host is installed without a logger: every failure it carries on past would * otherwise reach nobody. */ export const STRUCTURED_AGENT_SESSION_LOGGER_REQUIRED = @@ -250,9 +244,6 @@ async function install(deps: StructuredAgentSessionRuntimeDeps): Promise | undefined = deps.logger if (typeof declared?.warn !== 'function' || typeof declared.error !== 'function') { throw new Error(STRUCTURED_AGENT_SESSION_LOGGER_REQUIRED) diff --git a/src/main/runtime/structured-agent-session-spawn-token-descendants.test.ts b/src/main/runtime/structured-agent-session-spawn-token-descendants.test.ts index 7ebc52cd89a7..16cb94a812ff 100644 --- a/src/main/runtime/structured-agent-session-spawn-token-descendants.test.ts +++ b/src/main/runtime/structured-agent-session-spawn-token-descendants.test.ts @@ -142,7 +142,6 @@ describe('a process that inherited a spawn token', () => { hostId: HOST_ID, claimKeyId: 'key-1', resolveWorkspacePath: async () => stateDirectory, - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveEnvironment: async () => ({}) }) diff --git a/src/main/runtime/structured-agent-session-startup-reconcile.test.ts b/src/main/runtime/structured-agent-session-startup-reconcile.test.ts index ec800315ed6c..43d457790e31 100644 --- a/src/main/runtime/structured-agent-session-startup-reconcile.test.ts +++ b/src/main/runtime/structured-agent-session-startup-reconcile.test.ts @@ -80,7 +80,6 @@ function startupRuntime(log = recordingStructuredAgentSessionLogger()) { claimKeyId: 'key-1', resolveWorkspacePath: async () => root, resolveEnvironment: async () => ({}), - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }) }) internal.refreshMobileSessionPtyRecords = async () => new Set() diff --git a/src/main/runtime/structured-agent-session-startup-tab-restore.test.ts b/src/main/runtime/structured-agent-session-startup-tab-restore.test.ts index 0b3b8d9b307e..6131c830fa17 100644 --- a/src/main/runtime/structured-agent-session-startup-tab-restore.test.ts +++ b/src/main/runtime/structured-agent-session-startup-tab-restore.test.ts @@ -185,7 +185,6 @@ function startupRuntime(options: { afterInstall?: () => void; profileChats?: str claimKeyId: 'key-1', resolveWorkspacePath: async () => root, resolveEnvironment: async () => ({}), - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }) }) options.afterInstall?.() diff --git a/src/main/runtime/structured-chat-coordinator-mail-rig.test-fixture.ts b/src/main/runtime/structured-chat-coordinator-mail-rig.test-fixture.ts index 6a9ac88504d7..d96a497b3f60 100644 --- a/src/main/runtime/structured-chat-coordinator-mail-rig.test-fixture.ts +++ b/src/main/runtime/structured-chat-coordinator-mail-rig.test-fixture.ts @@ -255,7 +255,6 @@ beforeEach(async () => { claimKeyId: 'key-1', resolveWorkspacePath: async (workspaceId) => `/repos/${workspaceId}`, resolveCodexCommand: () => '/usr/local/bin/codex', - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveEnvironment: async () => ({ PATH: '/usr/bin' }), openCodexConnection: codex.openConnection, diff --git a/src/main/runtime/structured-child-records-switch.test.ts b/src/main/runtime/structured-child-records-switch.test.ts index 9e9eddb7f855..eaf5644164f8 100644 --- a/src/main/runtime/structured-child-records-switch.test.ts +++ b/src/main/runtime/structured-child-records-switch.test.ts @@ -123,7 +123,6 @@ describe('the chat strip and the session list read the same host child records', hostId: 'local', claimKeyId: 'key-1', resolveWorkspacePath: async () => root, - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveCodexCommand: () => 'codex', resolveEnvironment: async () => ({ PATH: process.env.PATH }), diff --git a/src/main/runtime/structured-claude-attachment-grant.test.ts b/src/main/runtime/structured-claude-attachment-grant.test.ts index 9971f93ccdbf..be3dfca4cbde 100644 --- a/src/main/runtime/structured-claude-attachment-grant.test.ts +++ b/src/main/runtime/structured-claude-attachment-grant.test.ts @@ -71,7 +71,6 @@ describe("structured Claude's read grant for chat attachments", () => { claimKeyId: 'key-1', resolveWorkspacePath: async () => directory, resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), - resolveLaunchArgs: () => [], resolveEnvironment: async () => ({}) }) diff --git a/src/main/runtime/structured-claude-pending-rewind.test.ts b/src/main/runtime/structured-claude-pending-rewind.test.ts index 4362c12fec76..52649785c59f 100644 --- a/src/main/runtime/structured-claude-pending-rewind.test.ts +++ b/src/main/runtime/structured-claude-pending-rewind.test.ts @@ -117,7 +117,6 @@ beforeEach(async () => { store, resolveWorkspacePath: async (id) => `/repos/${id}`, resolveClaudeCommand: () => '/usr/local/bin/claude', - resolveClaudeLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: false }), openClaudeConnection: claude.openConnection, readProcessStartTime: async () => HOST_TEST_NOW, diff --git a/src/main/runtime/structured-claude-runtime-adapter.ts b/src/main/runtime/structured-claude-runtime-adapter.ts index d9abf1cf3885..fef42047f01b 100644 --- a/src/main/runtime/structured-claude-runtime-adapter.ts +++ b/src/main/runtime/structured-claude-runtime-adapter.ts @@ -25,7 +25,6 @@ import type { PrepareNativeChatVisuals } from '../native-chat/native-chat-visual export type StructuredClaudeRuntimeAdapterDeps = { store: AgentSessionRecordStore resolveWorkspacePath: (workspaceId: string) => Promise - resolveClaudeLaunchArgs: () => Promise | string[] resolveClaudeCommand?: () => string /** Which version-gated flags a Claude CLI takes; absent never passes one. */ claudeCliFlags?: ClaudeCliFlagSupport @@ -105,7 +104,6 @@ export function createStructuredClaudeRuntimeAdapter( resolveLaunch: createClaudeStructuredLaunchResolver({ store, resolveWorkspacePath: deps.resolveWorkspacePath, - resolveLaunchArgs: deps.resolveClaudeLaunchArgs, resolveCommand: deps.resolveClaudeCommand ?? resolveClaudeCommand, ...(deps.resolveClaudeLaunchEnv ? { resolveEnv: deps.resolveClaudeLaunchEnv } : {}), ...(deps.resolveClaudeInheritedEnv diff --git a/src/main/runtime/structured-claude-scripted-runtime-test-support.ts b/src/main/runtime/structured-claude-scripted-runtime-test-support.ts index 77e66d31d639..0744bc809a6f 100644 --- a/src/main/runtime/structured-claude-scripted-runtime-test-support.ts +++ b/src/main/runtime/structured-claude-scripted-runtime-test-support.ts @@ -235,7 +235,6 @@ export function createScriptedClaudeRuntime(sessionIds: readonly string[]) { claimKeyId: 'key-1', resolveWorkspacePath: async () => directory, resolveClaudeCommand: () => '/usr/local/bin/claude', - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: false }), openClaudeConnection: openConnection, readProcessStartTime diff --git a/src/main/runtime/structured-codex-child-work-runtime.test.ts b/src/main/runtime/structured-codex-child-work-runtime.test.ts index 3bf2dbf7d19d..5e0cf36f14d7 100644 --- a/src/main/runtime/structured-codex-child-work-runtime.test.ts +++ b/src/main/runtime/structured-codex-child-work-runtime.test.ts @@ -76,7 +76,6 @@ describe('structured Codex child work through the production runtime', () => { hostId: 'local', claimKeyId: 'key-1', resolveWorkspacePath: async () => root!, - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveCodexCommand: () => 'codex', resolveEnvironment: async () => ({ PATH: process.env.PATH }), diff --git a/src/main/runtime/structured-codex-session-rpc-test-harness.ts b/src/main/runtime/structured-codex-session-rpc-test-harness.ts index 52929392d888..81e7d3f1d8c4 100644 --- a/src/main/runtime/structured-codex-session-rpc-test-harness.ts +++ b/src/main/runtime/structured-codex-session-rpc-test-harness.ts @@ -213,7 +213,6 @@ export async function openStructuredCodexRpcHarness( claimKeyId: 'key-1', resolveWorkspacePath: async (workspaceId) => `/repos/${workspaceId}`, resolveCodexCommand: () => '/usr/local/bin/codex', - resolveLaunchArgs: () => [], resolveClaudeAuthPolicy: () => ({ stripAuthEnv: true }), resolveEnvironment: async () => ({ PATH: '/shell/bin:/usr/bin', diff --git a/src/renderer/src/components/native-chat/agent-session-failure-words-text.test.ts b/src/renderer/src/components/native-chat/agent-session-failure-words-text.test.ts index 40d67de0c335..d60040428534 100644 --- a/src/renderer/src/components/native-chat/agent-session-failure-words-text.test.ts +++ b/src/renderer/src/components/native-chat/agent-session-failure-words-text.test.ts @@ -6,6 +6,7 @@ import zh from '@/i18n/locales/zh.json' import { AGENT_SESSION_ATTACHMENT_PROBLEM_REASONS, AGENT_SESSION_FAILURE_KINDS, + readAgentSessionFailureFact, readWholeAgentSessionFailureFact, type AgentSessionFailureFact } from '../../../../shared/agent-session-failure' @@ -223,6 +224,27 @@ describe('desktop words for a failure fact', () => { ) }) + // Current hosts never refuse saved Arguments; a row an older host stored must still read. + it("says an older host's saved Arguments restart failure on its row", () => { + const fact = readAgentSessionFailureFact({ + kind: 'restartFailed', + argumentProblem: { agent: 'Claude', option: '--model', problem: 'missingValue' } + }) + if (!fact) { + throw new Error('stored fact was not read') + } + expect( + agentSessionFailureSentence( + fact, + 'row', + { agentName: 'Claude' }, + sayAgentSessionFailureTranslated + ) + ).toBe( + "Claude couldn't restart. Saved Arguments need a value for --model. Edit them in Settings > Agents > Arguments. Send your message to try again." + ) + }) + it('uses the host sentence if the argument detail is newer than this reader', () => { const sentence = 'Codex could not start. Edit saved Arguments in Settings > Agents.' expect( diff --git a/src/renderer/src/components/settings/AgentCatalogRow.tsx b/src/renderer/src/components/settings/AgentCatalogRow.tsx index d9f6c926c399..75c20457a277 100644 --- a/src/renderer/src/components/settings/AgentCatalogRow.tsx +++ b/src/renderer/src/components/settings/AgentCatalogRow.tsx @@ -9,6 +9,7 @@ import { SettingsBadge, SettingsSegmentedControl } from './SettingsFormControls' import type { AgentSessionSourceHomeControl } from './codex-session-source-home-control' import { AgentSessionSourceHomeInput } from './codex-session-source-home-control' import { stringifyAgentDefaultEnvDraft } from './agent-default-env-draft' +import { agentArgumentsDescription } from './agent-arguments-description' import { AgentCommandOverrideInput, AgentDefaultArgsInput, @@ -214,6 +215,7 @@ export function AgentCatalogRow({ defaultArgs={defaultArgs} argsOverride={argsOverride} onSaveArgs={onSaveArgs} + description={agentArgumentsDescription(agentId)} /> {(defaultEnvSummary || envSummary) && ( diff --git a/src/renderer/src/components/settings/AgentLaunchDefaultsEditor.tsx b/src/renderer/src/components/settings/AgentLaunchDefaultsEditor.tsx index d6e51da9f49c..0ab21ab32255 100644 --- a/src/renderer/src/components/settings/AgentLaunchDefaultsEditor.tsx +++ b/src/renderer/src/components/settings/AgentLaunchDefaultsEditor.tsx @@ -72,11 +72,14 @@ export function AgentCommandOverrideInput({ export function AgentDefaultArgsInput({ defaultArgs, argsOverride, - onSaveArgs + onSaveArgs, + description }: { defaultArgs: string argsOverride: string onSaveArgs: (value: string) => void + /** Where these Arguments apply, for an agent whose chats don't use them. */ + description?: string }): React.JSX.Element { const [argsDraft, setArgsDraft] = useState(argsOverride) const commitArgs = (): void => onSaveArgs(argsDraft.trim()) @@ -123,6 +126,7 @@ export function AgentDefaultArgsInput({ )} + {description &&

{description}

} ) } diff --git a/src/renderer/src/components/settings/agent-arguments-description.test.ts b/src/renderer/src/components/settings/agent-arguments-description.test.ts new file mode 100644 index 000000000000..dbdedd9d3b8f --- /dev/null +++ b/src/renderer/src/components/settings/agent-arguments-description.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from 'vitest' +import { agentArgumentsDescription } from './agent-arguments-description' + +describe('agentArgumentsDescription', () => { + it.each([ + ['claude', 'Claude', '~/.claude/settings.json'], + ['codex', 'Codex', '~/.codex/config.toml'], + ['grok', 'Grok', '~/.grok/config.toml'], + ['opencode', 'OpenCode', '~/.config/opencode/opencode.json'] + ] as const)('tells %s users where native chat settings go instead', (agent, name, configFile) => { + expect(agentArgumentsDescription(agent)).toBe( + `Used when ${name} CLI is launched. Native chat does not use these; put its settings in ${configFile}.` + ) + }) + + it('names no file for an agent whose config Orca has not verified', () => { + expect(agentArgumentsDescription('omp')).toBe( + 'Used when OMP CLI is launched. Native chat does not use these.' + ) + }) + + // Agents with only terminal chats launch with these Arguments everywhere. + it('says nothing for an agent without a native chat', () => { + expect(agentArgumentsDescription('gemini')).toBeUndefined() + }) +}) diff --git a/src/renderer/src/components/settings/agent-arguments-description.ts b/src/renderer/src/components/settings/agent-arguments-description.ts new file mode 100644 index 000000000000..3ef5480bdbe1 --- /dev/null +++ b/src/renderer/src/components/settings/agent-arguments-description.ts @@ -0,0 +1,36 @@ +// The one line under an agent's Arguments field that says where they apply. Only agents with a +// native chat have one: that chat ignores saved Arguments, which only a CLI launch uses. +import { translate } from '@/i18n/i18n' +import type { TuiAgent } from '../../../../shared/tui-agent' + +const ARGUMENTS_DESCRIPTIONS: Partial string>> = { + claude: () => + translate( + 'auto.components.settings.AgentsPane.argumentsUse.claude', + 'Used when Claude CLI is launched. Native chat does not use these; put its settings in ~/.claude/settings.json.' + ), + codex: () => + translate( + 'auto.components.settings.AgentsPane.argumentsUse.codex', + 'Used when Codex CLI is launched. Native chat does not use these; put its settings in ~/.codex/config.toml.' + ), + grok: () => + translate( + 'auto.components.settings.AgentsPane.argumentsUse.grok', + 'Used when Grok CLI is launched. Native chat does not use these; put its settings in ~/.grok/config.toml.' + ), + opencode: () => + translate( + 'auto.components.settings.AgentsPane.argumentsUse.opencode', + 'Used when OpenCode CLI is launched. Native chat does not use these; put its settings in ~/.config/opencode/opencode.json.' + ), + omp: () => + translate( + 'auto.components.settings.AgentsPane.argumentsUse.omp', + 'Used when OMP CLI is launched. Native chat does not use these.' + ) +} + +export function agentArgumentsDescription(agent: TuiAgent): string | undefined { + return ARGUMENTS_DESCRIPTIONS[agent]?.() +} diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 97abc760cd61..d46e5dbc9dc6 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -6985,6 +6985,13 @@ "agentPermissions": "Agent Permissions", "agentPermissionsInfo": "Agent permissions info", "agentPermissionsTooltip": "Doesn't apply to agents where you've overridden launch arguments.", + "argumentsUse": { + "claude": "Used when Claude CLI is launched. Native chat does not use these; put its settings in ~/.claude/settings.json.", + "codex": "Used when Codex CLI is launched. Native chat does not use these; put its settings in ~/.codex/config.toml.", + "grok": "Used when Grok CLI is launched. Native chat does not use these; put its settings in ~/.grok/config.toml.", + "opencode": "Used when OpenCode CLI is launched. Native chat does not use these; put its settings in ~/.config/opencode/opencode.json.", + "omp": "Used when OMP CLI is launched. Native chat does not use these." + }, "agentPermissionsDescription": "Choose whether Orca launches agents with fewer permission prompts or with manual checks.", "agentPermissionsYolo": "Yolo", "agentPermissionsManual": "Manual", diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index ca78deda08b2..3f954baacb60 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -6747,6 +6747,13 @@ "agentPermissions": "Permisos de Agents", "agentPermissionsInfo": "Información de permisos de Agents", "agentPermissionsTooltip": "No se aplica a agents con argumentos de lanzamiento sobrescritos.", + "argumentsUse": { + "claude": "Se usan al iniciar Claude CLI. El chat nativo no los usa; su configuración va en ~/.claude/settings.json.", + "codex": "Se usan al iniciar Codex CLI. El chat nativo no los usa; su configuración va en ~/.codex/config.toml.", + "grok": "Se usan al iniciar Grok CLI. El chat nativo no los usa; su configuración va en ~/.grok/config.toml.", + "opencode": "Se usan al iniciar OpenCode CLI. El chat nativo no los usa; su configuración va en ~/.config/opencode/opencode.json.", + "omp": "Se usan al iniciar OMP CLI. El chat nativo no los usa." + }, "agentPermissionsDescription": "Elige si Orca lanza agents con menos prompts de permisos o con checks manuales.", "agentPermissionsYolo": "Yolo", "agentPermissionsManual": "Manual", diff --git a/src/renderer/src/i18n/locales/fr.json b/src/renderer/src/i18n/locales/fr.json index a18e5d599a4b..5cb30aa7620e 100644 --- a/src/renderer/src/i18n/locales/fr.json +++ b/src/renderer/src/i18n/locales/fr.json @@ -6856,6 +6856,13 @@ "agentPermissions": "Permissions des agents", "agentPermissionsInfo": "Infos permissions des agents", "agentPermissionsTooltip": "Ne s'applique pas aux agents dont vous avez remplacé les arguments de lancement.", + "argumentsUse": { + "claude": "Utilisés au lancement de Claude CLI. Le chat natif ne les utilise pas ; mettez ses paramètres dans ~/.claude/settings.json.", + "codex": "Utilisés au lancement de Codex CLI. Le chat natif ne les utilise pas ; mettez ses paramètres dans ~/.codex/config.toml.", + "grok": "Utilisés au lancement de Grok CLI. Le chat natif ne les utilise pas ; mettez ses paramètres dans ~/.grok/config.toml.", + "opencode": "Utilisés au lancement d'OpenCode CLI. Le chat natif ne les utilise pas ; mettez ses paramètres dans ~/.config/opencode/opencode.json.", + "omp": "Utilisés au lancement d'OMP CLI. Le chat natif ne les utilise pas." + }, "agentPermissionsDescription": "Choisissez si Orca lance les agents avec moins de demandes de permission ou avec des vérifications manuelles.", "agentPermissionsYolo": "Yolo", "agentPermissionsManual": "Manuel", diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 72efe22af4f2..a56b3e4145bf 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -6732,6 +6732,13 @@ "agentPermissions": "Agent の権限", "agentPermissionsInfo": "Agent 権限情報", "agentPermissionsTooltip": "起動引数を上書きしている Agent には適用されません。", + "argumentsUse": { + "claude": "Claude CLI の起動時に使用されます。ネイティブチャットではこれらは使用されません。設定は ~/.claude/settings.json に記述してください。", + "codex": "Codex CLI の起動時に使用されます。ネイティブチャットではこれらは使用されません。設定は ~/.codex/config.toml に記述してください。", + "grok": "Grok CLI の起動時に使用されます。ネイティブチャットではこれらは使用されません。設定は ~/.grok/config.toml に記述してください。", + "opencode": "OpenCode CLI の起動時に使用されます。ネイティブチャットではこれらは使用されません。設定は ~/.config/opencode/opencode.json に記述してください。", + "omp": "OMP CLI の起動時に使用されます。ネイティブチャットではこれらは使用されません。" + }, "agentPermissionsDescription": "Orca が権限プロンプトの少ない Agent を起動するか、手動チェックを含む Agent を起動するかを選択します。", "agentPermissionsYolo": "Yolo", "agentPermissionsManual": "手動", diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 1a66b828be6a..f5dc3e224612 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -6732,6 +6732,13 @@ "agentPermissions": "Agent 권한", "agentPermissionsInfo": "Agent 권한 정보", "agentPermissionsTooltip": "Custom agent arguments stay unchanged when switching modes.", + "argumentsUse": { + "claude": "Claude CLI를 실행할 때 사용됩니다. 네이티브 채팅은 이 인수를 사용하지 않습니다. 설정은 ~/.claude/settings.json에 넣으세요.", + "codex": "Codex CLI를 실행할 때 사용됩니다. 네이티브 채팅은 이 인수를 사용하지 않습니다. 설정은 ~/.codex/config.toml에 넣으세요.", + "grok": "Grok CLI를 실행할 때 사용됩니다. 네이티브 채팅은 이 인수를 사용하지 않습니다. 설정은 ~/.grok/config.toml에 넣으세요.", + "opencode": "OpenCode CLI를 실행할 때 사용됩니다. 네이티브 채팅은 이 인수를 사용하지 않습니다. 설정은 ~/.config/opencode/opencode.json에 넣으세요.", + "omp": "OMP CLI를 실행할 때 사용됩니다. 네이티브 채팅은 이 인수를 사용하지 않습니다." + }, "agentPermissionsDescription": "Orca가 agents를 실행할 때 권한 프롬프트를 줄일지, 수동 확인을 유지할지 선택합니다.", "agentPermissionsYolo": "Yolo", "agentPermissionsManual": "수동", diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index cccf45c2ed35..f654c730f5d6 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -6732,6 +6732,13 @@ "agentPermissions": "智能体权限", "agentPermissionsInfo": "智能体权限信息", "agentPermissionsTooltip": "切换模式时,自定义智能体参数保持不变。", + "argumentsUse": { + "claude": "启动 Claude CLI 时使用。原生聊天不使用这些参数;请将其设置写入 ~/.claude/settings.json。", + "codex": "启动 Codex CLI 时使用。原生聊天不使用这些参数;请将其设置写入 ~/.codex/config.toml。", + "grok": "启动 Grok CLI 时使用。原生聊天不使用这些参数;请将其设置写入 ~/.grok/config.toml。", + "opencode": "启动 OpenCode CLI 时使用。原生聊天不使用这些参数;请将其设置写入 ~/.config/opencode/opencode.json。", + "omp": "启动 OMP CLI 时使用。原生聊天不使用这些参数。" + }, "agentPermissionsDescription": "选择 Orca 是以更少的权限提示启动智能体,还是手动检查。", "agentPermissionsYolo": "Yolo", "agentPermissionsManual": "手动", diff --git a/src/shared/agent-launch-intent.ts b/src/shared/agent-launch-intent.ts index b16287660f7d..2738b9880a6f 100644 --- a/src/shared/agent-launch-intent.ts +++ b/src/shared/agent-launch-intent.ts @@ -71,8 +71,8 @@ export type AgentLaunchIntent = { * caller explicitly wants none, and collapsing the two would make a recipe that clears its args * silently inherit whatever the settings happen to hold. * - * Deliberately NOT a route input. Structured chat reads the execution host's saved Arguments; - * per-call overrides remain terminal-only and the host reports that in `warning`. + * Deliberately NOT a route input. Structured chat uses no launch arguments, saved or per-call; + * overrides remain terminal-only and the host reports that in `warning`. */ agentArgs?: string | null /** diff --git a/src/shared/agent-session-argument-problem.ts b/src/shared/agent-session-argument-problem.ts index b6fcc98d3362..2b9115fcd716 100644 --- a/src/shared/agent-session-argument-problem.ts +++ b/src/shared/agent-session-argument-problem.ts @@ -1,4 +1,5 @@ -/** Orca's validated saved Arguments refusal. The option has no value or user-authored operand. */ +/** An older host's saved Arguments refusal; current hosts never send one. The option has no value or + * user-authored operand. */ export type AgentSessionArgumentProblem = { agent: 'Codex' | 'Claude' option: string diff --git a/src/shared/agent-session-failure.ts b/src/shared/agent-session-failure.ts index cca1b1c9b27a..9589feb5db33 100644 --- a/src/shared/agent-session-failure.ts +++ b/src/shared/agent-session-failure.ts @@ -152,7 +152,7 @@ export type AgentSessionFailureFact = { attachment?: AgentSessionAttachmentProblem /** On `providerRetrying`: why the provider is retrying. */ retry?: AgentSessionProviderRetry - /** A safe option name from Orca's saved Arguments parser, never an error message. */ + /** From older hosts only, which refused saved Arguments: a safe option name, never an error message. */ argumentProblem?: AgentSessionArgumentProblem } diff --git a/src/shared/agent-session-launch-args.ts b/src/shared/agent-session-launch-args.ts deleted file mode 100644 index 94808d77db08..000000000000 --- a/src/shared/agent-session-launch-args.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { AgentSessionLaunchArgs } from './agent-session-record' - -const MAX_LAUNCH_ARGS = 256 -const MAX_LAUNCH_ARGS_BYTES = 16 * 1024 - -/** Arguments pinned on a record's first reservation, bounded before they are persisted. */ -export function isAgentSessionLaunchArgs(value: unknown): value is AgentSessionLaunchArgs { - return ( - Array.isArray(value) && - value.length <= MAX_LAUNCH_ARGS && - value.every((arg) => typeof arg === 'string' && !arg.includes('\0')) && - Buffer.byteLength(JSON.stringify(value), 'utf8') <= MAX_LAUNCH_ARGS_BYTES - ) -} diff --git a/src/shared/agent-session-record.ts b/src/shared/agent-session-record.ts index c228335a6264..a4a978d40710 100644 --- a/src/shared/agent-session-record.ts +++ b/src/shared/agent-session-record.ts @@ -1,5 +1,4 @@ import { isAgentSessionRewindRecord, type AgentSessionRewindRecord } from './agent-session-rewind' -import { isAgentSessionLaunchArgs } from './agent-session-launch-args' import { isAgentSessionConversationName } from './agent-session-conversation-name' import { isPersistedAgentSessionHandoffStage, @@ -56,9 +55,6 @@ export type AgentSessionExecutionLocation = { /** Provider launch environment captured by the host when the session is created. */ export type AgentSessionLaunchEnv = Record -/** Provider CLI arguments captured by the host when the session is created. */ -export type AgentSessionLaunchArgs = string[] - /** Still persisted because older builds read it. The removed terminal handoff's `tui` is mapped * away at decode (agent-session-legacy-handoff-lease). */ export type AgentSessionOwnerRuntimeKind = 'native' @@ -159,7 +155,6 @@ export type AgentSessionRecord = { conversationCommand?: AgentSessionConversationCommandRecord /** The name Orca gave this conversation, so a later acquisition need not name it again. */ conversationName?: string - launchArgs?: AgentSessionLaunchArgs lease: AgentSessionLease createdAt: number updatedAt: number @@ -363,7 +358,6 @@ export function isPersistedAgentSessionRecord( isAgentSessionConversationCommandRecord(record.conversationCommand)) && (record.conversationName === undefined || isAgentSessionConversationName(record.conversationName)) && - (record.launchArgs === undefined || isAgentSessionLaunchArgs(record.launchArgs)) && !Object.hasOwn(record, 'launchEnv') && isPersistedAgentSessionLease(record.lease) && record.lease.sessionId === record.sessionId && diff --git a/src/shared/agent-session-refusal-notice.test.ts b/src/shared/agent-session-refusal-notice.test.ts index 43ed3c6706a5..943a45a37c24 100644 --- a/src/shared/agent-session-refusal-notice.test.ts +++ b/src/shared/agent-session-refusal-notice.test.ts @@ -702,6 +702,7 @@ describe('agentSessionRefusalCauseParts', () => { }) }) +// Chats no longer read saved Arguments, but an older host still sends these refusals. describe('saved Arguments refusals', () => { it('keeps generic launch copy when an older reader drops the new detail', () => { const wire = JSON.parse(