mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 09:27:29 +00:00
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 80a9ed69f0)
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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<void> {
|
||||
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({
|
||||
// <home>/sessions/<yyyy>/<mm>/<dd>/rollout-*.jsonl
|
||||
sourceHome: resolve(transcriptPath, '../../../../..'),
|
||||
transcriptPath,
|
||||
targetHome: args.toHome,
|
||||
systemCodexHomePath: getSystemCodexHomePath()
|
||||
})
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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[]> | string[]
|
||||
/** The selected account's home, prepared for this start in `launchDirectory`; null keeps the record's. */
|
||||
prepareLaunchAccountHome?: (
|
||||
record: AgentSessionRecord,
|
||||
launchDirectory: string
|
||||
) => Promise<AgentSessionAccountHome | null>
|
||||
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<CodexStructuredLaunch, 'codexHome'>): {
|
||||
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<CodexStructuredLaunch> {
|
||||
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,
|
||||
|
||||
@@ -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<string, CodexSession>,
|
||||
readInvocation: () => CodexProviderInvocationDeps | undefined
|
||||
): NonNullable<StructuredAgentSessionAdapter['readAccountUsage']> {
|
||||
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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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<void>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 } : {})
|
||||
})
|
||||
),
|
||||
|
||||
+6
@@ -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<AgentSessionAccountHome | null>
|
||||
} & Pick<StructuredAgentSessionHostDeps, 'onRequestFailure' | 'statusSink'>
|
||||
|
||||
+5
-2
@@ -82,10 +82,13 @@ export const STRUCTURED_AGENT_SESSION_ACCOUNT_USAGE_METHODS = [
|
||||
params: OptionsParams,
|
||||
handler: async ({ sessionId }, ctx): Promise<AgentSessionAccountUsage> => {
|
||||
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<typeof setTimeout> | undefined
|
||||
try {
|
||||
|
||||
@@ -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<AgentSessionAccountHome | null> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user