diff --git a/src/main/codex/codex-pane-account-registry.ts b/src/main/codex/codex-pane-account-registry.ts index 640dcd17506..359bc3e9e8f 100644 --- a/src/main/codex/codex-pane-account-registry.ts +++ b/src/main/codex/codex-pane-account-registry.ts @@ -256,6 +256,18 @@ export function hasRecordedLegacySharedCodexPane(): boolean { ) } +/** True when startup may need to repair hooks for a retained managed host pane. */ +export function hasRecordedManagedHostCodexPane(): boolean { + return Object.values(readRegistry().panes).some( + (record) => + record.selectionKey === 'host' && + (record.homeRoute === undefined || + record.homeRoute === 'shared-home' || + record.homeRoute === 'custom-home' || + (record.homeRoute === 'account-home' && record.accountId !== null)) + ) +} + /** Drops records whose daemon PTYs are authoritatively absent. */ export function reconcileCodexPaneAccountsWithLivePtys(livePtyIds: readonly string[]): void { const registry = readRegistry() diff --git a/src/main/codex/codex-stale-pane-accounts.test.ts b/src/main/codex/codex-stale-pane-accounts.test.ts index c5ee4802e5b..720f2101f81 100644 --- a/src/main/codex/codex-stale-pane-accounts.test.ts +++ b/src/main/codex/codex-stale-pane-accounts.test.ts @@ -8,6 +8,7 @@ import { forgetCodexPaneAccount, getCodexPaneAccount, hasRecordedLegacySharedCodexPane, + hasRecordedManagedHostCodexPane, reconcileCodexPaneAccountsWithLivePtys, recordCodexPaneAccount } from './codex-pane-account-registry' @@ -125,6 +126,40 @@ describe('codex pane account registry', () => { expect(hasRecordedLegacySharedCodexPane()).toBe(true) }) + it('requests startup inventory only for managed host panes', () => { + recordCodexPaneAccount('pty-real', { + selectionKey: 'host', + accountId: null, + homeRoute: 'real-home' + }) + recordCodexPaneAccount('pty-wsl', { + selectionKey: 'wsl:Ubuntu', + accountId: 'account-wsl', + homeRoute: 'account-home' + }) + + expect(hasRecordedManagedHostCodexPane()).toBe(false) + + recordCodexPaneAccount('pty-shared', { + selectionKey: 'host', + accountId: null, + homeRoute: 'shared-home' + }) + + expect(hasRecordedManagedHostCodexPane()).toBe(true) + + forgetCodexPaneAccount('pty-shared') + expect(hasRecordedManagedHostCodexPane()).toBe(false) + + recordCodexPaneAccount('pty-account', { + selectionKey: 'host', + accountId: 'account-host', + homeRoute: 'account-home' + }) + + expect(hasRecordedManagedHostCodexPane()).toBe(true) + }) + it('drops leaked records that are absent from the authoritative daemon inventory', () => { recordCodexPaneAccount('pty-live', { selectionKey: 'host', diff --git a/src/main/index.ts b/src/main/index.ts index 0812a078c13..55180cc9ff8 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -39,7 +39,7 @@ import { shutdownDaemon } from './daemon/daemon-init' import { - hasRecordedLegacySharedCodexPane, + hasRecordedManagedHostCodexPane, reconcileCodexPaneAccountsWithLivePtys } from './codex/codex-pane-account-registry' import { closeAllWatchers } from './ipc/filesystem-watcher' @@ -919,19 +919,19 @@ function startTerminalRuntimeStartupServices(): Promise { macosLoginSessionWatch: process.platform === 'darwin' && !isServeMode }) // Why: a retained shell keeps its launch-time Codex home even when the current routing lane changes. - const livePtyIds = await listLiveDaemonPtyIds() - if (livePtyIds && codexRuntimeHome) { - if (codexRuntimeHome.isHostSystemDefaultRealHome() && hasRecordedLegacySharedCodexPane()) { + if (codexRuntimeHome && hasRecordedManagedHostCodexPane()) { + const livePtyIds = await listLiveDaemonPtyIds() + if (livePtyIds) { reconcileCodexPaneAccountsWithLivePtys(livePtyIds) + const settings = store?.getSettings() + reconcileRetainedCodexHookHomes({ + hookService: codexHookService, + hooksEnabled: + isAgentStatusHooksEnabled(settings) && + settings?.disabledTuiAgents.includes('codex') !== true, + runtimeHomePaths: codexRuntimeHome.getRetainedHostCodexHookHomePaths(livePtyIds) + }) } - const settings = store?.getSettings() - reconcileRetainedCodexHookHomes({ - hookService: codexHookService, - hooksEnabled: - isAgentStatusHooksEnabled(settings) && - settings?.disabledTuiAgents.includes('codex') !== true, - runtimeHomePaths: codexRuntimeHome.getRetainedHostCodexHookHomePaths(livePtyIds) - }) } // Why: retained shells can invoke Codex immediately after the startup gate. codexRuntimeHome?.reconcileLegacySharedHomeForRetainedPanes() diff --git a/src/main/startup/desktop-startup-ordering.test.ts b/src/main/startup/desktop-startup-ordering.test.ts index 42eb31617bc..376e6a48814 100644 --- a/src/main/startup/desktop-startup-ordering.test.ts +++ b/src/main/startup/desktop-startup-ordering.test.ts @@ -98,8 +98,8 @@ describe('startup ordering', () => { it('reconciles retained Codex homes after authoritative daemon inventory', () => { const source = readFileSync(join(process.cwd(), 'src/main/index.ts'), 'utf8') const daemonInitIndex = source.indexOf('await initDaemonPtyProvider(signal') - const routeGateIndex = source.indexOf( - 'codexRuntimeHome?.isHostSystemDefaultRealHome()', + const retainedPaneGateIndex = source.indexOf( + 'hasRecordedManagedHostCodexPane()', daemonInitIndex ) const inventoryIndex = source.indexOf('await listLiveDaemonPtyIds()', daemonInitIndex) @@ -113,8 +113,9 @@ describe('startup ordering', () => { const desktopIndex = source.indexOf('Promise.resolve(openMainWindow())', serveIndex) expect(daemonInitIndex).toBeGreaterThanOrEqual(0) + expect(retainedPaneGateIndex).toBeGreaterThan(daemonInitIndex) expect(inventoryIndex).toBeGreaterThan(daemonInitIndex) - expect(routeGateIndex).toBeGreaterThan(inventoryIndex) + expect(inventoryIndex).toBeGreaterThan(retainedPaneGateIndex) expect(hookReconciliationIndex).toBeGreaterThan(inventoryIndex) expect(hookReconciliationIndex).toBeLessThan(reconciliationIndex) expect(reconciliationIndex).toBeGreaterThan(inventoryIndex)