mirror of
https://github.com/stablyai/orca.git
synced 2026-09-28 00:02:41 +00:00
fix agent status identity recovery gaps
This commit is contained in:
@@ -88,6 +88,9 @@ export abstract class AgentHookServerIngestTerminal extends AgentHookServerInges
|
||||
) {
|
||||
// Why: OSC has no child identity or lead boundary, so it cannot replace a persisted child-only proof before the lifecycle hook arrives.
|
||||
this.commitStatusRowMutation(mutationBefore, previous)
|
||||
if (mutationBefore !== undefined) {
|
||||
this.emitEnrichedStatus(previous)
|
||||
}
|
||||
return
|
||||
}
|
||||
// Why: preserve the hook-completed turn stamp while OSC repaints the current state.
|
||||
|
||||
@@ -197,6 +197,31 @@ describe('the terminal handle a status row is stamped with', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('publishes only the remint observation for a Claude child-only row', () => {
|
||||
const server = new AgentHookServer()
|
||||
const enriched = vi.fn()
|
||||
server.subscribeEnrichedStatus(enriched)
|
||||
const payload = { state: 'working' as const, prompt: 'ship it', agentType: 'claude' as const }
|
||||
ingest(server, { payload })
|
||||
const row = server._getStateForTests().lastStatusByPaneKey.get(PANE_KEY) as
|
||||
| { claudeLeadBoundaryChildOnly?: true }
|
||||
| undefined
|
||||
if (!row) {
|
||||
throw new Error('expected seeded status row')
|
||||
}
|
||||
row.claudeLeadBoundaryChildOnly = true
|
||||
enriched.mockClear()
|
||||
|
||||
ingest(server, { payload })
|
||||
expect(enriched).not.toHaveBeenCalled()
|
||||
|
||||
ingest(server, { paneKey: NEW_PANE_KEY, tabId: 'tab-reminted', payload })
|
||||
expect(enriched).toHaveBeenCalledOnce()
|
||||
expect(enriched).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ paneKey: NEW_PANE_KEY, terminalHandle: HANDLE })
|
||||
)
|
||||
})
|
||||
|
||||
it('does not renew freshness from a provider-session-only dismissal remnant', () => {
|
||||
const server = new AgentHookServer()
|
||||
const freshness = vi.fn()
|
||||
|
||||
@@ -190,10 +190,10 @@ async function startOrcadRuntime(
|
||||
// which is safe but silently discards accept records on every launch.
|
||||
initSshHostKeyStoreFile(profile.dataFile)
|
||||
|
||||
uninstallObservedStatusIdentity = agentHookServer.subscribeEnrichedStatus((enriched) =>
|
||||
observedStatusCapture.observe(enriched)
|
||||
)
|
||||
if (isAgentStatusHooksEnabled(store.getSettings())) {
|
||||
uninstallObservedStatusIdentity = agentHookServer.subscribeEnrichedStatus((enriched) =>
|
||||
observedStatusCapture.observe(enriched)
|
||||
)
|
||||
await agentHookServer.start({ env: 'production', userDataPath: runtimeUserDataPath })
|
||||
}
|
||||
|
||||
@@ -258,9 +258,6 @@ async function startOrcadRuntime(
|
||||
// spawning an unauthenticated agent.
|
||||
await registerHeadlessPtyRuntime(runtime, undefined, () => store.getSettings(), undefined, store)
|
||||
|
||||
// PTY recovery binds terminal identities; only now can startup spool observations be fenced.
|
||||
observedStatusCapture.attach(runtime)
|
||||
|
||||
// Why: same post-registration reconciliation `--serve` performs. Skipping it leaves
|
||||
// restored orchestration rows claiming an authority this host never took over.
|
||||
// Why before the RPC server binds: a client host attaching first would find no pages to recover.
|
||||
@@ -269,6 +266,9 @@ async function startOrcadRuntime(
|
||||
await runtime.refreshRestoredOrchestrationAuthority()
|
||||
await runtime.reconcileLegacyWorkerTerminals()
|
||||
|
||||
// Recovery binds terminal and dispatch identities; only now can startup observations be fenced.
|
||||
observedStatusCapture.attach(runtime)
|
||||
|
||||
const bindHost = resolveOrcadBindHost(options.bind)
|
||||
rpc = new OrcaRuntimeRpcServer({
|
||||
runtime,
|
||||
|
||||
@@ -78,12 +78,14 @@ describe('headless PTY registry hydration ordering', () => {
|
||||
const runtime = source.indexOf('const runtime = new OrcaRuntimeService(')
|
||||
const identityReader = source.indexOf('readObservedAgentStatusPaneIdentity:', runtime)
|
||||
const identitySubscription = source.indexOf('agentHookServer.subscribeEnrichedStatus(')
|
||||
const hooksEnabled = source.indexOf('if (isAgentStatusHooksEnabled(', identitySubscription)
|
||||
const identityFlush = source.indexOf('observedStatusCapture.attach(runtime)', runtime)
|
||||
|
||||
expect(runtime).toBeGreaterThanOrEqual(0)
|
||||
expect(identityReader).toBeGreaterThan(runtime)
|
||||
expect(identitySubscription).toBeGreaterThanOrEqual(0)
|
||||
expect(identitySubscription).toBeLessThan(runtime)
|
||||
expect(hooksEnabled).toBeGreaterThan(identitySubscription)
|
||||
expect(identityFlush).toBeGreaterThan(runtime)
|
||||
expect(source.slice(identitySubscription, runtime)).toContain(
|
||||
'observedStatusCapture.observe(enriched)'
|
||||
@@ -96,13 +98,17 @@ describe('headless PTY registry hydration ordering', () => {
|
||||
const hookStart = source.indexOf('await agentHookServer.start(', subscription)
|
||||
const runtime = source.indexOf('const runtime = new OrcaRuntimeService(')
|
||||
const handlers = source.indexOf('await registerHeadlessPtyRuntime(', runtime)
|
||||
const identityRecovery = source.indexOf('await runtime.refreshRestoredOrchestrationAuthority()')
|
||||
const workerRecovery = source.indexOf('await runtime.reconcileLegacyWorkerTerminals()')
|
||||
const replay = source.indexOf('observedStatusCapture.attach(runtime)', runtime)
|
||||
|
||||
expect(subscription).toBeGreaterThanOrEqual(0)
|
||||
expect(hookStart).toBeGreaterThan(subscription)
|
||||
expect(runtime).toBeGreaterThan(hookStart)
|
||||
expect(handlers).toBeGreaterThan(runtime)
|
||||
expect(replay).toBeGreaterThan(handlers)
|
||||
expect(identityRecovery).toBeGreaterThan(handlers)
|
||||
expect(workerRecovery).toBeGreaterThan(identityRecovery)
|
||||
expect(replay).toBeGreaterThan(workerRecovery)
|
||||
expect(source.slice(subscription, runtime)).toContain('observedStatusCapture.observe(enriched)')
|
||||
expect(source.slice(replay)).toContain('observedStatusCapture.attach(runtime)')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user