mirror of
https://github.com/stablyai/orca.git
synced 2026-10-29 00:03:37 +00:00
fix(codex): restart account switches without blanking or repinning the pane Integrate the atomic pane replacement from #24350 and adapt the explicit conversation resume from #14877. Keep terminal protocol replies flowing while account notices block user input. Ordinary restores retain provenance. Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Co-authored-by: itisvincent <vincentcgamer@gmail.com> Co-authored-by: rayim <rayim@fxy.global> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
132 lines
6.4 KiB
TypeScript
132 lines
6.4 KiB
TypeScript
import { app } from 'electron'
|
|
import type { AgentProviderSessionMetadata } from '../../shared/agent-session-resume'
|
|
import type { CodexAccountSelectionTarget } from '../codex-accounts/runtime-selection'
|
|
import type { CodexSessionResumePreparation } from '../codex/codex-session-resume-home'
|
|
import { prepareCodexSessionResume } from '../codex/codex-session-resume-preparation'
|
|
import {
|
|
prepareCodexAccountRestartResume,
|
|
prepareLegacySharedCodexSessionResume
|
|
} from '../codex/codex-legacy-session-resume'
|
|
import { ManagedCodexHomeTemporarilyUnavailableError } from '../codex-accounts/host-codex-managed-home-ownership'
|
|
import { codexHookService } from '../codex/hook-service'
|
|
import {
|
|
awaitRealHomeCodexHookTrust,
|
|
ensureRealHomeCodexHookState
|
|
} from '../codex/codex-real-home-hook-install'
|
|
import { ensureCodexDaemonSocketGuard } from '../codex/codex-config-mirror'
|
|
import { isAgentStatusHooksEnabledForAgent } from '../agent-hooks/managed-agent-hook-controls'
|
|
import { getOrcaManagedCodexHomePath, getSystemCodexHomePath } from '../codex/codex-home-paths'
|
|
import { normalizeRuntimePathForComparison } from '../../shared/cross-platform-path'
|
|
import { mainProcessState as state } from './main-process-state'
|
|
|
|
export async function prepareCodexSessionResumeForLaunch(args: {
|
|
providerSession: AgentProviderSessionMetadata
|
|
target: CodexAccountSelectionTarget
|
|
launchEnv?: NodeJS.ProcessEnv
|
|
useSelectedAccount?: boolean
|
|
}): Promise<CodexSessionResumePreparation | null> {
|
|
const runtimeHome = state.codexRuntimeHome
|
|
const store = state.store
|
|
if (args.target.runtime === 'wsl' || !runtimeHome || !store) {
|
|
return null
|
|
}
|
|
const systemHomePath = getSystemCodexHomePath()
|
|
// Why: codexSessionSourceHome is import-only; treating it as CODEX_HOME would mutate history sources and bypass account auth.
|
|
const trustedHomes = [systemHomePath, ...runtimeHome.getHostCodexHomePathsForSessionDiscovery()]
|
|
// Why: resolved eagerly, once, before any ranking or provenance match. The
|
|
// marker read used to be deferred into the ranking thunk so a
|
|
// provenance-present resume never paid for it, but that optimisation let an
|
|
// unreadable selected home reach the PTY as "no selection": the provenance
|
|
// branch simply omits the account from `trustedHomes` and another account's
|
|
// readable alias wins. A throw here refuses the whole resume instead
|
|
// (#STA-4422).
|
|
const selectedAccountCodexHome = runtimeHome.resolveSelectedHostAccountCodexHomePathForResume()
|
|
// Why: a `fresh` outcome must skip migration and hook repair entirely — there is
|
|
// no verified origin home to prepare, so the PTY layer drops the resume argv (#10793).
|
|
const preparation = await prepareCodexSessionResume({
|
|
sessionId: args.providerSession.id,
|
|
transcriptPath: args.providerSession.transcriptPath,
|
|
trustedCodexHomes: trustedHomes,
|
|
// Why: the legacy id rescan's winning home becomes this pane's CODEX_HOME, i.e. its account;
|
|
// rank it by the current selection so settings insertion order can never decide the account.
|
|
getSelectedAccountCodexHome: () => selectedAccountCodexHome,
|
|
systemCodexHomePath: systemHomePath,
|
|
// Why: the mirror winning is what triggers the migration into ~/.codex below, so it must
|
|
// outrank the path-sorted account homes or a system-default selection resumes as an account.
|
|
sharedRuntimeCodexHomePath: getOrcaManagedCodexHomePath(),
|
|
resolveVerifiedResumeHome: async (sessionSource) => {
|
|
let migrated = { useRealCodexHome: false }
|
|
try {
|
|
migrated = await prepareLegacySharedCodexSessionResume(
|
|
{
|
|
agent: 'codex',
|
|
executionHostId: 'local',
|
|
filePath: sessionSource.transcriptPath,
|
|
codexHome: sessionSource.homePath
|
|
},
|
|
{
|
|
isHostSystemDefaultRealHome: () => runtimeHome.isHostSystemDefaultRealHome(),
|
|
systemCodexHomePath: systemHomePath
|
|
}
|
|
)
|
|
} catch (error) {
|
|
// A credential-read refusal must never fall back to the old account.
|
|
if (error instanceof ManagedCodexHomeTemporarilyUnavailableError) {
|
|
throw error
|
|
}
|
|
// Why: migration is a compatibility repair; its failure must not prevent the PTY from resuming from its trusted origin home.
|
|
console.warn(
|
|
'[codex-session-resume] Legacy rollout migration failed; using origin home:',
|
|
error
|
|
)
|
|
}
|
|
const resumeHome = args.useSelectedAccount
|
|
? await prepareCodexAccountRestartResume({
|
|
sourceHome: sessionSource.homePath,
|
|
transcriptPath: sessionSource.transcriptPath,
|
|
targetHome: selectedAccountCodexHome ?? systemHomePath,
|
|
systemCodexHomePath: systemHomePath
|
|
})
|
|
: migrated.useRealCodexHome
|
|
? systemHomePath
|
|
: sessionSource.homePath
|
|
const isSystemHome =
|
|
normalizeRuntimePathForComparison(resumeHome) ===
|
|
normalizeRuntimePathForComparison(systemHomePath)
|
|
const hooksEnabled = isAgentStatusHooksEnabledForAgent(store.getSettings(), 'codex')
|
|
try {
|
|
if (isSystemHome) {
|
|
await ensureRealHomeCodexHookState({
|
|
hooksEnabled,
|
|
userDataPath: app.getPath('userData'),
|
|
writePolicy: 'add-missing-only'
|
|
})
|
|
// Why: beside an unapproved entry Codex opens a blocking hook-review screen,
|
|
// and only the grant's own settle cannot race Codex's approval write.
|
|
await awaitRealHomeCodexHookTrust()
|
|
} else if (hooksEnabled) {
|
|
await codexHookService.installForLaunchPrep(resumeHome)
|
|
} else {
|
|
await codexHookService.refreshRuntimeUserHooksForLaunchPrep(resumeHome)
|
|
}
|
|
} catch (error) {
|
|
// Why: hook repair is best-effort; session provenance must still win over the currently selected home.
|
|
console.warn('[codex-hook-service] failed to prepare automatic resume home:', error)
|
|
}
|
|
if (!isSystemHome) {
|
|
// Why: this pins the resumed pane's CODEX_HOME, and hook repair above can skip or fail before its config mirror applies the daemon guard.
|
|
ensureCodexDaemonSocketGuard(resumeHome)
|
|
}
|
|
return resumeHome
|
|
}
|
|
})
|
|
return preparation.outcome === 'resume'
|
|
? {
|
|
...preparation,
|
|
reconcileSharedRuntimeAuth:
|
|
normalizeRuntimePathForComparison(preparation.codexHomePath) ===
|
|
normalizeRuntimePathForComparison(getOrcaManagedCodexHomePath())
|
|
}
|
|
: preparation
|
|
}
|