From 33d4b62b2e3a4391c0c7352fea0803fb81f156f4 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Thu, 8 Oct 2026 00:49:44 -0700 Subject: [PATCH 1/7] fix(native-chat): chats ignore saved Arguments; they are for terminal launches Settings -> Agents -> Arguments holds a terminal launch's options, but native chat tried to apply them too: Claude squeezed them into the SDK's one-value-per-option record (dropping some flags silently and refusing a chat over a valid repeat), and Codex placed them before `app-server`, where Codex ignores most of them. A user could not tell which saved option did anything in a chat. Native chat now ignores saved Arguments for every agent: Codex chat runs plain `app-server`, and Claude chat gets only Orca's own options. The Command setting still picks the program, and Agent Permissions' posture (which it keeps in the Arguments field) is still read for the chat's permissions. Removed with it: the Claude and Codex Arguments translators, the configured-Arguments reader, the host-side Arguments refusal and its error class, the runtime's Arguments resolver, the pinned launch-arguments helper, and the record's never-launched `launchArgs` field and its carry-through. The shared reader and copy for an Arguments refusal stay, so a chat on an older host still words one it sends. --- .../claude-agent-sdk-contract-pins.test.ts | 1 - .../claude-structured-auth-parity.test.ts | 1 - ...-structured-launch-account-routing.test.ts | 2 - .../claude-structured-launch-args.test.ts | 90 ------------- .../claude/claude-structured-launch-args.ts | 85 ------------ ...laude-structured-launch-resolution.test.ts | 122 ++---------------- .../claude-structured-launch-resolution.ts | 5 - .../claude-structured-launch-visuals.test.ts | 1 - ...claude-structured-real-cli-visuals.test.ts | 1 - .../claude-structured-spawn-options.test.ts | 37 ------ .../claude/claude-structured-spawn-options.ts | 27 +--- ...laude-structured-turn-resume-point.test.ts | 1 - .../codex/codex-model-catalog-probe.test.ts | 3 + .../codex-structured-launch-args.test.ts | 103 --------------- .../codex/codex-structured-launch-args.ts | 115 ----------------- ...codex-structured-launch-resolution.test.ts | 68 +++------- .../codex-structured-launch-resolution.ts | 6 +- ...-agent-session-accept-then-deliver.test.ts | 23 ---- .../structured-agent-session-agent-start.ts | 7 +- ...ured-agent-session-attach-orchestration.ts | 6 +- .../structured-agent-session-attach.ts | 4 - .../structured-agent-session-delivery-loop.ts | 6 +- ...red-agent-session-failed-create-refusal.ts | 19 +-- ...uctured-agent-session-failure-text.test.ts | 36 +----- .../structured-agent-session-failure-text.ts | 8 +- .../structured-agent-session-host-types.ts | 3 - ...tructured-agent-session-launch-env.test.ts | 11 +- .../structured-agent-session-launch-env.ts | 12 -- ...ent-session-pre-spawn-first-answer.test.ts | 24 ---- .../structured-conversation-command.test.ts | 32 ----- .../structured-agent-arguments-error.ts | 44 ------- .../structured-agent-configured-args.test.ts | 47 ------- .../structured-agent-configured-args.ts | 27 ---- ...-session-conversation-clear-commit.test.ts | 2 - .../runtime/agent-session-record-founding.ts | 9 +- .../agent-session-record-store-slot.test.ts | 3 +- .../agent-session-reservation-admission.ts | 14 +- ...ude-structured-session-integration.test.ts | 1 - .../runtime/orca-runtime-get-worktree-ps.ts | 3 - ...-structured-launch-settings-wiring.test.ts | 9 +- ...tructured-agent-launch-args-wiring.test.ts | 37 ------ .../structured-agent-runtime-registrations.ts | 2 - ...gent-session-codex-interrupt-order.test.ts | 1 - ...nt-session-codex-opening-send-hold.test.ts | 1 - ...ent-session-codex-settlement-order.test.ts | 1 - ...t-session-codex-stopped-send-order.test.ts | 1 - ...-session-codex-turn-end-settlement.test.ts | 1 - ...ured-agent-session-failure-logging.test.ts | 1 - ...ructured-agent-session-held-signal.test.ts | 1 - ...uctured-agent-session-host-refusal.test.ts | 1 - ...ured-agent-session-install-failure.test.ts | 1 - ...ructured-agent-session-integration.test.ts | 1 - ...uctured-agent-session-runtime-exit.test.ts | 5 - .../structured-agent-session-runtime.test.ts | 2 - .../structured-agent-session-runtime.ts | 9 -- ...nt-session-spawn-token-descendants.test.ts | 1 - ...ed-agent-session-startup-reconcile.test.ts | 1 - ...-agent-session-startup-tab-restore.test.ts | 1 - ...-chat-coordinator-mail-rig.test-fixture.ts | 1 - .../structured-child-records-switch.test.ts | 1 - ...structured-claude-attachment-grant.test.ts | 1 - .../structured-claude-pending-rewind.test.ts | 1 - .../structured-claude-runtime-adapter.ts | 2 - ...ed-claude-scripted-runtime-test-support.ts | 1 - ...tructured-codex-child-work-runtime.test.ts | 1 - ...ructured-codex-session-rpc-test-harness.ts | 1 - src/shared/agent-session-launch-args.ts | 14 -- src/shared/agent-session-record.ts | 6 - .../agent-session-refusal-notice.test.ts | 1 + 69 files changed, 56 insertions(+), 1060 deletions(-) delete mode 100644 src/main/claude/claude-structured-launch-args.test.ts delete mode 100644 src/main/claude/claude-structured-launch-args.ts delete mode 100644 src/main/codex/codex-structured-launch-args.test.ts delete mode 100644 src/main/codex/codex-structured-launch-args.ts delete mode 100644 src/main/native-chat/structured-agent-arguments-error.ts delete mode 100644 src/main/native-chat/structured-agent-configured-args.test.ts delete mode 100644 src/main/native-chat/structured-agent-configured-args.ts delete mode 100644 src/main/runtime/structured-agent-launch-args-wiring.test.ts delete mode 100644 src/shared/agent-session-launch-args.ts 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..425f3fabd99f 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, 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..6d614cade363 100644 --- a/src/main/claude/claude-structured-launch-visuals.test.ts +++ b/src/main/claude/claude-structured-launch-visuals.test.ts @@ -40,7 +40,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 } : {}), 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-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-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-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/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..cd02b81f552b 100644 --- a/src/main/runtime/structured-agent-session-failure-logging.test.ts +++ b/src/main/runtime/structured-agent-session-failure-logging.test.ts @@ -324,7 +324,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/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( From 6daeeef5634e9d8f53407ad8130ab250c19ffd7e Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Thu, 8 Oct 2026 00:57:40 -0700 Subject: [PATCH 2/7] test(native-chat): Claude visuals and SDK pins no longer expect saved Arguments in the launch --- .../claude/claude-agent-sdk-contract-pins.test.ts | 12 ++++-------- .../claude-structured-launch-visuals.test.ts | 15 ++++++--------- 2 files changed, 10 insertions(+), 17 deletions(-) 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 425f3fabd99f..018a4f97de23 100644 --- a/src/main/claude/claude-agent-sdk-contract-pins.test.ts +++ b/src/main/claude/claude-agent-sdk-contract-pins.test.ts @@ -328,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', @@ -357,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-launch-visuals.test.ts b/src/main/claude/claude-structured-launch-visuals.test.ts index 6d614cade363..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 }) { @@ -49,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) }) @@ -66,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) From 05eadc3d6ee15715cb12d3eec9efc7ca01e0c854 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Thu, 8 Oct 2026 01:23:12 -0700 Subject: [PATCH 3/7] feat(settings): say under Arguments that structured chats don't use them, and where their settings go --- .../components/settings/AgentCatalogRow.tsx | 2 + .../settings/AgentLaunchDefaultsEditor.tsx | 6 ++- .../agent-arguments-description.test.ts | 25 +++++++++++++ .../settings/agent-arguments-description.ts | 37 +++++++++++++++++++ src/renderer/src/i18n/locales/en.json | 7 ++++ 5 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/renderer/src/components/settings/agent-arguments-description.test.ts create mode 100644 src/renderer/src/components/settings/agent-arguments-description.ts 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..82b40420ca63 --- /dev/null +++ b/src/renderer/src/components/settings/agent-arguments-description.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest' +import { agentArgumentsDescription } from './agent-arguments-description' + +describe('agentArgumentsDescription', () => { + it.each([ + ['claude', '~/.claude/settings.json'], + ['codex', '~/.codex/config.toml'], + ['grok', '~/.grok/config.toml'], + ['opencode', '~/.config/opencode/opencode.json'] + ] as const)('tells %s users where chat settings go instead', (agent, configFile) => { + expect(agentArgumentsDescription(agent)).toContain("Structured chats don't use these") + expect(agentArgumentsDescription(agent)).toContain(configFile) + }) + + it('names no file for an agent whose config Orca has not verified', () => { + expect(agentArgumentsDescription('omp')).toBe( + "Used when OMP runs in a terminal, including terminal chats. Structured chats don't use these." + ) + }) + + // Agents with only terminal chats launch with these Arguments everywhere. + it('says nothing for an agent with no structured 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..21d1d638d915 --- /dev/null +++ b/src/renderer/src/components/settings/agent-arguments-description.ts @@ -0,0 +1,37 @@ +// The one line under an agent's Arguments field that says where they apply. Only agents with a +// structured chat have one: that chat ignores saved Arguments, while terminal tabs and the +// terminal-backed chat still launch with them. +import { translate } from '@/i18n/i18n' +import type { TuiAgent } from '../../../../shared/tui-agent' + +export function agentArgumentsDescription(agent: TuiAgent): string | undefined { + switch (agent) { + case 'claude': + return translate( + 'auto.components.settings.AgentsPane.argumentsUse.claude', + "Used when Claude runs in a terminal, including terminal chats. Structured chats don't use these; put their settings in ~/.claude/settings.json." + ) + case 'codex': + return translate( + 'auto.components.settings.AgentsPane.argumentsUse.codex', + "Used when Codex runs in a terminal, including terminal chats. Structured chats don't use these; put their settings in ~/.codex/config.toml." + ) + case 'grok': + return translate( + 'auto.components.settings.AgentsPane.argumentsUse.grok', + "Used when Grok runs in a terminal, including terminal chats. Structured chats don't use these; put their settings in ~/.grok/config.toml." + ) + case 'opencode': + return translate( + 'auto.components.settings.AgentsPane.argumentsUse.opencode', + "Used when OpenCode runs in a terminal, including terminal chats. Structured chats don't use these; put their settings in ~/.config/opencode/opencode.json." + ) + case 'omp': + return translate( + 'auto.components.settings.AgentsPane.argumentsUse.omp', + "Used when OMP runs in a terminal, including terminal chats. Structured chats don't use these." + ) + default: + return undefined + } +} diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 97abc760cd61..e0d07bed8025 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 runs in a terminal, including terminal chats. Structured chats don't use these; put their settings in ~/.claude/settings.json.", + "codex": "Used when Codex runs in a terminal, including terminal chats. Structured chats don't use these; put their settings in ~/.codex/config.toml.", + "grok": "Used when Grok runs in a terminal, including terminal chats. Structured chats don't use these; put their settings in ~/.grok/config.toml.", + "opencode": "Used when OpenCode runs in a terminal, including terminal chats. Structured chats don't use these; put their settings in ~/.config/opencode/opencode.json.", + "omp": "Used when OMP runs in a terminal, including terminal chats. Structured chats don't use these." + }, "agentPermissionsDescription": "Choose whether Orca launches agents with fewer permission prompts or with manual checks.", "agentPermissionsYolo": "Yolo", "agentPermissionsManual": "Manual", From d9c72a2cfb8d243dc7460e2eb063c7a2f1505d10 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Thu, 8 Oct 2026 01:29:09 -0700 Subject: [PATCH 4/7] feat(settings): Arguments line says terminal tabs and chats use them and the updated native chat doesn't --- .../agent-arguments-description.test.ts | 6 +- .../settings/agent-arguments-description.ts | 62 +++++++++---------- src/renderer/src/i18n/locales/en.json | 10 +-- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/renderer/src/components/settings/agent-arguments-description.test.ts b/src/renderer/src/components/settings/agent-arguments-description.test.ts index 82b40420ca63..718474003563 100644 --- a/src/renderer/src/components/settings/agent-arguments-description.test.ts +++ b/src/renderer/src/components/settings/agent-arguments-description.test.ts @@ -8,18 +8,18 @@ describe('agentArgumentsDescription', () => { ['grok', '~/.grok/config.toml'], ['opencode', '~/.config/opencode/opencode.json'] ] as const)('tells %s users where chat settings go instead', (agent, configFile) => { - expect(agentArgumentsDescription(agent)).toContain("Structured chats don't use these") + expect(agentArgumentsDescription(agent)).toContain("The updated native chat doesn't use these") expect(agentArgumentsDescription(agent)).toContain(configFile) }) it('names no file for an agent whose config Orca has not verified', () => { expect(agentArgumentsDescription('omp')).toBe( - "Used when OMP runs in a terminal, including terminal chats. Structured chats don't use these." + "Used in terminal tabs and terminal chats. The updated native chat doesn't use these." ) }) // Agents with only terminal chats launch with these Arguments everywhere. - it('says nothing for an agent with no structured chat', () => { + it('says nothing for an agent without the updated 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 index 21d1d638d915..d1779230f43c 100644 --- a/src/renderer/src/components/settings/agent-arguments-description.ts +++ b/src/renderer/src/components/settings/agent-arguments-description.ts @@ -1,37 +1,37 @@ -// The one line under an agent's Arguments field that says where they apply. Only agents with a -// structured chat have one: that chat ignores saved Arguments, while terminal tabs and the +// The one line under an agent's Arguments field that says where they apply. Only agents with the +// updated native chat have one: that chat ignores saved Arguments, while terminal tabs and the // terminal-backed chat still launch with them. 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 in terminal tabs and terminal chats. The updated native chat doesn't use these; put its settings in ~/.claude/settings.json." + ), + codex: () => + translate( + 'auto.components.settings.AgentsPane.argumentsUse.codex', + "Used in terminal tabs and terminal chats. The updated native chat doesn't use these; put its settings in ~/.codex/config.toml." + ), + grok: () => + translate( + 'auto.components.settings.AgentsPane.argumentsUse.grok', + "Used in terminal tabs and terminal chats. The updated native chat doesn't use these; put its settings in ~/.grok/config.toml." + ), + opencode: () => + translate( + 'auto.components.settings.AgentsPane.argumentsUse.opencode', + "Used in terminal tabs and terminal chats. The updated native chat doesn't use these; put its settings in ~/.config/opencode/opencode.json." + ), + omp: () => + translate( + 'auto.components.settings.AgentsPane.argumentsUse.omp', + "Used in terminal tabs and terminal chats. The updated native chat doesn't use these." + ) +} + export function agentArgumentsDescription(agent: TuiAgent): string | undefined { - switch (agent) { - case 'claude': - return translate( - 'auto.components.settings.AgentsPane.argumentsUse.claude', - "Used when Claude runs in a terminal, including terminal chats. Structured chats don't use these; put their settings in ~/.claude/settings.json." - ) - case 'codex': - return translate( - 'auto.components.settings.AgentsPane.argumentsUse.codex', - "Used when Codex runs in a terminal, including terminal chats. Structured chats don't use these; put their settings in ~/.codex/config.toml." - ) - case 'grok': - return translate( - 'auto.components.settings.AgentsPane.argumentsUse.grok', - "Used when Grok runs in a terminal, including terminal chats. Structured chats don't use these; put their settings in ~/.grok/config.toml." - ) - case 'opencode': - return translate( - 'auto.components.settings.AgentsPane.argumentsUse.opencode', - "Used when OpenCode runs in a terminal, including terminal chats. Structured chats don't use these; put their settings in ~/.config/opencode/opencode.json." - ) - case 'omp': - return translate( - 'auto.components.settings.AgentsPane.argumentsUse.omp', - "Used when OMP runs in a terminal, including terminal chats. Structured chats don't use these." - ) - default: - return undefined - } + return ARGUMENTS_DESCRIPTIONS[agent]?.() } diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index e0d07bed8025..3b699ee841da 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -6986,11 +6986,11 @@ "agentPermissionsInfo": "Agent permissions info", "agentPermissionsTooltip": "Doesn't apply to agents where you've overridden launch arguments.", "argumentsUse": { - "claude": "Used when Claude runs in a terminal, including terminal chats. Structured chats don't use these; put their settings in ~/.claude/settings.json.", - "codex": "Used when Codex runs in a terminal, including terminal chats. Structured chats don't use these; put their settings in ~/.codex/config.toml.", - "grok": "Used when Grok runs in a terminal, including terminal chats. Structured chats don't use these; put their settings in ~/.grok/config.toml.", - "opencode": "Used when OpenCode runs in a terminal, including terminal chats. Structured chats don't use these; put their settings in ~/.config/opencode/opencode.json.", - "omp": "Used when OMP runs in a terminal, including terminal chats. Structured chats don't use these." + "claude": "Used in terminal tabs and terminal chats. The updated native chat doesn't use these; put its settings in ~/.claude/settings.json.", + "codex": "Used in terminal tabs and terminal chats. The updated native chat doesn't use these; put its settings in ~/.codex/config.toml.", + "grok": "Used in terminal tabs and terminal chats. The updated native chat doesn't use these; put its settings in ~/.grok/config.toml.", + "opencode": "Used in terminal tabs and terminal chats. The updated native chat doesn't use these; put its settings in ~/.config/opencode/opencode.json.", + "omp": "Used in terminal tabs and terminal chats. The updated native chat doesn't use these." }, "agentPermissionsDescription": "Choose whether Orca launches agents with fewer permission prompts or with manual checks.", "agentPermissionsYolo": "Yolo", From 53b2364850de4bce2cfb8060bf121d655783dcf6 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Thu, 8 Oct 2026 01:40:35 -0700 Subject: [PATCH 5/7] fix(native-chat): drop the Arguments-era Fast test and warning wording; cover older hosts' rows and records --- .../agent-launch-executor.test.ts | 8 +++-- .../agent-launch/agent-launch-executor.ts | 6 ++-- .../claude-structured-start-fast-mode.test.ts | 34 ------------------- .../claude-structured-start-fast-mode.ts | 10 ++---- .../agent-session-record-store.test.ts | 12 +++++++ .../rpc/methods/agent-launch-surfaces.ts | 2 +- ...ured-agent-session-failure-logging.test.ts | 1 - .../agent-session-failure-words-text.test.ts | 22 ++++++++++++ src/shared/agent-launch-intent.ts | 4 +-- src/shared/agent-session-argument-problem.ts | 3 +- src/shared/agent-session-failure.ts | 2 +- 11 files changed, 51 insertions(+), 53 deletions(-) delete mode 100644 src/main/claude/claude-structured-start-fast-mode.test.ts 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-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..56d5839bf54f 100644 --- a/src/main/claude/claude-structured-start-fast-mode.ts +++ b/src/main/claude/claude-structured-start-fast-mode.ts @@ -8,10 +8,9 @@ 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. The value still to be applied, or null. */ export function admitClaudeStartFastMode( session: ClaudeSession, facts: ClaudeStartupFacts @@ -20,9 +19,6 @@ export function admitClaudeStartFastMode( if (!session.fastModeAtStart || fastMode === undefined) { return null } - if (fastMode !== 'true') { - return fastMode - } if (!facts.resumesTranscript && facts.prepared.fastModePerSessionOptIn === true) { session.options.delete('fastMode') return null 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/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-session-failure-logging.test.ts b/src/main/runtime/structured-agent-session-failure-logging.test.ts index cd02b81f552b..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] ) 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/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 } From a1f08abb287ce7d14ccea6480aeb6aaf01e9afca Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Thu, 8 Oct 2026 02:31:38 -0700 Subject: [PATCH 6/7] feat(settings): Brennan's Arguments line, in every locale: CLI launches use them, native chat does not --- .../agent-arguments-description.test.ts | 19 ++++++++++--------- .../settings/agent-arguments-description.ts | 15 +++++++-------- src/renderer/src/i18n/locales/en.json | 10 +++++----- src/renderer/src/i18n/locales/es.json | 7 +++++++ src/renderer/src/i18n/locales/fr.json | 7 +++++++ src/renderer/src/i18n/locales/ja.json | 7 +++++++ src/renderer/src/i18n/locales/ko.json | 7 +++++++ src/renderer/src/i18n/locales/zh.json | 7 +++++++ 8 files changed, 57 insertions(+), 22 deletions(-) diff --git a/src/renderer/src/components/settings/agent-arguments-description.test.ts b/src/renderer/src/components/settings/agent-arguments-description.test.ts index 718474003563..dbdedd9d3b8f 100644 --- a/src/renderer/src/components/settings/agent-arguments-description.test.ts +++ b/src/renderer/src/components/settings/agent-arguments-description.test.ts @@ -3,23 +3,24 @@ import { agentArgumentsDescription } from './agent-arguments-description' describe('agentArgumentsDescription', () => { it.each([ - ['claude', '~/.claude/settings.json'], - ['codex', '~/.codex/config.toml'], - ['grok', '~/.grok/config.toml'], - ['opencode', '~/.config/opencode/opencode.json'] - ] as const)('tells %s users where chat settings go instead', (agent, configFile) => { - expect(agentArgumentsDescription(agent)).toContain("The updated native chat doesn't use these") - expect(agentArgumentsDescription(agent)).toContain(configFile) + ['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 in terminal tabs and terminal chats. The updated native chat doesn't use these." + '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 the updated native chat', () => { + 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 index d1779230f43c..3ef5480bdbe1 100644 --- a/src/renderer/src/components/settings/agent-arguments-description.ts +++ b/src/renderer/src/components/settings/agent-arguments-description.ts @@ -1,6 +1,5 @@ -// The one line under an agent's Arguments field that says where they apply. Only agents with the -// updated native chat have one: that chat ignores saved Arguments, while terminal tabs and the -// terminal-backed chat still launch with them. +// 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' @@ -8,27 +7,27 @@ const ARGUMENTS_DESCRIPTIONS: Partial string>> = { claude: () => translate( 'auto.components.settings.AgentsPane.argumentsUse.claude', - "Used in terminal tabs and terminal chats. The updated native chat doesn't use these; put its settings in ~/.claude/settings.json." + '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 in terminal tabs and terminal chats. The updated native chat doesn't use these; put its settings in ~/.codex/config.toml." + '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 in terminal tabs and terminal chats. The updated native chat doesn't use these; put its settings in ~/.grok/config.toml." + '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 in terminal tabs and terminal chats. The updated native chat doesn't use these; put its settings in ~/.config/opencode/opencode.json." + '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 in terminal tabs and terminal chats. The updated native chat doesn't use these." + 'Used when OMP CLI is launched. Native chat does not use these.' ) } diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 3b699ee841da..d46e5dbc9dc6 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -6986,11 +6986,11 @@ "agentPermissionsInfo": "Agent permissions info", "agentPermissionsTooltip": "Doesn't apply to agents where you've overridden launch arguments.", "argumentsUse": { - "claude": "Used in terminal tabs and terminal chats. The updated native chat doesn't use these; put its settings in ~/.claude/settings.json.", - "codex": "Used in terminal tabs and terminal chats. The updated native chat doesn't use these; put its settings in ~/.codex/config.toml.", - "grok": "Used in terminal tabs and terminal chats. The updated native chat doesn't use these; put its settings in ~/.grok/config.toml.", - "opencode": "Used in terminal tabs and terminal chats. The updated native chat doesn't use these; put its settings in ~/.config/opencode/opencode.json.", - "omp": "Used in terminal tabs and terminal chats. The updated native chat doesn't use these." + "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", 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": "手动", From 6083afac9e3c7eb72e4cfc026ff2068d98a4e542 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Thu, 8 Oct 2026 02:51:09 -0700 Subject: [PATCH 7/7] refactor(claude): drop the resume check and value a new-conversation-only start Fast no longer needs --- .../claude-structured-session-acquisition.ts | 1 - .../claude-structured-session-startup.ts | 7 ++---- .../claude-structured-start-fast-mode.ts | 24 +++++++++---------- 3 files changed, 13 insertions(+), 19 deletions(-) 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-start-fast-mode.ts b/src/main/claude/claude-structured-start-fast-mode.ts index 56d5839bf54f..83cd4ae98b12 100644 --- a/src/main/claude/claude-structured-start-fast-mode.ts +++ b/src/main/claude/claude-structured-start-fast-mode.ts @@ -10,18 +10,17 @@ import type { ClaudeSession } from './claude-structured-session-state' /** 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. The value still to be applied, or null. */ + * 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 (!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) }) @@ -34,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) { @@ -62,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' } }) } } }