From 86f5ea3781bef4cdc110856e33eec0ddc214d22c Mon Sep 17 00:00:00 2001 From: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com> Date: Mon, 9 Nov 2026 05:15:37 +0000 Subject: [PATCH] fix(native-chat): move open Codex chats to the account you switch to (#40148) A Codex chat launched codex app-server on the account home saved when the chat was created, and nothing compared it with the selection, so after a switch every message kept going to the old account. A chat that had hit its usage limit kept failing with "Codex hit its usage limit" even though the new account had quota. Codex chats now follow the selection the way Claude chats do: the launch prepares the selected account's home, the started process reports that home, and the host replaces an idle chat whose account no longer matches before its next message. Codex resumes a thread only from a rollout in its own CODEX_HOME, so the launch links the thread's rollout into the new home first. The chat's usage card reads the account its next turn runs on. (cherry picked from commit 80a9ed69f05550664a34baea7d57c1337be668b7) --- .../content/docs/agents/codex-hot-swap.mdx | 6 +- docs/site/content/docs/agents/codex.mdx | 2 +- .../resume/codex-thread-account-move.ts | 39 ++++++++++++ .../codex-structured-session-acquire.ts | 3 +- ...codex-structured-launch-resolution.test.ts | 60 +++++++++++++++++++ .../codex-structured-launch-resolution.ts | 55 ++++++++++++++--- .../codex-session-account-usage.ts | 18 ++++-- .../codex-structured-session-start.ts | 1 + .../codex-structured-session-state.ts | 2 + .../runtime-structured-agent-host.ts | 8 +++ .../structured-agent-runtime-metadata.ts | 1 + .../structured-agent-runtime-registrations.ts | 3 + ...ructured-agent-session-runtime-contract.ts | 6 ++ .../structured-agent-session-account-usage.ts | 7 ++- .../structured-session-account-home.ts | 15 +++++ .../workspace-process-listing.ts | 2 + 16 files changed, 207 insertions(+), 21 deletions(-) create mode 100644 src/main/codex/history/resume/codex-thread-account-move.ts diff --git a/docs/site/content/docs/agents/codex-hot-swap.mdx b/docs/site/content/docs/agents/codex-hot-swap.mdx index f80db35dd4a..bc66a569409 100644 --- a/docs/site/content/docs/agents/codex-hot-swap.mdx +++ b/docs/site/content/docs/agents/codex-hot-swap.mdx @@ -14,11 +14,11 @@ Accounts are optional: without any, Orca uses your normal Codex login (**System ## Swap accounts -Click the Codex chip in the status bar to open the account switcher, or pick an account in Settings. Any new Codex session launched after that uses it. Sessions already running keep their original account until restarted. +Click the Codex chip in the status bar to open the account switcher, or pick an account in Settings. Any new Codex session launched after that uses it, and open Codex chats move to it before their next message (a reply already running finishes first). Terminal sessions already running keep their original account until restarted. ## Running accounts side by side -Because each added account has its own isolated home, two Codex sessions can run under different accounts at the same time: start one, switch accounts, start the next. Each session keeps the account it launched with. Usage still counts against each account's own plan; Orca does not pool quota across accounts. +Because each added account has its own isolated home, two Codex terminal sessions can run under different accounts at the same time: start one, switch accounts, start the next. Each terminal session keeps the account it launched with; chats always follow the selected account. Usage still counts against each account's own plan; Orca does not pool quota across accounts. ## System default @@ -31,7 +31,7 @@ For managed Codex accounts, Orca mirrors settings from your real `~/.codex/confi ## Rules & gotchas - Swapping does not re-authenticate; Orca launches new sessions in the selected account's home. -- Existing Codex processes keep their current account until restart. +- Existing Codex terminal processes keep their current account until restart. Chats switch on their next message. - Usage readouts in the status bar follow the currently-active account. - The restart chip preserves the active account at the time of restart. - Accounts belong to the machine that runs the agent. On a [Remote Orca Server](/docs/remote-servers), add them on that server. diff --git a/docs/site/content/docs/agents/codex.mdx b/docs/site/content/docs/agents/codex.mdx index 0cad8eef860..91175ac7d7b 100644 --- a/docs/site/content/docs/agents/codex.mdx +++ b/docs/site/content/docs/agents/codex.mdx @@ -20,7 +20,7 @@ Many Codex users run multiple accounts to stretch rate limits. Orca's account sw ## System default vs extra accounts -**System default** uses your real `~/.codex` login (the same home a bare `codex` outside Orca would use). Extra accounts Orca manages get their own home under Orca's account data so credentials and rollouts stay isolated. New Codex launches follow the active account; already-running sessions keep the home they started with until you restart them. +**System default** uses your real `~/.codex` login (the same home a bare `codex` outside Orca would use). Extra accounts Orca manages get their own home under Orca's account data so credentials and rollouts stay isolated. New Codex launches follow the active account. Open Codex chats move to it before their next message; already-running terminal sessions keep the home they started with until you restart them. ## Wrapper scripts that set `CODEX_HOME` diff --git a/src/main/codex/history/resume/codex-thread-account-move.ts b/src/main/codex/history/resume/codex-thread-account-move.ts new file mode 100644 index 00000000000..a1fab6194f2 --- /dev/null +++ b/src/main/codex/history/resume/codex-thread-account-move.ts @@ -0,0 +1,39 @@ +import { realpath } from 'node:fs/promises' +import { join, resolve } from 'node:path' +import { resolveSessionFilePath } from '../../../native-chat/session-file-resolver' +import { getSystemCodexHomePath } from '../../configuration/homes/codex-home-paths' +import { prepareCodexAccountRestartResume } from './codex-legacy-session-resume' + +/** Codex resumes a thread only from a rollout in its own CODEX_HOME, and the background history + * bridge may not have linked it yet, so a chat moving accounts brings its rollout along first. */ +export async function moveCodexThreadIntoAccountHome(args: { + threadId: string + /** Where the chat last ran; searched before every other known Codex home. */ + fromHome: string + toHome: string +}): Promise { + const inTarget = await resolveSessionFilePath('codex', args.threadId, { + codexSessionsDirs: [join(args.toHome, 'sessions')], + strictErrors: true + }) + if (inTarget) { + return + } + const found = + (await resolveSessionFilePath('codex', args.threadId, { + codexSessionsDirs: [join(args.fromHome, 'sessions')] + })) ?? (await resolveSessionFilePath('codex', args.threadId)) + if (!found) { + // Never saved; Codex's own resume refusal decides what happens next. + return + } + // A bridged symlink names its source; the file itself is what gets linked or copied. + const transcriptPath = await realpath(found) + await prepareCodexAccountRestartResume({ + // /sessions///
/rollout-*.jsonl + sourceHome: resolve(transcriptPath, '../../../../..'), + transcriptPath, + targetHome: args.toHome, + systemCodexHomePath: getSystemCodexHomePath() + }) +} diff --git a/src/main/codex/structured/acquisition/codex-structured-session-acquire.ts b/src/main/codex/structured/acquisition/codex-structured-session-acquire.ts index c92d65c839a..f10fe2d6025 100644 --- a/src/main/codex/structured/acquisition/codex-structured-session-acquire.ts +++ b/src/main/codex/structured/acquisition/codex-structured-session-acquire.ts @@ -1,4 +1,5 @@ import type { AgentSessionAccountKind } from '../../../../shared/agent-session/agent-session-availability' +import { codexLaunchedAccountHome } from '../launch/codex-structured-launch-resolution' import { CODEX_STRUCTURED_HANDLE_NAMESPACE, isAgentSessionProviderHandleInNamespace @@ -279,7 +280,7 @@ export async function acquireCodexStructuredSession(input: { }, openInput ) - return { process, acquisitionGeneration } + return { process, acquisitionGeneration, ...codexLaunchedAccountHome(launch) } } catch (error) { acquisitions.endings.retire(sessionId, acquisitionGeneration) if (sessions.get(sessionId)?.connection !== acquisition.connection) { diff --git a/src/main/codex/structured/launch/codex-structured-launch-resolution.test.ts b/src/main/codex/structured/launch/codex-structured-launch-resolution.test.ts index 7d0d2922f4d..a600516db87 100644 --- a/src/main/codex/structured/launch/codex-structured-launch-resolution.test.ts +++ b/src/main/codex/structured/launch/codex-structured-launch-resolution.test.ts @@ -1,3 +1,6 @@ +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' import { agentSessionRecordFixture } from '../../../native-chat/agent-session-record-test-fixture' import { createCodexStructuredLaunchResolver } from '../../../runtime/agent-session/launch/structured-agent-launch-composition.test-support' import { describe, expect, it, vi } from 'vitest' @@ -128,4 +131,61 @@ describe('codex structured launch resolution', () => { })({ identity: IDENTITY }) ).rejects.toThrow('workspace-1 is gone') }) + + it('resumes a chat on the account selected after it was created, bringing its thread along', async () => { + const root = mkdtempSync(join(tmpdir(), 'orca-codex-chat-account-move-')) + vi.stubEnv('ORCA_USER_DATA_PATH', join(root, 'user-data')) + try { + const threadId = '019a1a83-f690-7c82-a482-31195524a7d8' + const rollout = join( + 'sessions', + '2026', + '11', + '04', + `rollout-2026-11-04T20-28-46-${threadId}.jsonl` + ) + const oldHome = join(root, 'account-a') + const newHome = join(root, 'account-b') + mkdirSync(join(oldHome, 'sessions', '2026', '11', '04'), { recursive: true }) + writeFileSync(join(oldHome, rollout), '{"type":"session_meta"}\n') + const prepareLaunchAccountHome = vi.fn(async () => ({ + variable: 'CODEX_HOME' as const, + path: newHome + })) + const launch = await createCodexStructuredLaunchResolver({ + store: { + getRecord: () => + record({ + accountHome: { variable: 'CODEX_HOME', path: oldHome }, + providerHandleChain: [ + { + linkId: 'link-1', + handle: codexProviderHandle(threadId), + origin: 'created', + mintedAtFence: 1, + observedAt: 1 + } + ] + }), + pinLaunchDirectory: vi.fn() + }, + resolveWorkspacePath: async (id) => `/repos/${id}`, + resolveCommand: () => ({ command: '/usr/local/bin/codex', prefixArgs: [] }), + resolveLaunchArgs: () => [], + prepareLaunchAccountHome + })({ identity: IDENTITY }) + + expect(prepareLaunchAccountHome).toHaveBeenCalledWith( + expect.objectContaining({ sessionId: SESSION_ID }), + '/repos/workspace-1' + ) + expect(launch.codexHome).toBe(newHome) + expect(launch.env.CODEX_HOME).toBe(newHome) + expect(launch.resumeThreadId).toBe(threadId) + expect(readFileSync(join(newHome, rollout), 'utf8')).toBe('{"type":"session_meta"}\n') + } finally { + vi.unstubAllEnvs() + rmSync(root, { recursive: true, force: true }) + } + }) }) diff --git a/src/main/codex/structured/launch/codex-structured-launch-resolution.ts b/src/main/codex/structured/launch/codex-structured-launch-resolution.ts index ad0943d4415..82aeeae47b2 100644 --- a/src/main/codex/structured/launch/codex-structured-launch-resolution.ts +++ b/src/main/codex/structured/launch/codex-structured-launch-resolution.ts @@ -8,15 +8,23 @@ import { } from '../../../native-chat/structured-agent-command-resolution' // How a durable session record becomes a Codex process launch. // -// Every input is read back from the record the store already made durable, not -// from the call that triggered spawn. A client that attaches twice must -// land in the same working directory under the same account home, and a resume -// must name the thread this session actually proved — never one a caller asks -// for, which is how a resume becomes a fork wearing a resume's name. +// Every input except the account is read back from the record the store already made durable, +// not from the call that triggered spawn. A client that attaches twice must land in the same +// working directory, and a resume must name the thread this session actually proved — never one +// a caller asks for, which is how a resume becomes a fork wearing a resume's name. The account +// follows the selection, as Claude chats do. -import { requireLegacyAgentSessionAccountHome } from '../../../../shared/agent-session/lifecycle/agent-session-account-home' +import { + agentSessionAccountHome, + agentSessionAccountHomesEqual, + requireLegacyAgentSessionAccountHome, + type AgentSessionAccountHome +} from '../../../../shared/agent-session/lifecycle/agent-session-account-home' +import type { AgentSessionRecord } from '../../../../shared/agent-session/lifecycle/agent-session-record' +import { moveCodexThreadIntoAccountHome } from '../../history/resume/codex-thread-account-move' import { agentSessionProviderHandleChainHead } from '../../../../shared/agent-session/lifecycle/agent-session-provider-handle' import { resolveCodexCommand } from '../../../codex-cli/command' +import { CODEX_STRUCTURED_AGENT } from './codex-structured-agent-definition' import type { StructuredAgentLaunchPartResolver } from '../../../runtime/agent-session/launch/structured-agent-launch-composition' import type { CodexStructuredLaunch } from '../session-lifecycle/codex-structured-session-state' import { codexStructuredPermissionPolicy } from '../../../../shared/codex-permission-policy' @@ -29,6 +37,11 @@ import { export type CodexStructuredLaunchPartDeps = { resolveLaunchArgs: () => Promise | string[] + /** The selected account's home, prepared for this start in `launchDirectory`; null keeps the record's. */ + prepareLaunchAccountHome?: ( + record: AgentSessionRecord, + launchDirectory: string + ) => Promise resolveCommand?: (options?: { pathEnv?: string | null homePath?: string @@ -98,13 +111,22 @@ async function readCodexStructuredInvocation( return { ...invocation, environment } } -/** Derive resume identity and account home from the durable record. */ +/** The host compares this with the selection to move an idle chat to a newly picked account. */ +export function codexLaunchedAccountHome(launch: Pick): { + accountHome?: AgentSessionAccountHome +} { + return launch.codexHome + ? { accountHome: agentSessionAccountHome(CODEX_STRUCTURED_AGENT, launch.codexHome) } + : {} +} + +/** Derive resume identity from the durable record and the account from the selection. */ export function codexStructuredLaunchPart( deps: CodexStructuredLaunchPartDeps ): StructuredAgentLaunchPartResolver { return async (basis) => { const { record } = basis - const accountHome = requireLegacyAgentSessionAccountHome(record.accountHome) + const recordHome = requireLegacyAgentSessionAccountHome(record.accountHome) const { command, prefixArgs } = await resolveCodexStructuredInvocation({ ...(deps.resolveCommand ? { resolveCommand: deps.resolveCommand } : {}), refreshEnvironmentAfterMiss: basis.refreshAfterMissingProgram, @@ -118,7 +140,22 @@ export function codexStructuredLaunchPart( // Match the first turn's model to avoid duplicate prompt injection on a model switch. const model = record.options?.model const visuals = await basis.visuals() - await basis.launchDirectory() + const launchDirectory = await basis.launchDirectory() + const selectedHome = await deps.prepareLaunchAccountHome?.(record, launchDirectory) + const accountHome = selectedHome + ? requireLegacyAgentSessionAccountHome(selectedHome) + : recordHome + if ( + resumeThreadId && + selectedHome && + !agentSessionAccountHomesEqual(selectedHome, record.accountHome) + ) { + await moveCodexThreadIntoAccountHome({ + threadId: resumeThreadId, + fromHome: recordHome.path, + toHome: accountHome.path + }) + } return { launch: { command, diff --git a/src/main/codex/structured/session-lifecycle/codex-session-account-usage.ts b/src/main/codex/structured/session-lifecycle/codex-session-account-usage.ts index 2e0890b5b78..70cc65af869 100644 --- a/src/main/codex/structured/session-lifecycle/codex-session-account-usage.ts +++ b/src/main/codex/structured/session-lifecycle/codex-session-account-usage.ts @@ -1,5 +1,6 @@ import { requireLegacyAgentSessionAccountHome } from '../../../../shared/agent-session/lifecycle/agent-session-account-home' import type { CodexAppServerRpc } from '../../app-server/codex-app-server-session' +import { normalizeRuntimePathForComparison } from '../../../../shared/cross-platform-path' import { resolveCodexHomeProcessLockKey, withCodexHomeProcessLock @@ -34,15 +35,23 @@ async function readUsage( return { account, rateLimits: mapCodexRpcRateLimits((limits ?? {}) as RpcRateLimitsResponse) } } -/** Live chats read on their own connection; a chat at rest reads its pinned home through a - * short-lived app-server launched exactly as the chat is, without resuming the thread. */ +/** A chat running on `record.accountHome` reads on its own connection; otherwise that home is read + * through a short-lived app-server launched as the chat is, without resuming the thread. */ export function readCodexSessionAccountUsage( sessions: Map, readInvocation: () => CodexProviderInvocationDeps | undefined ): NonNullable { return async (record, signal) => { - const connection = sessions.get(record.sessionId)?.connection - if (connection && !connection.closed) { + const codexHome = requireLegacyAgentSessionAccountHome(record.accountHome).path + const live = sessions.get(record.sessionId) + const connection = live?.connection + if ( + connection && + !connection.closed && + live.codexHome && + normalizeRuntimePathForComparison(live.codexHome) === + normalizeRuntimePathForComparison(codexHome) + ) { const usage = await readUsage((method, params, options) => connection.request(method, params, options) ) @@ -52,7 +61,6 @@ export function readCodexSessionAccountUsage( if (!invocation) { throw new Error('Codex usage invocation is not configured') } - const codexHome = requireLegacyAgentSessionAccountHome(record.accountHome).path return withCodexHomeProcessLock(resolveCodexHomeProcessLockKey(codexHome), async () => { signal.throwIfAborted() return runCodexProviderAppServer( diff --git a/src/main/codex/structured/session-lifecycle/codex-structured-session-start.ts b/src/main/codex/structured/session-lifecycle/codex-structured-session-start.ts index b8a6b1abbf5..c52d281af5c 100644 --- a/src/main/codex/structured/session-lifecycle/codex-structured-session-start.ts +++ b/src/main/codex/structured/session-lifecycle/codex-structured-session-start.ts @@ -160,6 +160,7 @@ export async function openCodexStructuredSession( const session: CodexSession = { events: input.events, account: start.account, + codexHome: launch.codexHome, connection, ...codexSessionLifecycle(input.fence, generation), threadId: opened.threadId, diff --git a/src/main/codex/structured/session-lifecycle/codex-structured-session-state.ts b/src/main/codex/structured/session-lifecycle/codex-structured-session-state.ts index a69141e0d75..6c7f91f90e3 100644 --- a/src/main/codex/structured/session-lifecycle/codex-structured-session-state.ts +++ b/src/main/codex/structured/session-lifecycle/codex-structured-session-state.ts @@ -136,6 +136,8 @@ export type CodexStructuredSessionAdapterDeps = { export type CodexSession = { events?: StructuredAgentSessionEventSink account?: AgentSessionAccountKind + /** CODEX_HOME this app-server launched under. */ + codexHome?: string | null connection: CodexAppServerConnection ended: boolean /** First observed child exit survives rejected settlement admission. */ diff --git a/src/main/runtime/agent-session/installation/runtime-structured-agent-host.ts b/src/main/runtime/agent-session/installation/runtime-structured-agent-host.ts index 4d6e5db49dd..faea7c1089d 100644 --- a/src/main/runtime/agent-session/installation/runtime-structured-agent-host.ts +++ b/src/main/runtime/agent-session/installation/runtime-structured-agent-host.ts @@ -126,6 +126,14 @@ export abstract class RuntimeStructuredAgentHost extends OrcaRuntimeWithStartTui OrcaRuntimeWithGetStructuredAgentSessionCreateSupport['resolveStructuredAgentLaunchAccountHome'] > + abstract prepareStructuredAgentLaunchAccountHome( + ...args: Parameters< + OrcaRuntimeWithGetStructuredAgentSessionCreateSupport['prepareStructuredAgentLaunchAccountHome'] + > + ): ReturnType< + OrcaRuntimeWithGetStructuredAgentSessionCreateSupport['prepareStructuredAgentLaunchAccountHome'] + > + protected abstract assertStructuredChatAcquireAdmissible( record: AgentSessionRecord ): Promise diff --git a/src/main/runtime/agent-session/installation/structured-agent-runtime-metadata.ts b/src/main/runtime/agent-session/installation/structured-agent-runtime-metadata.ts index a055c45b055..5ff06a3a982 100644 --- a/src/main/runtime/agent-session/installation/structured-agent-runtime-metadata.ts +++ b/src/main/runtime/agent-session/installation/structured-agent-runtime-metadata.ts @@ -148,6 +148,7 @@ export const STRUCTURED_AGENT_RUNTIME_CATALOG: readonly StructuredAgentRuntimeCa modelCatalog: codexModelCatalogDiscovery, ...CODEX_CLI_CONVERSATIONS, supportsLocation: (location) => supportsCodexStructuredLocation(location), + launchFollowsSelection: () => true, resolveAccountHome: async (request, services) => agentSessionAccountHome( CODEX_STRUCTURED_AGENT, diff --git a/src/main/runtime/agent-session/installation/structured-agent-runtime-registrations.ts b/src/main/runtime/agent-session/installation/structured-agent-runtime-registrations.ts index 776a9082770..7521c496186 100644 --- a/src/main/runtime/agent-session/installation/structured-agent-runtime-registrations.ts +++ b/src/main/runtime/agent-session/installation/structured-agent-runtime-registrations.ts @@ -64,6 +64,9 @@ function createCodexAdapter(context: StructuredAgentAdapterContext): StructuredA structuredAgentLaunchSources(context), codexStructuredLaunchPart({ resolveLaunchArgs: () => deps.resolveLaunchArgs('codex'), + ...(deps.prepareLaunchAccountHome + ? { prepareLaunchAccountHome: deps.prepareLaunchAccountHome } + : {}), ...(deps.resolveCodexCommand ? { resolveCommand: deps.resolveCodexCommand } : {}) }) ), diff --git a/src/main/runtime/agent-session/installation/structured-agent-session-runtime-contract.ts b/src/main/runtime/agent-session/installation/structured-agent-session-runtime-contract.ts index af59a6372cf..91fea923bff 100644 --- a/src/main/runtime/agent-session/installation/structured-agent-session-runtime-contract.ts +++ b/src/main/runtime/agent-session/installation/structured-agent-session-runtime-contract.ts @@ -2,6 +2,7 @@ import type { StructuredAgentSessionHostDeps } from '../../../native-chat/agent- import type { AgentSessionAccountKind } from '../../../../shared/agent-session/agent-session-availability' import type { StructuredAgentSessionTransportOverrides } from '../structured-agent-session-transport-overrides' import type { AgentSessionRecord } from '../../../../shared/agent-session/lifecycle/agent-session-record' +import type { AgentSessionAccountHome } from '../../../../shared/agent-session/lifecycle/agent-session-account-home' import type { StructuredAttentionMobileDelivery } from '../structured-agent-session-mobile-attention' import type { StructuredAgentCommand, @@ -86,4 +87,9 @@ export type StructuredAgentSessionRuntimeDeps = StructuredAgentSessionTransportO resolveAgentAccountHome?: RuntimeAgentAccountHomeResolver /** See `StructuredAgentSessionHostDeps.launchAccountHome`. */ launchAccountHome?: StructuredAgentSessionHostDeps['launchAccountHome'] + /** That account home, prepared for a start of `record` in `launchDirectory`. */ + prepareLaunchAccountHome?: ( + record: AgentSessionRecord, + launchDirectory: string + ) => Promise } & Pick diff --git a/src/main/runtime/rpc/methods/structured-sessions/catalog/structured-agent-session-account-usage.ts b/src/main/runtime/rpc/methods/structured-sessions/catalog/structured-agent-session-account-usage.ts index 17d2f3c8dec..3175a1a07dc 100644 --- a/src/main/runtime/rpc/methods/structured-sessions/catalog/structured-agent-session-account-usage.ts +++ b/src/main/runtime/rpc/methods/structured-sessions/catalog/structured-agent-session-account-usage.ts @@ -82,10 +82,13 @@ export const STRUCTURED_AGENT_SESSION_ACCOUNT_USAGE_METHODS = [ params: OptionsParams, handler: async ({ sessionId }, ctx): Promise => { const host = await requireInstalledStructuredHost(ctx, sessionId) - const record = host.deps.store.getRecord(sessionId) - if (!record || !supportsSessionAccountUsage(record)) { + const saved = host.deps.store.getRecord(sessionId) + if (!saved || !supportsSessionAccountUsage(saved)) { return { status: 'failed' } } + // The account the chat's next turn runs on, which a switch moves it to. + const accountHome = await host.deps.launchAccountHome?.(saved).catch(() => null) + const record = accountHome ? { ...saved, accountHome } : saved const abort = new AbortController() let timer: ReturnType | undefined try { diff --git a/src/main/runtime/session-tabs/structured-session-account-home.ts b/src/main/runtime/session-tabs/structured-session-account-home.ts index c81606bd977..c4afca46c29 100644 --- a/src/main/runtime/session-tabs/structured-session-account-home.ts +++ b/src/main/runtime/session-tabs/structured-session-account-home.ts @@ -69,4 +69,19 @@ export abstract class OrcaRuntimeWithStructuredSessionAccountHome extends OrcaRu const launchEnv = this.structuredAgentLaunchEnv(provider, this.requireStore().getSettings()) return (await read?.({ launchEnv, location })) ?? null } + + /** The same account, prepared for a start in `launchDirectory` as a create prepares it. */ + async prepareStructuredAgentLaunchAccountHome( + record: AgentSessionRecord, + launchDirectory: string + ): Promise { + const { provider, location } = record + if (!structuredAgentRuntimeMetadata(provider)?.launchFollowsSelection?.()) { + return null + } + const prepare = this.structuredAgentAccountHomeResolver(provider, '', 'launch', launchDirectory) + await this.accounts.prepareHostAgentLaunch(provider) + const launchEnv = this.structuredAgentLaunchEnv(provider, this.requireStore().getSettings()) + return (await prepare?.({ launchEnv, location })) ?? null + } } diff --git a/src/main/runtime/workspaces/process-listing/workspace-process-listing.ts b/src/main/runtime/workspaces/process-listing/workspace-process-listing.ts index 36045b65990..6f5424779f2 100644 --- a/src/main/runtime/workspaces/process-listing/workspace-process-listing.ts +++ b/src/main/runtime/workspaces/process-listing/workspace-process-listing.ts @@ -271,6 +271,8 @@ export abstract class OrcaRuntimeWithGetWorktreePs extends RuntimeChatWorkerEven preModeChatGetsFullAccess(agent, this.requireStore().getSettings(), process.platform), launchAccountHome: (record, purpose) => this.resolveStructuredAgentLaunchAccountHome(record, purpose), + prepareLaunchAccountHome: (record, launchDirectory) => + this.prepareStructuredAgentLaunchAccountHome(record, launchDirectory), onRequestFailure: (failure) => this.onStructuredRequestFailureForErrorMail(failure), // Structured chat has no CLI hooks; this projection drives first-work renaming. onSessionStatusChanged: (summary, options) => {