| import { |
| AGENT_GRAPH_SUPERVISOR_WAKE_SCHEMA_VERSION, |
| type AgentRunHeader, |
| type AgentGraphSupervisorWakeRecord, |
| type AgentGraphSupervisorWakeStore, |
| type SessionEvent, |
| type UserMessageInput, |
| } from '@maka/core'; |
| import type { |
| GoalTurnOutcome, |
| SessionActivityLease, |
| SessionActivityRegistry, |
| } from './goal-turn-lifecycle.js'; |
| import { isContextOverflowErrorText } from './provider-error-classification.js'; |
| import type { AgentGraphClientSnapshot } from './stream-graph-read-model.js'; |
| import type { AgentGraphScheduleReconciliationResult } from './stream-graph-schedule-reconcile.js'; |
| |
| const DEFAULT_MAX_DELIVERY_ATTEMPTS = 3; |
| const MAX_PARTIAL_WORK_ITEMS = 32; |
| const MAX_PARTIAL_RECORD_IDS = 32; |
| |
| export interface AgentGraphSupervisorPartialResult { |
| schemaVersion: 1; |
| graphId: string; |
| snapshotVersion: string; |
| status: AgentGraphClientSnapshot['status']; |
| closed: boolean; |
| scheduleRevision: number; |
| work: Array<{ |
| workId: string; |
| status: AgentGraphClientSnapshot['work'][number]['status']; |
| target: AgentGraphClientSnapshot['work'][number]['target']; |
| replaces?: string; |
| }>; |
| terminalRecordIds: string[]; |
| omitted: { |
| work: number; |
| terminalRecordIds: number; |
| }; |
| } |
| |
| export class AgentGraphSupervisorContextOverflowError extends Error { |
| readonly code = 'agent_graph_supervisor_context_overflow'; |
| readonly partialResult: AgentGraphSupervisorPartialResult; |
| readonly recoveryAttempted: boolean; |
| readonly recoveryFailure?: string; |
| |
| constructor(input: { |
| partialResult: AgentGraphSupervisorPartialResult; |
| recoveryAttempted: boolean; |
| recoveryFailure?: string; |
| }) { |
| const recovery = input.recoveryFailure |
| ? ` Recovery failed: ${input.recoveryFailure}.` |
| : input.recoveryAttempted |
| ? ' Aggressive history compaction did not restore capacity.' |
| : ' No overflow recovery was available.'; |
| super( |
| `Agent graph supervisor context overflow; graph remains durable and recoverable.${recovery} Partial result: ${JSON.stringify( |
| input.partialResult, |
| )}`, |
| ); |
| this.name = 'AgentGraphSupervisorContextOverflowError'; |
| this.partialResult = input.partialResult; |
| this.recoveryAttempted = input.recoveryAttempted; |
| if (input.recoveryFailure !== undefined) this.recoveryFailure = input.recoveryFailure; |
| } |
| } |
| |
| export interface AgentGraphSupervisorContextRecoveryDiagnostic { |
| estimatedTokensBefore?: number; |
| estimatedTokensAfter?: number; |
| droppedTurns?: number; |
| droppedEvents?: number; |
| historyCompactedEvents?: number; |
| historyCompactBlocksWritten?: number; |
| } |
| |
| export type AgentGraphSupervisorTurnOutcome = |
| | GoalTurnOutcome |
| | { kind: 'context_overflow'; turnId: string; reason: string } |
| | { kind: 'superseded'; turnId: string; reason: string }; |
| |
| export async function recoverAgentGraphSupervisorContextOverflow(input: { |
| rootSessionId: string; |
| compactTurnId: string; |
| abortSignal: AbortSignal; |
| compactSession( |
| sessionId: string, |
| input: { turnId: string; minRecentTurns: number }, |
| ): AsyncIterable<SessionEvent>; |
| }): Promise<AgentGraphSupervisorContextRecoveryDiagnostic | undefined> { |
| input.abortSignal.throwIfAborted(); |
| let recovery: AgentGraphSupervisorContextRecoveryDiagnostic | undefined; |
| for await (const event of input.compactSession(input.rootSessionId, { |
| turnId: input.compactTurnId, |
| minRecentTurns: 0, |
| })) { |
| input.abortSignal.throwIfAborted(); |
| if (event.type !== 'token_usage' || !event.contextBudget) continue; |
| const diagnostic = event.contextBudget; |
| recovery = { |
| estimatedTokensBefore: diagnostic.estimatedTokensBefore, |
| estimatedTokensAfter: diagnostic.estimatedTokensAfter, |
| droppedTurns: diagnostic.droppedTurns, |
| droppedEvents: diagnostic.droppedEvents, |
| ...(diagnostic.historyCompactedEvents !== undefined |
| ? { historyCompactedEvents: diagnostic.historyCompactedEvents } |
| : {}), |
| ...(diagnostic.historyCompactBlocksWritten !== undefined |
| ? { historyCompactBlocksWritten: diagnostic.historyCompactBlocksWritten } |
| : {}), |
| }; |
| } |
| input.abortSignal.throwIfAborted(); |
| return recovery; |
| } |
| |
| export type AgentGraphSupervisorWakeDiagnostic = |
| | { |
| event: 'context_overflow_detected'; |
| graphId: string; |
| wakeId: string; |
| attemptId: string; |
| attempt: number; |
| maxAttempts: number; |
| recoveryAvailable: boolean; |
| recoveryAlreadyAttempted: boolean; |
| } |
| | { |
| event: 'context_overflow_recovery_completed'; |
| graphId: string; |
| wakeId: string; |
| attemptId: string; |
| recovery?: AgentGraphSupervisorContextRecoveryDiagnostic; |
| } |
| | { |
| event: 'context_overflow_recovery_failed'; |
| graphId: string; |
| wakeId: string; |
| attemptId: string; |
| failureReason: string; |
| } |
| | { |
| event: 'context_overflow_exhausted'; |
| graphId: string; |
| wakeId: string; |
| recoveryAttempted: boolean; |
| partial: { |
| status: AgentGraphSupervisorPartialResult['status']; |
| workItems: number; |
| terminalRecordIds: number; |
| omittedWorkItems: number; |
| omittedTerminalRecordIds: number; |
| }; |
| }; |
| |
| export interface AgentGraphSupervisorWakeInput { |
| activityRegistry: SessionActivityRegistry; |
| wakeStore: AgentGraphSupervisorWakeStore; |
| readSnapshot(rootSessionId: string): Promise<AgentGraphClientSnapshot>; |
| startTurn( |
| sessionId: string, |
| input: UserMessageInput, |
| activity: SessionActivityLease, |
| abortSignal: AbortSignal, |
| isCurrent: () => Promise<boolean>, |
| ): Promise<AgentGraphSupervisorTurnOutcome>; |
| inspectAttempt( |
| rootSessionId: string, |
| attemptId: string, |
| turnId: string, |
| ): Promise<AgentRunHeader['status'] | 'missing'>; |
| shouldWake?( |
| rootSessionId: string, |
| result: AgentGraphScheduleReconciliationResult | undefined, |
| snapshot: AgentGraphClientSnapshot, |
| ): boolean | undefined | Promise<boolean | undefined>; |
| renderWake?( |
| rootSessionId: string, |
| snapshot: AgentGraphClientSnapshot, |
| result?: AgentGraphScheduleReconciliationResult, |
| ): |
| | { |
| text: string; |
| displayText: string; |
| orchestrationMode: 'graph' | 'swarm'; |
| } |
| | undefined |
| | Promise< |
| | { |
| text: string; |
| displayText: string; |
| orchestrationMode: 'graph' | 'swarm'; |
| } |
| | undefined |
| >; |
| recoverContextOverflow?( |
| rootSessionId: string, |
| input: { |
| graphId: string; |
| wakeId: string; |
| attemptId: string; |
| turnId: string; |
| failureReason: string; |
| abortSignal: AbortSignal; |
| }, |
| ): Promise<AgentGraphSupervisorContextRecoveryDiagnostic | void>; |
| newId(): string; |
| isSessionDeliverable?(rootSessionId: string): Promise<boolean>; |
| /** Keep an external host alive while a durable wake is admitted or delivered. */ |
| acquireResidency?(rootSessionId: string): SessionActivityLease; |
| maxDeliveryAttempts?: number; |
| onDiagnostic?(diagnostic: AgentGraphSupervisorWakeDiagnostic): void | Promise<void>; |
| onError?(rootSessionId: string, error: unknown): void | Promise<void>; |
| } |
| |
| /** |
| * Host-side control-plane bridge from graph quiescence back to the root |
| * supervisor Agent. |
| * |
| * SQLite owns wake admission and delivery state. A persisted prompt or Run is |
| * only an attempt: the wake becomes delivered after the host observes a |
| * completed root turn. Interrupted attempts remain retryable across callbacks |
| * and process recovery. |
| */ |
| export class AgentGraphSupervisorWakeCoordinator { |
| readonly #input: AgentGraphSupervisorWakeInput; |
| readonly #tasks = new Set<Promise<void>>(); |
| readonly #pendingWakeIds = new Set<string>(); |
| readonly #abortController = new AbortController(); |
| readonly #maxDeliveryAttempts: number; |
| #closed = false; |
| |
| constructor(input: AgentGraphSupervisorWakeInput) { |
| this.#input = input; |
| this.#maxDeliveryAttempts = input.maxDeliveryAttempts ?? DEFAULT_MAX_DELIVERY_ATTEMPTS; |
| if (!Number.isSafeInteger(this.#maxDeliveryAttempts) || this.#maxDeliveryAttempts < 1) { |
| throw new Error('Agent graph supervisor wake attempts must be a positive safe integer'); |
| } |
| } |
| |
| notify( |
| rootSessionId: string, |
| result?: AgentGraphScheduleReconciliationResult, |
| ): Promise<void> | undefined { |
| if ( |
| this.#closed || |
| (!this.#input.shouldWake && (!result || !isAgentGraphSupervisorMilestone(result))) |
| ) { |
| return undefined; |
| } |
| return this.#runTracked(rootSessionId, async () => { |
| try { |
| await this.#wake(rootSessionId, result); |
| } catch (error) { |
| if (!this.#closed && !isAbortError(error)) { |
| await notifyError(this.#input.onError, rootSessionId, error); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Reconciles a parked wake after the user answers a permission prompt. |
| * |
| * A live waiter keeps the original startTurn activity lease until the same |
| * attempt settles. If the stream already ended suspended, admission here |
| * proves that waiter is gone and makes the wake eligible for a fresh turn. |
| */ |
| notifyPermissionResponse(rootSessionId: string): Promise<void> | undefined { |
| if (this.#closed) return undefined; |
| return this.#runTracked(rootSessionId, async () => { |
| try { |
| await this.#settlePermissionResponse(rootSessionId); |
| } catch (error) { |
| if (!this.#closed && !isAbortError(error)) { |
| await notifyError(this.#input.onError, rootSessionId, error); |
| } |
| } |
| }); |
| } |
| |
| /** Converges persisted wakes from AgentRun facts and resumes only safe retries. */ |
| async recover(): Promise<number> { |
| if (this.#closed) return 0; |
| let recovered = await this.#input.wakeStore.recoverAgentGraphSupervisorWakes(); |
| for (const wake of await this.#input.wakeStore.listUnsettledAgentGraphSupervisorWakes()) { |
| if (this.#closed) return recovered; |
| recovered += await this.#recoverUnsettledWake(wake); |
| } |
| if (this.#closed) return recovered; |
| for (const wake of await this.#input.wakeStore.listRetryableAgentGraphSupervisorWakes()) { |
| this.#scheduleRecoveredWake(wake); |
| } |
| return recovered; |
| } |
| |
| async waitForIdle(): Promise<void> { |
| while (this.#tasks.size > 0) await Promise.all([...this.#tasks]); |
| } |
| |
| hasLiveSessionState(rootSessionId: string): boolean { |
| return this.#input.activityRegistry.whenIdle(rootSessionId) !== undefined; |
| } |
| |
| async retireSessions(rootSessionIds: readonly string[]): Promise<number> { |
| return this.#input.wakeStore.supersedeAgentGraphSupervisorWakes({ |
| rootSessionIds, |
| reason: 'session_retired', |
| }); |
| } |
| |
| async close(): Promise<void> { |
| if (this.#closed) return; |
| this.#closed = true; |
| this.#abortController.abort(); |
| await this.waitForIdle(); |
| } |
| |
| async #wake( |
| rootSessionId: string, |
| result?: AgentGraphScheduleReconciliationResult, |
| ): Promise<void> { |
| if (!(await this.#isSessionDeliverable(rootSessionId))) return; |
| const snapshot = await this.#input.readSnapshot(rootSessionId); |
| const wakeDecision = await this.#input.shouldWake?.(rootSessionId, result, snapshot); |
| if ( |
| wakeDecision === false || |
| (wakeDecision === undefined && (!result || !isAgentGraphSupervisorMilestone(result))) |
| ) { |
| return; |
| } |
| if (this.#closed || snapshot.closed || snapshot.scheduleRevision === 0) return; |
| const wakeId = `${snapshot.graphId}:${snapshot.snapshotVersion}`; |
| if (this.#pendingWakeIds.has(wakeId)) return; |
| this.#pendingWakeIds.add(wakeId); |
| try { |
| const claimed = await this.#input.wakeStore.claimAgentGraphSupervisorWake({ |
| schemaVersion: AGENT_GRAPH_SUPERVISOR_WAKE_SCHEMA_VERSION, |
| graphId: snapshot.graphId, |
| wakeId, |
| snapshotVersion: snapshot.snapshotVersion, |
| rootSessionId, |
| }); |
| if (this.#closed || claimed.wake.status === 'delivered') return; |
| await this.#deliverWake(claimed.wake, snapshot, result); |
| } finally { |
| this.#pendingWakeIds.delete(wakeId); |
| } |
| } |
| |
| #scheduleRecoveredWake(wake: AgentGraphSupervisorWakeRecord): void { |
| if (this.#closed || this.#pendingWakeIds.has(wake.wakeId)) return; |
| this.#pendingWakeIds.add(wake.wakeId); |
| void this.#runTracked(wake.rootSessionId, async () => { |
| try { |
| await this.#resumeWake(wake); |
| } catch (error) { |
| if (!this.#closed && !isAbortError(error)) { |
| await notifyError(this.#input.onError, wake.rootSessionId, error); |
| } |
| } finally { |
| this.#pendingWakeIds.delete(wake.wakeId); |
| } |
| }); |
| } |
| |
| #runTracked(rootSessionId: string, operation: () => Promise<void>): Promise<void> { |
| const residency = this.#input.acquireResidency?.(rootSessionId); |
| const task = Promise.resolve() |
| .then(operation) |
| .finally(() => residency?.release()); |
| this.#tasks.add(task); |
| void task.then( |
| () => this.#tasks.delete(task), |
| () => this.#tasks.delete(task), |
| ); |
| return task; |
| } |
| |
| async #resumeWake(wake: AgentGraphSupervisorWakeRecord): Promise<void> { |
| if (!(await this.#isSessionDeliverable(wake.rootSessionId))) { |
| await this.#supersedeSession(wake.rootSessionId, 'session_unavailable'); |
| return; |
| } |
| const snapshot = await this.#input.readSnapshot(wake.rootSessionId); |
| if ( |
| this.#closed || |
| snapshot.closed || |
| snapshot.graphId !== wake.graphId || |
| snapshot.scheduleRevision === 0 |
| ) { |
| return; |
| } |
| await this.#deliverWake(wake, snapshot); |
| } |
| |
| async #deliverWake( |
| wake: AgentGraphSupervisorWakeRecord, |
| snapshot: AgentGraphClientSnapshot, |
| result?: AgentGraphScheduleReconciliationResult, |
| ): Promise<void> { |
| const presentation = (await this.#input.renderWake?.(wake.rootSessionId, snapshot, result)) ?? { |
| text: renderAgentGraphSupervisorWakePrompt(snapshot, result), |
| displayText: 'Agent graph reached a supervisor checkpoint.', |
| orchestrationMode: 'graph' as const, |
| }; |
| let lastFailure: string | undefined; |
| let overflowRecoveryAttempted = false; |
| for (let index = 0; index < this.#maxDeliveryAttempts; index += 1) { |
| if (!(await this.#isSessionDeliverable(wake.rootSessionId))) { |
| await this.#supersedeSession(wake.rootSessionId, 'session_unavailable'); |
| return; |
| } |
| let overflowAttempt: { attemptId: string; turnId: string; failureReason: string } | undefined; |
| const activity = await this.#input.activityRegistry.acquire( |
| wake.rootSessionId, |
| this.#abortController.signal, |
| ); |
| try { |
| if (this.#closed) return; |
| const attemptId = this.#input.newId(); |
| const turnId = this.#input.newId(); |
| const admission = await this.#input.wakeStore.beginAgentGraphSupervisorWakeAttempt({ |
| graphId: wake.graphId, |
| wakeId: wake.wakeId, |
| attemptId, |
| turnId, |
| }); |
| if (!admission.acquired) return; |
| if (this.#closed) { |
| await this.#markRetryable(wake.graphId, wake.wakeId, attemptId, 'host_shutdown'); |
| return; |
| } |
| |
| try { |
| const outcome = await this.#input.startTurn( |
| wake.rootSessionId, |
| { |
| turnId, |
| text: presentation.text, |
| displayText: presentation.displayText, |
| turnOrchestration: { mode: presentation.orchestrationMode, source: 'host_api' }, |
| origin: { |
| kind: 'agent_graph', |
| graphId: wake.graphId, |
| wakeId: wake.wakeId, |
| attemptId, |
| }, |
| }, |
| activity, |
| this.#abortController.signal, |
| () => this.#isWakeCurrent(wake), |
| ); |
| if (outcome.kind === 'completed') { |
| await this.#input.wakeStore.completeAgentGraphSupervisorWakeAttempt({ |
| graphId: wake.graphId, |
| wakeId: wake.wakeId, |
| attemptId, |
| status: 'delivered', |
| }); |
| return; |
| } |
| if (outcome.kind === 'superseded') { |
| await this.#input.wakeStore.completeAgentGraphSupervisorWakeAttempt({ |
| graphId: wake.graphId, |
| wakeId: wake.wakeId, |
| attemptId, |
| status: 'superseded', |
| failureReason: outcome.reason, |
| }); |
| return; |
| } |
| if (outcome.kind === 'suspended') { |
| await this.#input.wakeStore.completeAgentGraphSupervisorWakeAttempt({ |
| graphId: wake.graphId, |
| wakeId: wake.wakeId, |
| attemptId, |
| status: 'waiting_permission', |
| }); |
| return; |
| } |
| lastFailure = wakeOutcomeFailure(outcome); |
| await this.#markRetryable(wake.graphId, wake.wakeId, attemptId, lastFailure); |
| if (outcome.kind === 'context_overflow' || isSupervisorContextOverflow(lastFailure)) { |
| overflowAttempt = { attemptId, turnId, failureReason: lastFailure }; |
| } |
| } catch (error) { |
| lastFailure = errorMessage(error); |
| await this.#markRetryable(wake.graphId, wake.wakeId, attemptId, lastFailure); |
| if (isSupervisorContextOverflow(lastFailure)) { |
| overflowAttempt = { attemptId, turnId, failureReason: lastFailure }; |
| } |
| } |
| } finally { |
| activity.release(); |
| } |
| if (this.#closed) return; |
| if (overflowAttempt) { |
| const canRecover = |
| !overflowRecoveryAttempted && |
| index + 1 < this.#maxDeliveryAttempts && |
| this.#input.recoverContextOverflow !== undefined; |
| await emitWakeDiagnostic(this.#input.onDiagnostic, { |
| event: 'context_overflow_detected', |
| graphId: wake.graphId, |
| wakeId: wake.wakeId, |
| attemptId: overflowAttempt.attemptId, |
| attempt: index + 1, |
| maxAttempts: this.#maxDeliveryAttempts, |
| recoveryAvailable: this.#input.recoverContextOverflow !== undefined, |
| recoveryAlreadyAttempted: overflowRecoveryAttempted, |
| }); |
| if (!canRecover) { |
| const overflowError = await this.#contextOverflowError(wake.rootSessionId, snapshot, { |
| recoveryAttempted: overflowRecoveryAttempted, |
| }); |
| await emitWakeDiagnostic( |
| this.#input.onDiagnostic, |
| exhaustedDiagnostic(wake, overflowError), |
| ); |
| throw overflowError; |
| } |
| overflowRecoveryAttempted = true; |
| try { |
| const recovery = await this.#input.recoverContextOverflow!(wake.rootSessionId, { |
| graphId: wake.graphId, |
| wakeId: wake.wakeId, |
| ...overflowAttempt, |
| abortSignal: this.#abortController.signal, |
| }); |
| await emitWakeDiagnostic(this.#input.onDiagnostic, { |
| event: 'context_overflow_recovery_completed', |
| graphId: wake.graphId, |
| wakeId: wake.wakeId, |
| attemptId: overflowAttempt.attemptId, |
| ...(recovery ? { recovery } : {}), |
| }); |
| } catch (error) { |
| if (this.#closed || isAbortError(error)) return; |
| const failureReason = errorMessage(error); |
| await emitWakeDiagnostic(this.#input.onDiagnostic, { |
| event: 'context_overflow_recovery_failed', |
| graphId: wake.graphId, |
| wakeId: wake.wakeId, |
| attemptId: overflowAttempt.attemptId, |
| failureReason: failureReason.slice(0, 1_000), |
| }); |
| const overflowError = await this.#contextOverflowError(wake.rootSessionId, snapshot, { |
| recoveryAttempted: true, |
| recoveryFailure: failureReason, |
| }); |
| await emitWakeDiagnostic( |
| this.#input.onDiagnostic, |
| exhaustedDiagnostic(wake, overflowError), |
| ); |
| throw overflowError; |
| } |
| } |
| if (this.#closed) return; |
| } |
| throw new Error( |
| `Agent graph supervisor wake was not delivered after ${this.#maxDeliveryAttempts} attempts: ${ |
| lastFailure ?? 'unknown failure' |
| }`, |
| ); |
| } |
| |
| async #contextOverflowError( |
| rootSessionId: string, |
| fallbackSnapshot: AgentGraphClientSnapshot, |
| input: { recoveryAttempted: boolean; recoveryFailure?: string }, |
| ): Promise<AgentGraphSupervisorContextOverflowError> { |
| let currentSnapshot = fallbackSnapshot; |
| try { |
| currentSnapshot = await this.#input.readSnapshot(rootSessionId); |
| } catch { |
| // The checkpoint snapshot is already durable and sufficient for a bounded fallback. |
| } |
| return new AgentGraphSupervisorContextOverflowError({ |
| partialResult: projectAgentGraphSupervisorPartialResult(currentSnapshot), |
| ...input, |
| }); |
| } |
| |
| async #markRetryable( |
| graphId: string, |
| wakeId: string, |
| attemptId: string, |
| failureReason: string, |
| ): Promise<AgentGraphSupervisorWakeRecord> { |
| return this.#input.wakeStore.completeAgentGraphSupervisorWakeAttempt({ |
| graphId, |
| wakeId, |
| attemptId, |
| status: 'retryable_failed', |
| failureReason: failureReason.slice(0, 4_000) || 'unknown failure', |
| }); |
| } |
| |
| async #isSessionDeliverable(rootSessionId: string): Promise<boolean> { |
| return (await this.#input.isSessionDeliverable?.(rootSessionId)) ?? true; |
| } |
| |
| async #isWakeCurrent(wake: AgentGraphSupervisorWakeRecord): Promise<boolean> { |
| if (!(await this.#isSessionDeliverable(wake.rootSessionId))) return false; |
| const snapshot = await this.#input.readSnapshot(wake.rootSessionId); |
| return ( |
| !snapshot.closed && |
| snapshot.graphId === wake.graphId && |
| `${snapshot.graphId}:${snapshot.snapshotVersion}` === wake.wakeId |
| ); |
| } |
| |
| #supersedeSession(rootSessionId: string, reason: string): Promise<number> { |
| return this.#input.wakeStore.supersedeAgentGraphSupervisorWakes({ |
| rootSessionIds: [rootSessionId], |
| reason, |
| }); |
| } |
| |
| async #recoverUnsettledWake(wake: AgentGraphSupervisorWakeRecord): Promise<number> { |
| const attemptId = wake.currentAttemptId; |
| const turnId = wake.currentTurnId; |
| if (!attemptId || !turnId) { |
| throw new Error( |
| `Unsettled Agent graph supervisor wake ${wake.graphId}/${wake.wakeId} has no current attempt`, |
| ); |
| } |
| const runStatus = await this.#input.inspectAttempt(wake.rootSessionId, attemptId, turnId); |
| if (runStatus === 'completed') { |
| await this.#input.wakeStore.completeAgentGraphSupervisorWakeAttempt({ |
| graphId: wake.graphId, |
| wakeId: wake.wakeId, |
| attemptId, |
| status: 'delivered', |
| }); |
| return 1; |
| } |
| await this.#markRetryable( |
| wake.graphId, |
| wake.wakeId, |
| attemptId, |
| runStatus === 'failed' || runStatus === 'cancelled' |
| ? `agent_run_${runStatus}` |
| : `host_restart:${runStatus}`, |
| ); |
| return 1; |
| } |
| |
| async #settlePermissionResponse(rootSessionId: string): Promise<void> { |
| const activity = await this.#input.activityRegistry.acquire( |
| rootSessionId, |
| this.#abortController.signal, |
| ); |
| const retryable: AgentGraphSupervisorWakeRecord[] = []; |
| try { |
| if (this.#closed) return; |
| const unsettled = ( |
| await this.#input.wakeStore.listUnsettledAgentGraphSupervisorWakes() |
| ).filter((wake) => wake.rootSessionId === rootSessionId); |
| for (const wake of unsettled) { |
| const attemptId = wake.currentAttemptId; |
| const turnId = wake.currentTurnId; |
| if (!attemptId || !turnId) { |
| throw new Error( |
| `Parked Agent graph supervisor wake ${wake.graphId}/${wake.wakeId} has no current attempt`, |
| ); |
| } |
| const runStatus = await this.#input.inspectAttempt(rootSessionId, attemptId, turnId); |
| if (runStatus === 'completed') { |
| await this.#input.wakeStore.completeAgentGraphSupervisorWakeAttempt({ |
| graphId: wake.graphId, |
| wakeId: wake.wakeId, |
| attemptId, |
| status: 'delivered', |
| }); |
| continue; |
| } |
| retryable.push( |
| await this.#markRetryable( |
| wake.graphId, |
| wake.wakeId, |
| attemptId, |
| `permission_waiter_lost:${runStatus}`, |
| ), |
| ); |
| } |
| } finally { |
| activity.release(); |
| } |
| for (const wake of retryable) this.#scheduleRecoveredWake(wake); |
| } |
| } |
| |
| export function isAgentGraphSupervisorMilestone( |
| result: AgentGraphScheduleReconciliationResult, |
| ): boolean { |
| if ( |
| result.status === 'cancelled' || |
| result.status === 'stale' || |
| result.status === 'limit_reached' |
| ) { |
| return false; |
| } |
| return result.dispatches.length > 0 || result.failures.length > 0; |
| } |
| |
| function wakeOutcomeFailure( |
| outcome: Exclude<AgentGraphSupervisorTurnOutcome, { kind: 'completed' | 'superseded' }>, |
| ): string { |
| if (outcome.kind === 'context_overflow') return outcome.reason; |
| if (outcome.kind === 'errored' || outcome.kind === 'suspended') { |
| return `${outcome.kind}: ${outcome.reason}`; |
| } |
| return 'aborted'; |
| } |
| |
| function isSupervisorContextOverflow(failureReason: string): boolean { |
| const normalized = failureReason.toLowerCase(); |
| return ( |
| normalized.includes('context_overflow') || |
| normalized.includes('context window exceeded') || |
| normalized.includes('context budget exhausted') || |
| isContextOverflowErrorText(failureReason) |
| ); |
| } |
| |
| function projectAgentGraphSupervisorPartialResult( |
| snapshot: AgentGraphClientSnapshot, |
| ): AgentGraphSupervisorPartialResult { |
| const work = snapshot.work.slice(0, MAX_PARTIAL_WORK_ITEMS).map((item) => ({ |
| workId: item.workId, |
| status: item.status, |
| target: item.target, |
| ...(item.replaces !== undefined ? { replaces: item.replaces } : {}), |
| })); |
| const allTerminalRecordIds = [ |
| ...(snapshot.finish?.resultIds ?? []), |
| ...snapshot.terminalHistory.records.map((record) => record.recordId), |
| ].filter((recordId, index, recordIds) => recordIds.indexOf(recordId) === index); |
| const terminalRecordIds = allTerminalRecordIds.slice(0, MAX_PARTIAL_RECORD_IDS); |
| return { |
| schemaVersion: 1, |
| graphId: snapshot.graphId, |
| snapshotVersion: snapshot.snapshotVersion, |
| status: snapshot.status, |
| closed: snapshot.closed, |
| scheduleRevision: snapshot.scheduleRevision, |
| work, |
| terminalRecordIds, |
| omitted: { |
| work: snapshot.omitted.work + Math.max(0, snapshot.work.length - work.length), |
| terminalRecordIds: Math.max(0, allTerminalRecordIds.length - terminalRecordIds.length), |
| }, |
| }; |
| } |
| |
| function renderAgentGraphSupervisorWakePrompt( |
| snapshot: AgentGraphClientSnapshot, |
| result?: AgentGraphScheduleReconciliationResult, |
| ): string { |
| return [ |
| '<agent-graph-supervisor-checkpoint>', |
| `Graph ${snapshot.graphId} reached a durable supervisor checkpoint.`, |
| `Reconciliation status: ${result?.status ?? 'recovered'}. Snapshot: ${snapshot.snapshotVersion}.`, |
| 'Inspect the graph with view_agent_graph. Read child results with agent_output view=result; use raw event views only for narrow diagnostics.', |
| 'Then either schedule the next work with update_agent_graph or finish the graph with the selected result record IDs.', |
| 'If you schedule more work and no immediate supervisor decision remains, call yield_agent_graph. Do not poll or sleep while operators execute.', |
| 'Report the useful outcome to the user when the graph is complete.', |
| '</agent-graph-supervisor-checkpoint>', |
| ].join('\n'); |
| } |
| |
| function errorMessage(error: unknown): string { |
| return error instanceof Error ? error.message : String(error); |
| } |
| |
| function isAbortError(error: unknown): boolean { |
| return error instanceof Error && error.name === 'AbortError'; |
| } |
| |
| async function notifyError( |
| observer: AgentGraphSupervisorWakeInput['onError'], |
| rootSessionId: string, |
| error: unknown, |
| ): Promise<void> { |
| try { |
| await observer?.(rootSessionId, error); |
| } catch { |
| // Wake diagnostics must not become graph data-path failures. |
| } |
| } |
| |
| function exhaustedDiagnostic( |
| wake: AgentGraphSupervisorWakeRecord, |
| error: AgentGraphSupervisorContextOverflowError, |
| ): AgentGraphSupervisorWakeDiagnostic { |
| return { |
| event: 'context_overflow_exhausted', |
| graphId: wake.graphId, |
| wakeId: wake.wakeId, |
| recoveryAttempted: error.recoveryAttempted, |
| partial: { |
| status: error.partialResult.status, |
| workItems: error.partialResult.work.length, |
| terminalRecordIds: error.partialResult.terminalRecordIds.length, |
| omittedWorkItems: error.partialResult.omitted.work, |
| omittedTerminalRecordIds: error.partialResult.omitted.terminalRecordIds, |
| }, |
| }; |
| } |
| |
| async function emitWakeDiagnostic( |
| observer: AgentGraphSupervisorWakeInput['onDiagnostic'], |
| diagnostic: AgentGraphSupervisorWakeDiagnostic, |
| ): Promise<void> { |
| try { |
| await observer?.(diagnostic); |
| } catch { |
| // Wake diagnostics must never alter delivery or recovery correctness. |
| } |
| } |