mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 08:03:28 +00:00
fix(agent-sleep): keep active dispatch workers awake
This commit is contained in:
@@ -36036,6 +36036,7 @@ describe('OrcaRuntimeService', () => {
|
||||
expect(result.agentOrchestrationByPaneKey?.[workerPaneKey]).toMatchObject({
|
||||
taskId: 'task-1',
|
||||
dispatchId: 'ctx-1',
|
||||
dispatchStatus: 'dispatched',
|
||||
taskTitle: 'Dispatch prompt work',
|
||||
displayName: 'Review dispatch prompts and make worker labels distinct',
|
||||
parentPaneKey: coordinatorPaneKey,
|
||||
@@ -36113,6 +36114,7 @@ describe('OrcaRuntimeService', () => {
|
||||
expect(result.agentOrchestrationByPaneKey?.[workerPaneKey]).toMatchObject({
|
||||
taskId: 'task-done',
|
||||
dispatchId: 'ctx-done',
|
||||
dispatchStatus: 'completed',
|
||||
parentPaneKey: coordinatorPaneKey,
|
||||
parentTerminalHandle: coordinatorHandle
|
||||
})
|
||||
@@ -36170,7 +36172,8 @@ describe('OrcaRuntimeService', () => {
|
||||
|
||||
expect(result.agentOrchestrationByPaneKey?.[workerPaneKey]).toEqual({
|
||||
taskId: 'task-done',
|
||||
dispatchId: 'ctx-done'
|
||||
dispatchId: 'ctx-done',
|
||||
dispatchStatus: 'completed'
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -29198,6 +29198,7 @@ export class OrcaRuntimeService {
|
||||
return {
|
||||
taskId: dispatch.task_id,
|
||||
dispatchId: dispatch.id,
|
||||
dispatchStatus: dispatch.status,
|
||||
...(display.taskTitle ? { taskTitle: display.taskTitle } : {}),
|
||||
...(display.displayName ? { displayName: display.displayName } : {}),
|
||||
...(parentTerminalHandle ? { parentTerminalHandle } : {}),
|
||||
|
||||
@@ -356,6 +356,35 @@ describe('agent sleep coordinator', () => {
|
||||
expect(shutdown).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('rechecks dispatch settlement before shutdown', async () => {
|
||||
vi.useFakeTimers()
|
||||
const completed = {
|
||||
...entry(),
|
||||
orchestration: {
|
||||
taskId: 'task-1',
|
||||
dispatchId: 'ctx-1',
|
||||
dispatchStatus: 'completed' as const
|
||||
}
|
||||
}
|
||||
const shutdown = installEligibleState(vi.fn().mockResolvedValue(undefined), {
|
||||
agentStatusByPaneKey: { [completed.paneKey]: completed }
|
||||
})
|
||||
startAgentHibernationCoordinator({ intervalMs: 1000, now: () => NOW })
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1000)
|
||||
useAppStore.setState({
|
||||
agentStatusByPaneKey: {
|
||||
[completed.paneKey]: {
|
||||
...completed,
|
||||
orchestration: { ...completed.orchestration, dispatchStatus: 'dispatched' }
|
||||
}
|
||||
}
|
||||
})
|
||||
await vi.advanceTimersByTimeAsync(1000)
|
||||
|
||||
expect(shutdown).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('restarts confirmation when a foreground terminal visit refreshes idle state', async () => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(NOW)
|
||||
|
||||
@@ -134,6 +134,39 @@ describe('agent sleep planner', () => {
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
it.each([undefined, 'pending', 'dispatched'] as const)(
|
||||
'blocks orchestration panes while dispatch status is %s',
|
||||
(dispatchStatus) => {
|
||||
const orchestrated = entry({
|
||||
orchestration: {
|
||||
taskId: 'task-1',
|
||||
dispatchId: 'ctx-1',
|
||||
...(dispatchStatus ? { dispatchStatus } : {})
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
plannedPaneKeys(
|
||||
snapshot({ agentStatusByPaneKey: { [orchestrated.paneKey]: orchestrated } })
|
||||
)
|
||||
).toEqual([])
|
||||
}
|
||||
)
|
||||
|
||||
it('allows orchestration panes after authoritative dispatch settlement', () => {
|
||||
const orchestrated = entry({
|
||||
orchestration: {
|
||||
taskId: 'task-1',
|
||||
dispatchId: 'ctx-1',
|
||||
dispatchStatus: 'completed'
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
plannedPaneKeys(snapshot({ agentStatusByPaneKey: { [orchestrated.paneKey]: orchestrated } }))
|
||||
).toEqual([orchestrated.paneKey])
|
||||
})
|
||||
|
||||
it('requires the idle threshold and blocks input after done', () => {
|
||||
const fresh = entry({ updatedAt: NOW - 1_000 })
|
||||
expect(
|
||||
|
||||
@@ -110,6 +110,13 @@ function getEntryTabId(entry: AgentStatusEntry): string | null {
|
||||
return parsePaneKey(entry.paneKey)?.tabId ?? null
|
||||
}
|
||||
|
||||
function hasUnsettledOrUnknownDispatch({ orchestration }: AgentStatusEntry): boolean {
|
||||
// Why: provider done hooks can fire mid-Dispatch; only runtime-confirmed settlement makes sleep safe.
|
||||
return orchestration
|
||||
? !['completed', 'failed', 'circuit_broken'].includes(orchestration.dispatchStatus ?? '')
|
||||
: false
|
||||
}
|
||||
|
||||
function getEligiblePane(args: {
|
||||
entry: AgentStatusEntry
|
||||
tab: TerminalTab
|
||||
@@ -140,6 +147,7 @@ function getEligiblePane(args: {
|
||||
if (
|
||||
entry.state !== 'done' ||
|
||||
entry.interrupted === true ||
|
||||
hasUnsettledOrUnknownDispatch(entry) ||
|
||||
(sleepingRecord && !hasOnlyLivePiCompatibleRecoveryIdentity)
|
||||
) {
|
||||
return null
|
||||
|
||||
@@ -265,6 +265,36 @@ describe('agent status runtime orchestration metadata', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('updates runtime status for the same dispatch', () => {
|
||||
vi.useFakeTimers()
|
||||
const store = createTestStore()
|
||||
const childPaneKey = 'tab-child:11111111-1111-4111-8111-111111111111'
|
||||
|
||||
store.getState().setAgentStatus(childPaneKey, {
|
||||
state: 'done',
|
||||
prompt: 'child agent',
|
||||
agentType: 'claude',
|
||||
orchestration: {
|
||||
taskId: 'task-1',
|
||||
dispatchId: 'ctx-1',
|
||||
dispatchStatus: 'dispatched'
|
||||
}
|
||||
})
|
||||
store.getState().setRuntimeAgentOrchestrationByPaneKey({
|
||||
[childPaneKey]: {
|
||||
taskId: 'task-1',
|
||||
dispatchId: 'ctx-1',
|
||||
dispatchStatus: 'completed'
|
||||
}
|
||||
})
|
||||
|
||||
expect(store.getState().agentStatusByPaneKey[childPaneKey].orchestration).toMatchObject({
|
||||
taskId: 'task-1',
|
||||
dispatchId: 'ctx-1',
|
||||
dispatchStatus: 'completed'
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps current payload orchestration ahead of a stale runtime map entry', () => {
|
||||
vi.useFakeTimers()
|
||||
const store = createTestStore()
|
||||
|
||||
@@ -1090,6 +1090,7 @@ function orchestrationContextsEqual(
|
||||
return (
|
||||
a.taskId === b.taskId &&
|
||||
a.dispatchId === b.dispatchId &&
|
||||
a.dispatchStatus === b.dispatchStatus &&
|
||||
a.taskTitle === b.taskTitle &&
|
||||
a.displayName === b.displayName &&
|
||||
a.parentTerminalHandle === b.parentTerminalHandle &&
|
||||
|
||||
@@ -60,6 +60,8 @@ export const AGENT_STATE_HISTORY_MAX = 20
|
||||
export type AgentStatusOrchestrationContext = {
|
||||
taskId: string
|
||||
dispatchId: string
|
||||
/** Runtime-authoritative lifecycle state. Hook-only contexts may omit it. */
|
||||
dispatchStatus?: 'pending' | 'dispatched' | 'completed' | 'failed' | 'circuit_broken'
|
||||
taskTitle?: string
|
||||
displayName?: string
|
||||
parentTerminalHandle?: string
|
||||
|
||||
Reference in New Issue
Block a user