Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
6ba01b3
Publish Pi at spawn and settle signed-out starts once
brennanb2025 Oct 12, 2026
11998e4
Merge updated startup piece 3 into piece 4
brennanb2025 Oct 12, 2026
efc8bd7
Merge updated startup piece 3 into piece 4
brennanb2025 Oct 12, 2026
2ae82e4
Merge updated startup piece 3 into piece 4
brennanb2025 Oct 12, 2026
7c47e5e
Carry catalog startup fix from piece 3 into piece 4
brennanb2025 Oct 13, 2026
e91234f
Carry awaited ACP opening into Pi startup
brennanb2025 Oct 13, 2026
39eb527
Await Pi open and fail signed-out construction
brennanb2025 Oct 13, 2026
50d36b0
Read Pi catalog facts from awaited opening
brennanb2025 Oct 13, 2026
90db80b
Keep the Pi fixture exit observer
brennanb2025 Oct 13, 2026
62eca67
Carry owned-open failure recording through the startup stack
brennanb2025 Oct 13, 2026
6e71c1f
Carry owned-open failure recording through the startup stack
brennanb2025 Oct 13, 2026
8dfc95a
Carry host and ACP startup fixes into Pi opening
brennanb2025 Oct 13, 2026
0a282e3
Restore signed-out restart and fail Pi startup faults
brennanb2025 Oct 13, 2026
6ff10a6
Run Pi opening catalog fixtures with Node SQLite
brennanb2025 Oct 13, 2026
240f147
Carry ordered delivery fixes into Pi opening
brennanb2025 Oct 13, 2026
f1571b8
Remove retired Pi startup revision and match saved-pick behavior
brennanb2025 Oct 13, 2026
5cd688e
Carry startup picks and parked-message ordering into Pi opening [skip…
brennanb2025 Oct 13, 2026
0b4f30e
Apply Pi startup picks after readiness and remove unused open facts […
brennanb2025 Oct 13, 2026
c3d744e
Carry startup picks through an ended opening [skip ci]
brennanb2025 Oct 13, 2026
8946ecd
Carry main sync into 28048
brennanb2025 Oct 13, 2026
2965e30
Carry main sync into 28048
brennanb2025 Oct 13, 2026
46425fe
Carry main sync into 28048
brennanb2025 Oct 13, 2026
bdcf7bd
Carry main sync into 28048
brennanb2025 Oct 13, 2026
8cf7ddd
Carry main sync into 28048
brennanb2025 Oct 13, 2026
ffd9a3b
Carry main sync into 28048
brennanb2025 Oct 13, 2026
672d7b2
Carry main sync into 28048
brennanb2025 Oct 13, 2026
f854127
Carry main sync into 28048
brennanb2025 Oct 13, 2026
b85ccad
Keep startup recovery flows and remove duplicate Pi handshake cases
brennanb2025 Oct 13, 2026
b9e16a8
Cover both Pi startup choices in the delivery flow
brennanb2025 Oct 13, 2026
a826ea9
Match the Pi prompt entry recorded by the scripted wire
brennanb2025 Oct 13, 2026
c09bbdc
Carry main sync into 28048
brennanb2025 Oct 13, 2026
35c9a40
Carry main sync into 28048
brennanb2025 Oct 13, 2026
17971b7
Carry current main and startup corrections into 28048
brennanb2025 Oct 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config/scripts/vitest-sqlite-runtime-files.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { SQLITE_DATABASE_RUNTIME_INCLUDE } from './vitest-sqlite-database-runtim

// Real SQLite fixtures keep Node publication and close semantics.
export const SQLITE_RUNTIME_INCLUDE = [
'src/main/pi/rpc-session-adapter.test.ts',
'src/main/pi/rpc-session-startup.test.ts',
'src/main/acp/acp-structured-publish-at-spawn.test.ts',
'tests/e2e/native-chat-startup-option-pickers.unit.test.ts',
...SQLITE_DATABASE_RUNTIME_INCLUDE,
Expand Down Expand Up @@ -255,6 +257,10 @@ export const SQLITE_RUNTIME_INCLUDE = [
'src/main/runtime/runtime-client-agent-launch-settings-durability.test.ts',
'src/main/runtime/structured-agent-session-codex-scripted-child.test.ts',
'src/main/runtime/structured-agent-session-codex-start-deadline.test.ts',
'src/main/runtime/structured-agent-session-pi-startup.test.ts',
'src/main/runtime/structured-agent-session-pi-signed-out-start.test.ts',
'src/main/runtime/structured-agent-session-adapter-start-failure.test.ts',
'src/main/runtime/structured-agent-session-provider-operations-while-starting.test.ts',
'src/main/runtime/structured-agent-session-codex-settlement-order.test.ts',
'src/main/runtime/structured-agent-session-integration-replay.test.ts',
'src/main/runtime/structured-agent-session-integration.test.ts',
Expand Down
13 changes: 9 additions & 4 deletions src/main/jsonl-rpc/agent-connection.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { PipedProcessSpawner } from '@orca/process-host/process-spec'
import { spawnProcess } from '@orca/process-host'
import { providerDiagnostic, withProviderDiagnostic } from '../../shared/agent-session-failure'
import {
spawnManagedProviderProcess,
type ManagedProviderProcess,
Expand Down Expand Up @@ -81,10 +82,14 @@ export class JsonlRpcAgentConnection {
managed.child.stderr.on('error', this.onError)
managed.onExit((exit) => {
clearTimeout(this.streamExitTimer)
const error = new Error(
exit.processless
? 'Agent process could not start'
: `Agent process exited (code ${exit.code ?? 'none'}, signal ${exit.signal ?? 'none'})`
// The agent's last stderr lines are what a person can act on when it dies.
const error = withProviderDiagnostic(
new Error(
exit.processless
? 'Agent process could not start'
: `Agent process exited (code ${exit.code ?? 'none'}, signal ${exit.signal ?? 'none'})`
),
providerDiagnostic(managed.stderrTail(), 'person')
)
this.peer.finishOnInputEnd(error)
const stdout = managed.child.stdout
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,6 @@ export class StructuredAgentSessionAdapterRouter implements StructuredAgentSessi
this.liveOwnerOrNull(sessionId)?.holdsLiveProviderProcess?.(sessionId, acquisitionGeneration) ??
false

startUnavailable: NonNullable<StructuredAgentSessionAdapter['startUnavailable']> = (sessionId) =>
this.liveOwnerOrNull(sessionId)?.startUnavailable?.(sessionId)

stopEndsSession = (sessionId: string): boolean =>
this.liveOwnerOrNull(sessionId)?.stopEndsSession?.(sessionId) ?? false

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import type {
AgentSessionAccountKind,
AgentSessionUnavailable
} from '../../../shared/agent-session-availability'
import type { AgentSessionAccountKind } from '../../../shared/agent-session-availability'
import type {
AgentSessionRewindReason,
AgentSessionRewindSupport
Expand Down Expand Up @@ -368,9 +365,6 @@ export type StructuredAgentSessionAdapter = StructuredAgentSessionAdapterStop &
/** The adapter's own child for this exact acquisition has a pid and its root exit has not been
* seen: first-hand proof of life for lease renewal. Absent or false falls back to a PID probe. */
holdsLiveProviderProcess?(sessionId: string, acquisitionGeneration: string): boolean
/** Why the session's running child can take no turn, as its own start found; undefined when
* unknown or no child runs. Lives and dies with that child. */
startUnavailable?(sessionId: string): AgentSessionUnavailable | undefined
/** The `/` surface the running provider reports for itself. Undefined when the
* provider never reports one, which is what keeps the client on its catalog. */
readCommands?(sessionId: string): AgentSessionSlashCommand[] | undefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ export function createStructuredAgentSessionConversationDelivery(input: {
},
providerHoldsDispatch: () => deps.adapter.holdsDispatch?.(sessionId) === true
},
startUnavailable: () => deps.adapter.startUnavailable?.(sessionId),
launchPermission: async () => {
const record = deps.store.getRecord(sessionId)
return record ? structuredAgentSessionLaunchPermission(deps, record) : null
Expand Down Expand Up @@ -205,7 +204,7 @@ export function createStructuredAgentSessionConversationDelivery(input: {
* A rewind found prepared when the conversation opens was started under a child this process no
* longer has — the open runs only when none is indexed — so nothing will finish it, and left alone
* it refuses every send until a view attaches. Settled here instead of by a start inside
* acceptance. A Codex rewind only its provider can prove stays for the attach.
* acceptance. A Codex rewind only its provider can prove stays for the next proven start.
*/
async function settleInterruptedCommands(
deps: StructuredAgentSessionHostDeps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import type {
AgentJournalTurnLifecycle
} from '../../../shared/agent-session-journal-types'
import type { AgentChildWorkView } from '../../../shared/agent-status-child-work-view'
import type { AgentSessionUnavailable } from '../../../shared/agent-session-availability'
import type { JournalItemLinkageVisitor } from '../agent-session-journal/journal-store-contracts'
import type { StructuredAgentSessionChildWorkReads } from './structured-agent-session-idle-sweep'

Expand Down Expand Up @@ -69,17 +68,6 @@ describe('a send after the agent said it is not signed in', () => {
expect(rig.adapter.acquire).toHaveBeenCalledTimes(2)
})

// A Pi that started with no model keeps a placeholder: a sign-in made since reaches a new Pi only.
it('goes to a new agent when the running one started signed out', async () => {
await foundRestTestChat(rig)
rig.adapter.startUnavailable.mockReturnValueOnce({ reason: 'notSignedIn' })
rig.adapter.closeSession.mockClear()
expect((await rig.host.send(CALLER, restTestSend('signed in since', fence()))).ok).toBe(true)
await vi.waitFor(() => expect(rig.adapter.dispatch).toHaveBeenCalledTimes(2))
expect(rig.adapter.closeSession).toHaveBeenCalledWith(SESSION)
expect(rig.adapter.acquire).toHaveBeenCalledTimes(2)
})

it('keeps the agent after any other rejection', async () => {
await foundRestTestChat(rig)
rig.adapter.dispatch.mockImplementationOnce(async () => ({
Expand Down Expand Up @@ -180,14 +168,6 @@ describe('structuredAgentSessionChildReportedSignedOut', () => {
expect(reported({ items, child: childAt({ phase: 'starting' }) })).toBe(false)
expect(reported({ items, child: null })).toBe(false)
})

it("reads the running child's own start, and only a signed-out one", () => {
const fromStart = (startUnavailable: AgentSessionUnavailable, child = childAt({})) =>
structuredAgentSessionChildReportedSignedOut(conversation({ child }), startUnavailable)
expect(fromStart({ reason: 'notSignedIn' })).toBe(true)
expect(fromStart({ reason: 'cliMissing' })).toBe(false)
expect(fromStart({ reason: 'notSignedIn' }, childAt({ phase: 'starting' }))).toBe(false)
})
})

describe('retireOutgrownStructuredAgentSessionChild', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// A child the chat outgrew (signed out, or launched with another permission mode) is replaced before
// the next send: agents read their login and permission mode only when they start.

import type { AgentSessionUnavailable } from '../../../shared/agent-session-availability'
import { readWholeAgentSessionFailureFact } from '../../../shared/agent-session-failure'
import { isRootAgentJournalItem } from '../../../shared/agent-session-journal-producer'
import type { AgentJournalSubmission } from '../../../shared/agent-session-journal-types'
Expand All @@ -28,20 +27,15 @@ type OutgrownChildReading = Pick<StructuredAgentSessionHostSession, 'child'> & {
const signedOut = (failure: unknown): boolean =>
readWholeAgentSessionFailureFact(failure)?.kind === 'notSignedIn'

/** The running child itself said it is not signed in: its start, a send it rejected, or a row it
* wrote. Each child holds its own fence, so matching it scopes both to this child; a new one has
* nothing. `startUnavailable` is the adapter's read of that child's start. */
/** The running child itself said it is not signed in through a rejected send or a row it wrote.
* Its fence scopes both to this child, so a new one has nothing. */
export function structuredAgentSessionChildReportedSignedOut(
session: OutgrownChildReading,
startUnavailable?: AgentSessionUnavailable
session: OutgrownChildReading
): boolean {
const { child, journal } = session
if (!child || child.phase === 'starting' || child.close) {
return false
}
if (startUnavailable?.reason === 'notSignedIn') {
return true
}
if (
journal
.submissions()
Expand Down Expand Up @@ -99,7 +93,6 @@ export async function retireOutgrownStructuredAgentSessionChild(
session: OutgrownChildReading | undefined,
deps: {
work: StructuredAgentSessionChildWorkReads
startUnavailable?: () => AgentSessionUnavailable | undefined
/** The mode a start of the chat would launch with now. */
launchPermission?: () => Promise<AgentChatPermissionMode | null>
stopAgent: (sessionId: string) => Promise<void>
Expand All @@ -109,7 +102,7 @@ export async function retireOutgrownStructuredAgentSessionChild(
if (
!session ||
!(
structuredAgentSessionChildReportedSignedOut(session, deps.startUnavailable?.()) ||
structuredAgentSessionChildReportedSignedOut(session) ||
(await launchedWithOtherPermissionMode(session.child, deps.launchPermission))
) ||
structuredAgentSessionChildHasOpenWork(session.journal, deps.work) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export type RestTestAdapter = {
NonNullable<StructuredAgentSessionAdapter['acknowledgeSessionRelease']>
>
holdsDispatch: Mock<NonNullable<StructuredAgentSessionAdapter['holdsDispatch']>>
startUnavailable: Mock<NonNullable<StructuredAgentSessionAdapter['startUnavailable']>>
readOptions: Mock<NonNullable<StructuredAgentSessionAdapter['readOptions']>>
}

Expand Down Expand Up @@ -166,7 +165,6 @@ export async function createRestTestRig(
dispatch: vi.fn(async () => acceptedDispatch()),
acknowledgeSessionRelease: vi.fn(),
holdsDispatch: vi.fn(() => false),
startUnavailable: vi.fn(() => undefined),
readOptions: vi.fn(async () => ({ models: [], current: { model: 'gpt-live' } }))
}
const starts = structuredAgentSessionHeldStarts(() => rig.host)
Expand Down
Loading
Loading