mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 08:03:33 +00:00
perf(codex): skip migration scans for nonshared reattaches
This commit is contained in:
@@ -225,6 +225,13 @@ export function getCodexPaneAccount(ptyId: string): CodexPaneAccountRecord | nul
|
||||
return readRegistry().panes[ptyId] ?? null
|
||||
}
|
||||
|
||||
/** `custom-home` stays conservative because it can mask a non-comparable shared-home route. */
|
||||
export function isCodexPaneHomeRouteProvenAwayFromSharedHome(
|
||||
route: CodexPaneHomeRoute | undefined
|
||||
): boolean {
|
||||
return route === 'real-home' || route === 'account-home' || route === 'wsl-home'
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the lane each given PTY launched from, omitting panes with no record.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { CodexSessionMigrationRecentExits } from './codex-session-migration-recent-exits'
|
||||
|
||||
describe('CodexSessionMigrationRecentExits', () => {
|
||||
it('refreshes a rerecorded ID before evicting the oldest entry', () => {
|
||||
const exits = new CodexSessionMigrationRecentExits()
|
||||
for (let index = 0; index < 256; index += 1) {
|
||||
exits.record(`pty-${index}`, index + 1)
|
||||
}
|
||||
|
||||
exits.record('pty-0', 500)
|
||||
exits.record('pty-overflow', 501)
|
||||
|
||||
expect(exits.matchesAfter('pty-0', 499)).toBe(true)
|
||||
expect(exits.matchesAfter('pty-1', 0)).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,52 @@
|
||||
const RETENTION_MS = 60_000
|
||||
const MAX_ENTRIES = 256
|
||||
|
||||
type RecentPtyExit = { sequence: number; recordedAt: number }
|
||||
|
||||
export class CodexSessionMigrationRecentExits {
|
||||
private readonly exits = new Map<string, RecentPtyExit>()
|
||||
|
||||
record(leaseId: string, sequence: number): void {
|
||||
const now = Date.now()
|
||||
for (const [id, exit] of this.exits) {
|
||||
if (now - exit.recordedAt > RETENTION_MS) {
|
||||
this.exits.delete(id)
|
||||
}
|
||||
}
|
||||
this.exits.delete(leaseId)
|
||||
this.exits.set(leaseId, { sequence, recordedAt: now })
|
||||
while (this.exits.size > MAX_ENTRIES) {
|
||||
const oldestLeaseId = this.exits.keys().next().value
|
||||
if (oldestLeaseId === undefined) {
|
||||
break
|
||||
}
|
||||
this.exits.delete(oldestLeaseId)
|
||||
}
|
||||
}
|
||||
|
||||
consumeAfter(leaseId: string, startedSequence: number | undefined): RecentPtyExit | null {
|
||||
const exit = this.exits.get(leaseId)
|
||||
this.exits.delete(leaseId)
|
||||
if (
|
||||
!exit ||
|
||||
startedSequence === undefined ||
|
||||
exit.sequence <= startedSequence ||
|
||||
Date.now() - exit.recordedAt > RETENTION_MS
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return exit
|
||||
}
|
||||
|
||||
matchesAfter(leaseId: string, startedSequence: number | undefined): boolean {
|
||||
const exit = this.exits.get(leaseId)
|
||||
if (!exit) {
|
||||
return false
|
||||
}
|
||||
if (Date.now() - exit.recordedAt > RETENTION_MS) {
|
||||
this.exits.delete(leaseId)
|
||||
return false
|
||||
}
|
||||
return startedSequence !== undefined && exit.sequence > startedSequence
|
||||
}
|
||||
}
|
||||
@@ -491,6 +491,146 @@ describe('createCodexSessionMigrationScheduler', () => {
|
||||
await vi.waitFor(() => expect(finishScheduledRun).toHaveBeenCalledOnce())
|
||||
})
|
||||
|
||||
it('keeps an ignored reattach exit from poisoning a same-ID launch already starting', async () => {
|
||||
const startBackfill = vi.fn().mockResolvedValue({ stopped: false })
|
||||
const scheduler = createCodexSessionMigrationScheduler({
|
||||
isEligible: () => true,
|
||||
isQuitting: () => false,
|
||||
resolveSystemCodexHomePathOverride: () => undefined,
|
||||
startBackfill,
|
||||
startIndexHeal: vi.fn().mockResolvedValue(null),
|
||||
initialDelayMs: 1_000
|
||||
})
|
||||
|
||||
scheduler.ignoreLaunch('stable-pty', 1)
|
||||
scheduler.finishLaunch('stable-pty', 3)
|
||||
scheduler.beginLaunch('stable-pty', false, new Date(), 2)
|
||||
await vi.advanceTimersByTimeAsync(1_000)
|
||||
|
||||
expect(startBackfill).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
scanDates: expect.any(Array),
|
||||
writeCompletionMarker: false,
|
||||
writeBoundedCompletionMarker: false
|
||||
}),
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('matches an ignored reattach exit that arrived before its lifecycle callback', async () => {
|
||||
const startBackfill = vi.fn().mockResolvedValue({ stopped: false })
|
||||
const scheduler = createCodexSessionMigrationScheduler({
|
||||
isEligible: () => true,
|
||||
isQuitting: () => false,
|
||||
resolveSystemCodexHomePathOverride: () => undefined,
|
||||
startBackfill,
|
||||
startIndexHeal: vi.fn().mockResolvedValue(null),
|
||||
initialDelayMs: 1_000
|
||||
})
|
||||
|
||||
scheduler.finishLaunch('stable-pty', 2)
|
||||
scheduler.ignoreLaunch('stable-pty', 1)
|
||||
scheduler.beginLaunch('stable-pty', false, new Date(), 3)
|
||||
await vi.advanceTimersByTimeAsync(1_000)
|
||||
|
||||
expect(startBackfill).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
scanDates: expect.any(Array),
|
||||
writeCompletionMarker: false,
|
||||
writeBoundedCompletionMarker: false
|
||||
}),
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps a newer same-ID launch active while the ignored incarnation exits', async () => {
|
||||
const startBackfill = vi.fn().mockResolvedValue({ stopped: false })
|
||||
const scheduler = createCodexSessionMigrationScheduler({
|
||||
isEligible: () => true,
|
||||
isQuitting: () => false,
|
||||
resolveSystemCodexHomePathOverride: () => undefined,
|
||||
startBackfill,
|
||||
startIndexHeal: vi.fn().mockResolvedValue(null),
|
||||
initialDelayMs: 1_000
|
||||
})
|
||||
|
||||
scheduler.ignoreLaunch('stable-pty', 1)
|
||||
scheduler.beginLaunch('stable-pty', false, new Date(), 2)
|
||||
scheduler.finishLaunch('stable-pty', 3)
|
||||
await vi.advanceTimersByTimeAsync(1_000)
|
||||
|
||||
expect(startBackfill).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ writeCompletionMarker: false }),
|
||||
undefined
|
||||
)
|
||||
|
||||
scheduler.finishLaunch('stable-pty', 4)
|
||||
await vi.advanceTimersByTimeAsync(1_000)
|
||||
expect(startBackfill).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ writeCompletionMarker: true }),
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('matches every late duplicate ignored callback without poisoning reuse', async () => {
|
||||
const startBackfill = vi.fn().mockResolvedValue({ stopped: false })
|
||||
const scheduler = createCodexSessionMigrationScheduler({
|
||||
isEligible: () => true,
|
||||
isQuitting: () => false,
|
||||
resolveSystemCodexHomePathOverride: () => undefined,
|
||||
startBackfill,
|
||||
startIndexHeal: vi.fn().mockResolvedValue(null),
|
||||
initialDelayMs: 1_000
|
||||
})
|
||||
|
||||
scheduler.ignoreLaunch('stable-pty', 1)
|
||||
scheduler.finishLaunch('stable-pty', 3)
|
||||
expect(scheduler.matchesIgnoredLaunchExit('stable-pty', 2)).toBe(true)
|
||||
expect(scheduler.matchesIgnoredLaunchExit('stable-pty', 2)).toBe(true)
|
||||
scheduler.beginLaunch('stable-pty', false, new Date(), 4)
|
||||
await vi.advanceTimersByTimeAsync(1_000)
|
||||
expect(startBackfill).toHaveBeenCalledTimes(1)
|
||||
|
||||
scheduler.finishLaunch('stable-pty', 5)
|
||||
await vi.advanceTimersByTimeAsync(1_000)
|
||||
expect(startBackfill).toHaveBeenCalledTimes(2)
|
||||
expect(startBackfill).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ writeCompletionMarker: true }),
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps three exit-before-callback reattaches from stranding a reused ID', async () => {
|
||||
const startBackfill = vi.fn().mockResolvedValue({ stopped: false })
|
||||
const scheduler = createCodexSessionMigrationScheduler({
|
||||
isEligible: () => true,
|
||||
isQuitting: () => false,
|
||||
resolveSystemCodexHomePathOverride: () => undefined,
|
||||
startBackfill,
|
||||
startIndexHeal: vi.fn().mockResolvedValue(null),
|
||||
initialDelayMs: 1_000
|
||||
})
|
||||
|
||||
scheduler.finishLaunch('stable-pty', 4)
|
||||
scheduler.ignoreLaunch('stable-pty', 1)
|
||||
scheduler.ignoreLaunch('stable-pty', 2)
|
||||
scheduler.ignoreLaunch('stable-pty', 3)
|
||||
scheduler.beginLaunch('stable-pty', false, new Date(), 5)
|
||||
await vi.advanceTimersByTimeAsync(1_000)
|
||||
expect(startBackfill).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ writeCompletionMarker: false }),
|
||||
undefined
|
||||
)
|
||||
|
||||
scheduler.finishLaunch('stable-pty', 6)
|
||||
await vi.advanceTimersByTimeAsync(1_000)
|
||||
expect(startBackfill).toHaveBeenCalledTimes(2)
|
||||
expect(startBackfill).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ writeCompletionMarker: true }),
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('does not consume an exit from an earlier stable-id incarnation', async () => {
|
||||
const startBackfill = vi.fn().mockResolvedValue({ stopped: false })
|
||||
const scheduler = createCodexSessionMigrationScheduler({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getCodexSessionBackfillDate } from './codex-session-backfill-date'
|
||||
import { CodexSessionMigrationRecentExits } from './codex-session-migration-recent-exits'
|
||||
import type {
|
||||
CodexSessionBackfillDate,
|
||||
CodexSessionBackfillOptions
|
||||
@@ -9,11 +10,6 @@ type MigrationRun = (
|
||||
systemCodexHomePathOverride?: string
|
||||
) => Promise<unknown>
|
||||
|
||||
const EARLY_PTY_EXIT_RETENTION_MS = 60_000
|
||||
const MAX_EARLY_PTY_EXITS = 256
|
||||
|
||||
type EarlyPtyExit = { sequence: number; recordedAt: number }
|
||||
|
||||
export type CodexSessionMigrationScheduler = {
|
||||
beginLaunch(
|
||||
leaseId: string,
|
||||
@@ -21,6 +17,8 @@ export type CodexSessionMigrationScheduler = {
|
||||
startedAt?: Date,
|
||||
startedSequence?: number
|
||||
): void
|
||||
ignoreLaunch(leaseId: string, startedSequence: number): void
|
||||
matchesIgnoredLaunchExit(leaseId: string, startedSequence: number): boolean
|
||||
finishLaunch(leaseId: string, exitSequence?: number): void
|
||||
scheduleInitialRun(): void
|
||||
scheduleRun(fullScanRequired?: boolean): void
|
||||
@@ -46,7 +44,10 @@ export function createCodexSessionMigrationScheduler(args: {
|
||||
let pendingFullScan = false
|
||||
let migrationTask: Promise<void> | null = null
|
||||
const activeLaunches = new Map<string, Date>()
|
||||
const earlyPtyExits = new Map<string, EarlyPtyExit>()
|
||||
// Why: non-shared reattach exits must not masquerade as exit-before-begin races for a reused id.
|
||||
const ignoredLaunches = new Set<string>()
|
||||
const ignoredPtyExits = new CodexSessionMigrationRecentExits()
|
||||
const earlyPtyExits = new CodexSessionMigrationRecentExits()
|
||||
let activeRunStopObserved = false
|
||||
let rerunRequested = false
|
||||
|
||||
@@ -194,18 +195,41 @@ export function createCodexSessionMigrationScheduler(args: {
|
||||
if (args.isQuitting() || activeLaunches.has(leaseId)) {
|
||||
return
|
||||
}
|
||||
if (consumeEarlyPtyExit(earlyPtyExits, leaseId, startedSequence)) {
|
||||
if (earlyPtyExits.consumeAfter(leaseId, startedSequence)) {
|
||||
scheduleRun(true)
|
||||
return
|
||||
}
|
||||
activeLaunches.set(leaseId, startedAt)
|
||||
scheduleRun(fullScanRequired)
|
||||
},
|
||||
ignoreLaunch(leaseId, startedSequence): void {
|
||||
if (args.isQuitting() || ignoredLaunches.has(leaseId)) {
|
||||
return
|
||||
}
|
||||
if (ignoredPtyExits.matchesAfter(leaseId, startedSequence)) {
|
||||
return
|
||||
}
|
||||
const earlyExit = earlyPtyExits.consumeAfter(leaseId, startedSequence)
|
||||
if (earlyExit) {
|
||||
ignoredPtyExits.record(leaseId, earlyExit.sequence)
|
||||
return
|
||||
}
|
||||
ignoredLaunches.add(leaseId)
|
||||
},
|
||||
matchesIgnoredLaunchExit(leaseId, startedSequence): boolean {
|
||||
return ignoredPtyExits.matchesAfter(leaseId, startedSequence)
|
||||
},
|
||||
finishLaunch(leaseId, exitSequence): void {
|
||||
if (ignoredLaunches.delete(leaseId)) {
|
||||
if (exitSequence !== undefined) {
|
||||
ignoredPtyExits.record(leaseId, exitSequence)
|
||||
}
|
||||
return
|
||||
}
|
||||
const startedAt = activeLaunches.get(leaseId)
|
||||
if (!startedAt) {
|
||||
if (exitSequence !== undefined) {
|
||||
recordEarlyPtyExit(earlyPtyExits, leaseId, exitSequence)
|
||||
earlyPtyExits.record(leaseId, exitSequence)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -253,42 +277,6 @@ function compareBackfillDates(
|
||||
return left.join('-').localeCompare(right.join('-'))
|
||||
}
|
||||
|
||||
function recordEarlyPtyExit(
|
||||
exits: Map<string, EarlyPtyExit>,
|
||||
leaseId: string,
|
||||
sequence: number
|
||||
): void {
|
||||
const now = Date.now()
|
||||
for (const [id, exit] of exits) {
|
||||
if (now - exit.recordedAt > EARLY_PTY_EXIT_RETENTION_MS) {
|
||||
exits.delete(id)
|
||||
}
|
||||
}
|
||||
exits.set(leaseId, { sequence, recordedAt: now })
|
||||
while (exits.size > MAX_EARLY_PTY_EXITS) {
|
||||
const oldestLeaseId = exits.keys().next().value
|
||||
if (oldestLeaseId === undefined) {
|
||||
break
|
||||
}
|
||||
exits.delete(oldestLeaseId)
|
||||
}
|
||||
}
|
||||
|
||||
function consumeEarlyPtyExit(
|
||||
exits: Map<string, EarlyPtyExit>,
|
||||
leaseId: string,
|
||||
startedSequence: number | undefined
|
||||
): boolean {
|
||||
const exit = exits.get(leaseId)
|
||||
exits.delete(leaseId)
|
||||
return (
|
||||
exit !== undefined &&
|
||||
startedSequence !== undefined &&
|
||||
exit.sequence > startedSequence &&
|
||||
Date.now() - exit.recordedAt <= EARLY_PTY_EXIT_RETENTION_MS
|
||||
)
|
||||
}
|
||||
|
||||
function isStoppedMigrationResult(result: unknown): boolean {
|
||||
return Boolean(result && typeof result === 'object' && 'stopped' in result && result.stopped)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
getCodexPaneAccount,
|
||||
hasRecordedLegacySharedCodexPane,
|
||||
hasRecordedManagedHostCodexPane,
|
||||
isCodexPaneHomeRouteProvenAwayFromSharedHome,
|
||||
reconcileCodexPaneAccountsWithLivePtys,
|
||||
recordCodexPaneAccount
|
||||
} from './codex-pane-account-registry'
|
||||
@@ -47,6 +48,17 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
describe('codex pane account registry', () => {
|
||||
it.each([
|
||||
['real-home', true],
|
||||
['account-home', true],
|
||||
['wsl-home', true],
|
||||
['shared-home', false],
|
||||
['custom-home', false],
|
||||
[undefined, false]
|
||||
] as const)('classifies whether %s proves a pane avoided the shared home', (route, expected) => {
|
||||
expect(isCodexPaneHomeRouteProvenAwayFromSharedHome(route)).toBe(expected)
|
||||
})
|
||||
|
||||
it('survives a process restart so a daemon-backed shell stays attributable', () => {
|
||||
recordCodexPaneAccount('pty-1', {
|
||||
selectionKey: 'host',
|
||||
|
||||
@@ -39,7 +39,10 @@ import {
|
||||
shutdownDaemon
|
||||
} from './daemon/daemon-init'
|
||||
import {
|
||||
type CodexPaneHomeRoute,
|
||||
getCodexPaneAccount,
|
||||
hasRecordedManagedHostCodexPane,
|
||||
isCodexPaneHomeRouteProvenAwayFromSharedHome,
|
||||
reconcileCodexPaneAccountsWithLivePtys
|
||||
} from './codex/codex-pane-account-registry'
|
||||
import { closeAllWatchers } from './ipc/filesystem-watcher'
|
||||
@@ -401,10 +404,30 @@ function handleCodexHomePtySpawned(args: {
|
||||
id: string
|
||||
codexHomePath: string | null
|
||||
reattached?: boolean
|
||||
reattachedHomeRoute?: CodexPaneHomeRoute | null
|
||||
launchEnv?: NodeJS.ProcessEnv
|
||||
startedAt?: Date
|
||||
startedSequence?: number
|
||||
}): void {
|
||||
// Why: only shared or ambiguous retained shells can create rollout logs that still need publication.
|
||||
if (args.reattached && args.startedSequence !== undefined) {
|
||||
const paneAccount = getCodexPaneAccount(args.id)
|
||||
const homeRoute =
|
||||
args.reattachedHomeRoute !== undefined
|
||||
? (args.reattachedHomeRoute ?? undefined)
|
||||
: paneAccount?.homeRoute
|
||||
if (codexSessionMigration && isCodexPaneHomeRouteProvenAwayFromSharedHome(homeRoute)) {
|
||||
codexSessionMigration.ignoreLaunch(args.id, args.startedSequence)
|
||||
return
|
||||
}
|
||||
if (
|
||||
!paneAccount &&
|
||||
homeRoute === undefined &&
|
||||
codexSessionMigration?.matchesIgnoredLaunchExit(args.id, args.startedSequence)
|
||||
) {
|
||||
return
|
||||
}
|
||||
}
|
||||
const fullScanRequired =
|
||||
codexRuntimeHome?.beginHostSystemDefaultSessionMigrationLaunch(args.codexHomePath, {
|
||||
reattached: args.reattached,
|
||||
|
||||
@@ -70,6 +70,7 @@ const {
|
||||
clearPaneKeyAliasesForPtyMock,
|
||||
recordCodexPaneAccountMock,
|
||||
forgetCodexPaneAccountMock,
|
||||
getCodexPaneAccountMock,
|
||||
ensureCodexBackfillRecoveryMock
|
||||
} = vi.hoisted(() => ({
|
||||
handleMock: vi.fn(),
|
||||
@@ -107,6 +108,7 @@ const {
|
||||
clearPaneKeyAliasesForPtyMock: vi.fn(),
|
||||
recordCodexPaneAccountMock: vi.fn(),
|
||||
forgetCodexPaneAccountMock: vi.fn(),
|
||||
getCodexPaneAccountMock: vi.fn(),
|
||||
ensureCodexBackfillRecoveryMock: vi.fn(() => Promise.resolve())
|
||||
}))
|
||||
|
||||
@@ -221,7 +223,8 @@ vi.mock('../agent-hooks/migration-unsupported-pty-state', () => ({
|
||||
|
||||
vi.mock('../codex/codex-pane-account-registry', () => ({
|
||||
recordCodexPaneAccount: recordCodexPaneAccountMock,
|
||||
forgetCodexPaneAccount: forgetCodexPaneAccountMock
|
||||
forgetCodexPaneAccount: forgetCodexPaneAccountMock,
|
||||
getCodexPaneAccount: getCodexPaneAccountMock
|
||||
}))
|
||||
|
||||
vi.mock('../codex/codex-state-db-backfill-recovery', () => ({
|
||||
@@ -410,6 +413,7 @@ describe('registerPtyHandlers', () => {
|
||||
clearPaneKeyAliasesForPtyMock.mockReset()
|
||||
recordCodexPaneAccountMock.mockReset()
|
||||
forgetCodexPaneAccountMock.mockReset()
|
||||
getCodexPaneAccountMock.mockReset()
|
||||
ensureCodexBackfillRecoveryMock.mockReset()
|
||||
ensureCodexBackfillRecoveryMock.mockResolvedValue(undefined)
|
||||
mainWindow.webContents.on.mockReset()
|
||||
@@ -9092,16 +9096,20 @@ describe('registerPtyHandlers', () => {
|
||||
{
|
||||
label: 'git worktree',
|
||||
worktreeId: 'repo-1::/tmp/live-owner',
|
||||
cwd: '/tmp/live-owner'
|
||||
cwd: '/tmp/live-owner',
|
||||
paneAccountAtStart: { homeRoute: 'shared-home' },
|
||||
expectedHomeRouteAtStart: 'shared-home'
|
||||
},
|
||||
{
|
||||
label: 'folder workspace',
|
||||
worktreeId: 'folder:live-owner',
|
||||
cwd: '/tmp'
|
||||
cwd: '/tmp',
|
||||
paneAccountAtStart: null,
|
||||
expectedHomeRouteAtStart: null
|
||||
}
|
||||
])(
|
||||
'adopts a completed runtime-owned pane before replacement launch preflight ($label)',
|
||||
async ({ worktreeId, cwd }) => {
|
||||
async ({ worktreeId, cwd, paneAccountAtStart, expectedHomeRouteAtStart }) => {
|
||||
type StableAdoption = {
|
||||
result: { id: string; incarnationId?: string; isReattach?: boolean }
|
||||
owner: { handle?: string; tabId: string; leafId: string; ptyId: string }
|
||||
@@ -9308,8 +9316,10 @@ describe('registerPtyHandlers', () => {
|
||||
runtime.beginPtyRegistration.mockImplementation(() => {
|
||||
runtimeSecondAdoption ??= spawnController.adoptStablePane(adoptionArgs)
|
||||
})
|
||||
getCodexPaneAccountMock.mockReturnValue(paneAccountAtStart)
|
||||
const rendererFirstMount = handlers.get('pty:spawn')!(null, mountArgs)
|
||||
await vi.waitFor(() => expect(providerSpawn).toHaveBeenCalledTimes(3))
|
||||
getCodexPaneAccountMock.mockReturnValue({ homeRoute: 'real-home' })
|
||||
releaseAttach()
|
||||
await vi.waitFor(() => expect(runtimeSecondAdoption).not.toBeNull())
|
||||
const pendingRuntimeAdoption = runtimeSecondAdoption
|
||||
@@ -9354,6 +9364,7 @@ describe('registerPtyHandlers', () => {
|
||||
id: 'pty-live-owner',
|
||||
codexHomePath: null,
|
||||
reattached: true,
|
||||
reattachedHomeRoute: expectedHomeRouteAtStart,
|
||||
startedAt: expect.any(Date),
|
||||
startedSequence: expect.any(Number)
|
||||
})
|
||||
|
||||
+78
-2
@@ -198,7 +198,9 @@ import {
|
||||
} from '../codex-accounts/managed-codex-auth-readiness'
|
||||
import {
|
||||
forgetCodexPaneAccount,
|
||||
recordCodexPaneAccount
|
||||
getCodexPaneAccount,
|
||||
recordCodexPaneAccount,
|
||||
type CodexPaneHomeRoute
|
||||
} from '../codex/codex-pane-account-registry'
|
||||
import { resolveCodexPaneLaunchAccount } from '../codex/codex-pane-launch-account'
|
||||
import { getSystemCodexHomePath } from '../codex/codex-home-paths'
|
||||
@@ -1238,6 +1240,7 @@ export type CodexHomePtySpawnedLifecycleArgs = {
|
||||
id: string
|
||||
codexHomePath: string | null
|
||||
reattached?: boolean
|
||||
reattachedHomeRoute?: CodexPaneHomeRoute | null
|
||||
launchEnv?: NodeJS.ProcessEnv
|
||||
startedAt?: Date
|
||||
startedSequence?: number
|
||||
@@ -1449,6 +1452,30 @@ function recordCodexPaneAccountForSpawn(args: {
|
||||
recordCodexPaneAccount(args.ptyId, record)
|
||||
}
|
||||
|
||||
function snapshotCodexPaneHomeRoutes(
|
||||
ptyIds: readonly (string | null | undefined)[]
|
||||
): ReadonlyMap<string, CodexPaneHomeRoute | null> {
|
||||
const routes = new Map<string, CodexPaneHomeRoute | null>()
|
||||
for (const ptyId of ptyIds) {
|
||||
if (!ptyId || routes.has(ptyId)) {
|
||||
continue
|
||||
}
|
||||
routes.set(ptyId, getCodexPaneAccount(ptyId)?.homeRoute ?? null)
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
function codexReattachedHomeRouteField(
|
||||
routes: ReadonlyMap<string, CodexPaneHomeRoute | null>,
|
||||
ptyId: string,
|
||||
reattached: boolean
|
||||
): { reattachedHomeRoute?: CodexPaneHomeRoute | null } {
|
||||
if (!reattached || !routes.has(ptyId)) {
|
||||
return {}
|
||||
}
|
||||
return { reattachedHomeRoute: routes.get(ptyId) ?? null }
|
||||
}
|
||||
|
||||
function readEnvWithProcessFallback(
|
||||
baseEnv: Record<string, string>,
|
||||
key: string
|
||||
@@ -4407,6 +4434,12 @@ export function registerPtyHandlers(
|
||||
const codexHomeLaunchStartedAt = !args.connectionId ? new Date() : undefined
|
||||
const codexHomeLaunchStartedSequence = !args.connectionId ? ++ptyLifecycleSequence : undefined
|
||||
const preAdoptedStablePane = args.adoptedStablePane ?? null
|
||||
const reattachedCodexHomeRoutes = !args.connectionId
|
||||
? snapshotCodexPaneHomeRoutes([
|
||||
preAdoptedStablePane?.result.id,
|
||||
args.sessionId ? getAppPtyId(args.connectionId, args.sessionId) : undefined
|
||||
])
|
||||
: new Map<string, CodexPaneHomeRoute | null>()
|
||||
const startupPromise = getLocalPtyStartupPromise(args.connectionId)
|
||||
if (startupPromise) {
|
||||
await startupPromise
|
||||
@@ -4440,7 +4473,8 @@ export function registerPtyHandlers(
|
||||
codexHomePath: null,
|
||||
reattached: true,
|
||||
startedAt: codexHomeLaunchStartedAt,
|
||||
startedSequence: codexHomeLaunchStartedSequence
|
||||
startedSequence: codexHomeLaunchStartedSequence,
|
||||
...codexReattachedHomeRouteField(reattachedCodexHomeRoutes, result.id, true)
|
||||
})
|
||||
}
|
||||
return result
|
||||
@@ -5115,6 +5149,7 @@ export function registerPtyHandlers(
|
||||
reattached: true,
|
||||
startedAt: codexHomeLaunchStartedAt,
|
||||
startedSequence: codexHomeLaunchStartedSequence,
|
||||
...codexReattachedHomeRouteField(reattachedCodexHomeRoutes, result.id, true),
|
||||
...(env ? { launchEnv: env } : {})
|
||||
})
|
||||
}
|
||||
@@ -5288,6 +5323,11 @@ export function registerPtyHandlers(
|
||||
codexHomePath: selectedCodexHomePath,
|
||||
startedAt: codexHomeLaunchStartedAt,
|
||||
startedSequence: codexHomeLaunchStartedSequence,
|
||||
...codexReattachedHomeRouteField(
|
||||
reattachedCodexHomeRoutes,
|
||||
result.id,
|
||||
result.isReattach === true
|
||||
),
|
||||
...(result.isReattach === true ? { reattached: true } : env ? { launchEnv: env } : {})
|
||||
})
|
||||
}
|
||||
@@ -5786,6 +5826,37 @@ export function registerPtyHandlers(
|
||||
) => {
|
||||
const codexHomeLaunchStartedAt = !args.connectionId ? new Date() : undefined
|
||||
const codexHomeLaunchStartedSequence = !args.connectionId ? ++ptyLifecycleSequence : undefined
|
||||
const initialLeafId =
|
||||
typeof args.leafId === 'string' && isTerminalLeafId(args.leafId) ? args.leafId : null
|
||||
const initialPaneKey =
|
||||
typeof args.worktreeId === 'string' &&
|
||||
typeof args.tabId === 'string' &&
|
||||
isValidTerminalTabId(args.tabId) &&
|
||||
args.tabId.length <= 512 &&
|
||||
initialLeafId
|
||||
? makePaneKey(args.tabId, initialLeafId)
|
||||
: null
|
||||
const initialStablePanePtyId = (() => {
|
||||
try {
|
||||
return !args.connectionId && initialPaneKey
|
||||
? resolveStablePaneOwner(
|
||||
runtime,
|
||||
store,
|
||||
initialPaneKey,
|
||||
args.worktreeId,
|
||||
args.connectionId
|
||||
)?.ptyId
|
||||
: undefined
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
})()
|
||||
const reattachedCodexHomeRoutes = !args.connectionId
|
||||
? snapshotCodexPaneHomeRoutes([
|
||||
initialStablePanePtyId,
|
||||
args.sessionId ? getAppPtyId(args.connectionId, args.sessionId) : undefined
|
||||
])
|
||||
: new Map<string, CodexPaneHomeRoute | null>()
|
||||
const spawnTiming = createPtySpawnTiming()
|
||||
const startupPromise = getLocalPtyStartupPromise(args.connectionId)
|
||||
if (startupPromise) {
|
||||
@@ -6840,6 +6911,11 @@ export function registerPtyHandlers(
|
||||
codexHomePath: selectedCodexHomePath,
|
||||
startedAt: codexHomeLaunchStartedAt,
|
||||
startedSequence: codexHomeLaunchStartedSequence,
|
||||
...codexReattachedHomeRouteField(
|
||||
reattachedCodexHomeRoutes,
|
||||
result.id,
|
||||
result.isReattach === true
|
||||
),
|
||||
...(result.isReattach === true
|
||||
? { reattached: true }
|
||||
: baseEnv
|
||||
|
||||
Reference in New Issue
Block a user