mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(codex): resume a thread with the permissions it was last given, not Orca's launch flag (#33460)
* fix(codex): resume a thread with the permissions it was last given, not Orca's launch flag Codex restores a resumed thread's saved approval/sandbox settings unless the launch passes a permission flag. Orca replayed its launch-time recipe (--dangerously-bypass-approvals-and-sandbox, --approve-for-me, or the Ask flags) on every resume, so a session switched to "Approve for me" came back as Full Access after a restart, and that override was saved to the thread. Fixes #10886 Co-authored-by: mys <4546173+wowpotato@users.noreply.github.com> * test(mobile): Codex AI Vault resume no longer replays Orca's permission flag The mobile AI Vault resume builds its command through the shared resume plan, so it now omits --dangerously-bypass-approvals-and-sandbox like desktop does. Co-authored-by: mys <4546173+wowpotato@users.noreply.github.com> --------- Co-authored-by: mys <4546173+wowpotato@users.noreply.github.com>
This commit is contained in:
@@ -70,7 +70,7 @@ describe('prepareMobileAiVaultSessionResume', () => {
|
||||
'terminal.send',
|
||||
{
|
||||
terminal: 'pty-1',
|
||||
text: `cd '/Users/ada/repo' && CODEX_HOME='${LEGACY_CODEX_HOME}' codex '--dangerously-bypass-approvals-and-sandbox' 'resume' 'legacy-1'`,
|
||||
text: `cd '/Users/ada/repo' && CODEX_HOME='${LEGACY_CODEX_HOME}' codex 'resume' 'legacy-1'`,
|
||||
enter: true
|
||||
},
|
||||
{ timeoutMs: RESUME_RPC_TIMEOUT_MS }
|
||||
@@ -98,9 +98,7 @@ describe('prepareMobileAiVaultSessionResume', () => {
|
||||
{ timeoutMs: RESUME_RPC_TIMEOUT_MS }
|
||||
)
|
||||
expect(prepared).toEqual({ ...legacy, codexHome: null })
|
||||
expect(launch.command).toBe(
|
||||
"cd '/Users/ada/repo' && codex '--dangerously-bypass-approvals-and-sandbox' 'resume' 'legacy-1'"
|
||||
)
|
||||
expect(launch.command).toBe("cd '/Users/ada/repo' && codex 'resume' 'legacy-1'")
|
||||
expect(launch.envToDelete).toEqual(['CODEX_HOME', 'ORCA_CODEX_HOME'])
|
||||
})
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ export function launchSleepingAgentSession(
|
||||
: {})
|
||||
},
|
||||
launchAgent: record.agent,
|
||||
...(launchConfig ? { agentArgsOverride: launchConfig.agentArgs } : {}),
|
||||
...(launchConfig ? { agentArgsOverride: startupPlan.launchConfig.agentArgs } : {}),
|
||||
...(startupPlan.startupCommandDelivery
|
||||
? { startupCommandDelivery: startupPlan.startupCommandDelivery }
|
||||
: {}),
|
||||
|
||||
@@ -50,14 +50,56 @@ describe('automatic terminal permissions', () => {
|
||||
shell,
|
||||
isRemote: true
|
||||
})
|
||||
if (agent === 'codex') {
|
||||
// Codex restores the thread's own saved permissions on resume.
|
||||
expect(resumed?.launchConfig.agentArgs).toBe('')
|
||||
expect(tokenizeStartupCommand(resumed?.launchCommand ?? '', shell)).toMatchObject({
|
||||
ok: true,
|
||||
tokens: ['codex', 'resume', 'saved-session']
|
||||
})
|
||||
continue
|
||||
}
|
||||
expect(resumed?.launchConfig.agentArgs).toBe(agentArgs)
|
||||
expect(tokenizeStartupCommand(resumed?.launchCommand ?? '', shell)).toMatchObject({
|
||||
ok: true,
|
||||
tokens: expect.arrayContaining(
|
||||
agent === 'codex' ? ['--approve-for-me'] : ['--permission-mode', 'auto']
|
||||
)
|
||||
tokens: expect.arrayContaining(['--permission-mode', 'auto'])
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('codex resume permissions', () => {
|
||||
it.each(['posix', 'powershell', 'cmd'] as const)(
|
||||
"drops Orca's full-access flag from a recorded %s launch but keeps the user's args",
|
||||
(shell) => {
|
||||
const platform = shell === 'posix' ? 'linux' : 'win32'
|
||||
const startup = buildAgentStartupPlan({
|
||||
agent: 'codex',
|
||||
agentArgs: "--dangerously-bypass-approvals-and-sandbox -m gpt-5.5 --add-dir 'my dir'",
|
||||
prompt: '',
|
||||
cmdOverrides: { codex: 'my-codex' },
|
||||
platform,
|
||||
shell,
|
||||
allowEmptyPromptLaunch: true
|
||||
})
|
||||
const resume = (fork: boolean) =>
|
||||
buildAgentResumeStartupPlan({
|
||||
agent: 'codex',
|
||||
...startup?.launchConfig,
|
||||
providerSession: { key: 'session_id', id: 'saved-session' },
|
||||
cmdOverrides: {},
|
||||
platform,
|
||||
shell,
|
||||
fork
|
||||
})
|
||||
expect(tokenizeStartupCommand(resume(false)?.launchCommand ?? '', shell)).toMatchObject({
|
||||
ok: true,
|
||||
tokens: ['my-codex', '-m', 'gpt-5.5', '--add-dir', 'my dir', 'resume', 'saved-session']
|
||||
})
|
||||
expect(resume(false)?.launchConfig.agentArgs).toBe("-m gpt-5.5 --add-dir 'my dir'")
|
||||
// A fork starts a new thread, so it keeps the recorded launch's permission.
|
||||
expect(resume(true)?.launchCommand).toContain('--dangerously-bypass-approvals-and-sandbox')
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
type AgentPermissionChoice
|
||||
} from './tui-agent-permission-choice'
|
||||
import { permissionRecipeCut } from './agent-permission-args'
|
||||
import { planAgentCliArgsSuffix, type AgentStartupShell } from './tui-agent-startup-shell'
|
||||
export { agentArgsSetPermission } from './agent-permission-args'
|
||||
import type { TuiAgent } from './tui-agent'
|
||||
|
||||
@@ -33,6 +34,29 @@ export function liftAgentPermissionArgs(
|
||||
return { choice: null, args: original }
|
||||
}
|
||||
|
||||
/** A recorded launch without Orca's permission flag; recorded text it cannot rebuild stays whole. */
|
||||
export function dropRecordedAgentPermissionArgs(
|
||||
agent: TuiAgent,
|
||||
recorded: { agentArgs?: string | null; agentCommand?: string | null },
|
||||
shell: AgentStartupShell
|
||||
): { agentArgs?: string | null; agentCommand?: string | null } {
|
||||
const lifted = liftAgentPermissionArgs(agent, recorded.agentArgs)
|
||||
if (!lifted.choice) {
|
||||
return recorded
|
||||
}
|
||||
const command = recorded.agentCommand?.trim()
|
||||
if (!command) {
|
||||
return { ...recorded, agentArgs: lifted.args }
|
||||
}
|
||||
const before = planAgentCliArgsSuffix(recorded.agentArgs, shell)
|
||||
const after = planAgentCliArgsSuffix(lifted.args, shell)
|
||||
if (!before.ok || !after.ok || !command.endsWith(` ${before.suffix}`)) {
|
||||
return recorded
|
||||
}
|
||||
const head = command.slice(0, -before.suffix.length).trimEnd()
|
||||
return { agentArgs: lifted.args, agentCommand: after.suffix ? `${head} ${after.suffix}` : head }
|
||||
}
|
||||
|
||||
/** Lift env bypass with execution-host name casing; unknown browser caches retain ambiguous spellings. */
|
||||
export function liftAgentPermissionEnv(
|
||||
agent: TuiAgent,
|
||||
|
||||
@@ -15,6 +15,7 @@ import { buildAgentResumeLaunchCommand } from './agent-resume-launch-command'
|
||||
import { buildAiVaultResumeShellCommand } from './ai-vault-resume-command'
|
||||
import { normalizeRuntimePathForComparison } from './cross-platform-path'
|
||||
import { parseWslUncPath } from './wsl-paths'
|
||||
import { dropRecordedAgentPermissionArgs } from './tui-agent-permission-lift'
|
||||
|
||||
export function buildAgentResumeStartupPlan(args: {
|
||||
agent: ResumableTuiAgent
|
||||
@@ -48,7 +49,13 @@ export function buildAgentResumeStartupPlan(args: {
|
||||
? [resumeArgv[0], '-c', 'tui.resume_cwd=current', ...resumeArgv.slice(1)]
|
||||
: resumeArgv
|
||||
const shell = resolveStartupShell(args.platform, args.shell)
|
||||
const resolvedAgentCommand = args.agentCommand?.trim()
|
||||
// Why: Codex restores a resumed thread's saved permissions unless the launch passes one, so
|
||||
// replaying Orca's launch-time flag would undo a change made inside the session (#10886).
|
||||
const recorded =
|
||||
args.agent === 'codex' && !args.fork
|
||||
? dropRecordedAgentPermissionArgs(args.agent, args, shell)
|
||||
: args
|
||||
const resolvedAgentCommand = recorded.agentCommand?.trim()
|
||||
const baseCommand = resolvedAgentCommand
|
||||
? ({
|
||||
ok: true,
|
||||
@@ -61,7 +68,7 @@ export function buildAgentResumeStartupPlan(args: {
|
||||
cmdOverrides: args.cmdOverrides,
|
||||
platform: args.platform,
|
||||
shell,
|
||||
agentArgs: args.agentArgs,
|
||||
agentArgs: recorded.agentArgs,
|
||||
sessionOptions: args.sessionOptions,
|
||||
sessionOptionsOverrideAgentArgs: args.sessionOptionsOverrideAgentArgs,
|
||||
isRemote: args.isRemote
|
||||
@@ -71,6 +78,7 @@ export function buildAgentResumeStartupPlan(args: {
|
||||
}
|
||||
const launchConfig = buildSleepingAgentLaunchConfig({
|
||||
...args,
|
||||
agentArgs: recorded.agentArgs,
|
||||
agentCommand: baseCommand.commandWithoutSessionOptions
|
||||
})
|
||||
const resumeCommand = buildAgentResumeLaunchCommand(args.agent, baseCommand.command, argv, shell)
|
||||
|
||||
Reference in New Issue
Block a user