@botbotgo/agent-harness 0.0.100 → 0.0.101

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,14 +10,14 @@ import { getConcurrencyConfig, getRecoveryConfig, getRoutingDefaultAgentId, getR
10
10
  import { createHarnessEvent, inferRoutingBindings, renderRuntimeFailure, renderToolFailure, } from "./support/harness-support.js";
11
11
  import { ThreadMemorySync } from "./harness/system/thread-memory-sync.js";
12
12
  import { FileBackedStore } from "./harness/system/store.js";
13
- import { CheckpointMaintenanceLoop, discoverCheckpointMaintenanceTargets, readCheckpointMaintenanceConfig, } from "./checkpoint-maintenance.js";
13
+ import { CheckpointMaintenanceLoop, discoverCheckpointMaintenanceTargets, readCheckpointMaintenanceConfig, } from "./maintenance/checkpoint-maintenance.js";
14
14
  import { RuntimeRecordMaintenanceLoop, discoverRuntimeRecordMaintenanceTargets, readRuntimeRecordMaintenanceConfig, } from "./maintenance/runtime-record-maintenance.js";
15
15
  import { HealthMonitor } from "./harness/system/health-monitor.js";
16
16
  import { readHealthMonitorConfig } from "./harness/system/health-monitor.js";
17
17
  import { extractMessageText, normalizeMessageContent } from "../utils/message-content.js";
18
18
  import { buildPersistedRunRequest, isTerminalRunState, normalizeInvocationEnvelope, normalizeRunPriority, resolveRunListeners, toPublicApprovalRecord, } from "./harness/run/helpers.js";
19
- import { emitHarnessEvent, emitRunCreatedEvent, emitSyntheticFallbackEvent, persistApproval, requestApprovalAndEmitEvent, setRunStateAndEmitEvent, } from "./harness/events/events.js";
20
- import { appendAssistantMessage as appendLifecycleAssistantMessage, checkpointRefForState as getCheckpointRefForRunState, expirePendingApprovals as expireLifecyclePendingApprovals, finalizeCancelledRun as finalizeLifecycleCancelledRun, finalizeContinuedRun as finalizeLifecycleContinuedRun, } from "./harness/run/run-lifecycle.js";
19
+ import { emitHarnessEvent, emitRunCreatedEvent, emitSyntheticFallbackEvent, requestApprovalAndEmitEvent, setRunStateAndEmitEvent, } from "./harness/events/events.js";
20
+ import { appendAssistantMessage as appendLifecycleAssistantMessage, finalizeCancelledRun as finalizeLifecycleCancelledRun, finalizeContinuedRun as finalizeLifecycleContinuedRun, } from "./harness/run/run-lifecycle.js";
21
21
  import { createContentBlocksItem as createStreamingContentBlocksItem, createToolResultKey as createStreamingToolResultKey, dispatchRunListeners as dispatchStreamingRunListeners, emitOutputDeltaAndCreateItem as emitStreamingOutputDeltaAndCreateItem, } from "./harness/events/streaming.js";
22
22
  import { buildResumePayload as buildHarnessResumePayload, resolveApprovalRecord as resolveHarnessApprovalRecord, } from "./harness/run/resume.js";
23
23
  import { dropPendingRunSlot, enqueuePendingRunSlot, shiftNextPendingRunSlot } from "./harness/run/run-queue.js";
@@ -28,6 +28,7 @@ import { getBindingAdapterKind, getBindingPrimaryTools, getBindingStoreConfig }
28
28
  import { isRuntimeEntryBinding } from "./support/runtime-entry.js";
29
29
  import { describeWorkspaceInventory, listAgentSkills as listWorkspaceAgentSkills, } from "./harness/system/inventory.js";
30
30
  import { createDefaultHealthSnapshot, isInventoryEnabled, isThreadMemorySyncEnabled, } from "./harness/runtime-defaults.js";
31
+ import { recoverQueuedStartupRun, recoverResumingStartupRun, recoverRunningStartupRun, } from "./harness/run/recovery.js";
31
32
  export class AgentHarnessRuntime {
32
33
  workspace;
33
34
  runtimeAdapterOptions;
@@ -85,42 +86,30 @@ export class AgentHarnessRuntime {
85
86
  getThreadSummary: (currentThreadId) => this.getSession(currentThreadId),
86
87
  });
87
88
  }
88
- resolveStore(binding) {
89
- return resolveStore(this.stores, this.defaultStore, this.defaultRunRoot(), (currentBinding) => currentBinding ? getBindingStoreConfig(currentBinding) : undefined, binding);
90
- }
91
- resolveStoreFromConfig(storeConfig, runRoot) {
92
- return resolveStoreFromConfig(this.stores, storeConfig, runRoot);
93
- }
94
- async resolveEmbeddingModel(embeddingModelRef) {
95
- return resolveEmbeddingModel(this.workspace, this.embeddingModels, embeddingModelRef, this.runtimeAdapterOptions);
96
- }
97
- async resolveVectorStore(vectorStoreRef) {
98
- return resolveVectorStore(this.workspace, this.vectorStores, vectorStoreRef, this.runtimeAdapterOptions);
99
- }
100
89
  constructor(workspace, runtimeAdapterOptions = {}) {
101
90
  this.workspace = workspace;
102
91
  this.runtimeAdapterOptions = runtimeAdapterOptions;
103
92
  const runRoot = this.defaultRunRoot();
104
93
  this.persistence = new SqlitePersistence(runRoot);
105
94
  const defaultStoreConfig = this.listHostBindings()[0]?.harnessRuntime.store;
106
- this.defaultStore = this.resolveStoreFromConfig(defaultStoreConfig, runRoot) ?? new FileBackedStore(`${runRoot}/store.json`);
95
+ this.defaultStore = resolveStoreFromConfig(this.stores, defaultStoreConfig, runRoot) ?? new FileBackedStore(`${runRoot}/store.json`);
107
96
  const runtimeMemoryStoreConfig = typeof this.listHostBindings()[0]?.harnessRuntime.runtimeMemory?.store === "object" &&
108
97
  this.listHostBindings()[0]?.harnessRuntime.runtimeMemory?.store
109
98
  ? this.listHostBindings()[0]?.harnessRuntime.runtimeMemory?.store
110
99
  : undefined;
111
- this.runtimeMemoryStore = this.resolveStoreFromConfig(runtimeMemoryStoreConfig, runRoot) ?? this.defaultStore;
100
+ this.runtimeMemoryStore = resolveStoreFromConfig(this.stores, runtimeMemoryStoreConfig, runRoot) ?? this.defaultStore;
112
101
  this.resolvedRuntimeAdapterOptions = {
113
102
  ...runtimeAdapterOptions,
114
103
  toolResolver: runtimeAdapterOptions.toolResolver ??
115
104
  createResourceToolResolver(workspace, {
116
- getStore: (binding) => this.resolveStore(binding),
117
- getEmbeddingModel: (embeddingModelRef) => this.resolveEmbeddingModel(embeddingModelRef),
118
- getVectorStore: (vectorStoreRef) => this.resolveVectorStore(vectorStoreRef),
105
+ getStore: (binding) => resolveStore(this.stores, this.defaultStore, this.defaultRunRoot(), (currentBinding) => currentBinding ? getBindingStoreConfig(currentBinding) : undefined, binding),
106
+ getEmbeddingModel: (embeddingModelRef) => resolveEmbeddingModel(this.workspace, this.embeddingModels, embeddingModelRef, this.runtimeAdapterOptions),
107
+ getVectorStore: (vectorStoreRef) => resolveVectorStore(this.workspace, this.vectorStores, vectorStoreRef, this.runtimeAdapterOptions),
119
108
  }),
120
109
  checkpointerResolver: runtimeAdapterOptions.checkpointerResolver ??
121
110
  ((binding) => resolveCheckpointer(this.checkpointers, binding)),
122
111
  storeResolver: runtimeAdapterOptions.storeResolver ??
123
- ((binding) => this.resolveStore(binding)),
112
+ ((binding) => resolveStore(this.stores, this.defaultStore, this.defaultRunRoot(), (currentBinding) => currentBinding ? getBindingStoreConfig(currentBinding) : undefined, binding)),
124
113
  backendResolver: runtimeAdapterOptions.backendResolver ??
125
114
  ((binding) => createResourceBackendResolver(workspace)(binding)),
126
115
  };
@@ -183,22 +172,12 @@ export class AgentHarnessRuntime {
183
172
  }
184
173
  return createDefaultHealthSnapshot(this.activeRunSlots, this.pendingRunSlots.length);
185
174
  }
186
- getBinding(agentId) {
187
- return this.workspace.bindings.get(agentId);
188
- }
189
- listAgentTools(agentId) {
190
- const binding = this.getBinding(agentId);
191
- if (!binding) {
192
- throw new Error(`Unknown agent ${agentId}`);
193
- }
194
- return getBindingPrimaryTools(binding);
195
- }
196
175
  resolveAgentTools(agentId) {
197
- const binding = this.getBinding(agentId);
176
+ const binding = this.workspace.bindings.get(agentId);
198
177
  if (!binding) {
199
178
  throw new Error(`Unknown agent ${agentId}`);
200
179
  }
201
- const compiledTools = this.listAgentTools(agentId);
180
+ const compiledTools = getBindingPrimaryTools(binding);
202
181
  const resolver = this.resolvedRuntimeAdapterOptions.toolResolver;
203
182
  const resolvedTools = resolver ? resolver(compiledTools.map((tool) => tool.id), binding) : [];
204
183
  return compiledTools.map((compiledTool, index) => ({
@@ -416,9 +395,6 @@ export class AgentHarnessRuntime {
416
395
  const userTurn = history.find((message) => message.runId === runId && message.role === "user");
417
396
  return userTurn?.content ?? "";
418
397
  }
419
- async appendAssistantMessage(threadId, runId, content) {
420
- return appendLifecycleAssistantMessage(this.persistence, threadId, runId, content);
421
- }
422
398
  async getRunCancellation(runId) {
423
399
  const control = await this.persistence.getRunControl(runId);
424
400
  return {
@@ -426,12 +402,6 @@ export class AgentHarnessRuntime {
426
402
  ...(control?.cancelReason ? { reason: control.cancelReason } : {}),
427
403
  };
428
404
  }
429
- async expirePendingApprovals(threadId, runId) {
430
- return expireLifecyclePendingApprovals({
431
- persistence: this.persistence,
432
- emit: (currentThreadId, currentRunId, sequence, eventType, payload, source) => this.emit(currentThreadId, currentRunId, sequence, eventType, payload, source),
433
- }, threadId, runId);
434
- }
435
405
  async finalizeCancelledRun(threadId, runId, previousState, reason) {
436
406
  return finalizeLifecycleCancelledRun({
437
407
  persistence: this.persistence,
@@ -507,7 +477,12 @@ export class AgentHarnessRuntime {
507
477
  };
508
478
  }
509
479
  catch (error) {
510
- await this.emitSyntheticFallback(threadId, runId, agentId, error, 103);
480
+ await emitSyntheticFallbackEvent({
481
+ persistence: this.persistence,
482
+ publishEvent: (event) => this.eventBus.publish(event),
483
+ trackBackgroundTask: (task) => this.trackBackgroundTask(task),
484
+ backgroundEventTypes: AgentHarnessRuntime.BACKGROUND_EVENT_TYPES,
485
+ }, threadId, runId, agentId, error, 103);
511
486
  await this.setRunStateAndEmit(threadId, runId, 104, "failed", {
512
487
  previousState: previousState === "queued" ? "running" : previousState,
513
488
  error: error instanceof Error ? error.message : String(error),
@@ -524,9 +499,6 @@ export class AgentHarnessRuntime {
524
499
  await this.persistence.clearRunRequest(threadId, runId);
525
500
  }
526
501
  }
527
- checkpointRefForState(threadId, runId, state) {
528
- return getCheckpointRefForRunState(threadId, runId, state);
529
- }
530
502
  async finalizeContinuedRun(binding, threadId, runId, input, actual, options) {
531
503
  return finalizeLifecycleContinuedRun({
532
504
  persistence: this.persistence,
@@ -535,23 +507,6 @@ export class AgentHarnessRuntime {
535
507
  requestApprovalAndEmit: (currentThreadId, currentRunId, lifecycleInput, interruptContent, checkpointRef, sequence) => this.requestApprovalAndEmit(currentThreadId, currentRunId, lifecycleInput, interruptContent, checkpointRef, sequence),
536
508
  }, binding, threadId, runId, input, actual, options);
537
509
  }
538
- async emitOutputDeltaAndCreateItem(threadId, runId, agentId, content) {
539
- return emitStreamingOutputDeltaAndCreateItem((currentThreadId, currentRunId, sequence, eventType, payload) => this.emit(currentThreadId, currentRunId, sequence, eventType, payload), threadId, runId, agentId, content);
540
- }
541
- createContentBlocksItem(threadId, runId, agentId, contentBlocks) {
542
- return createStreamingContentBlocksItem(threadId, runId, agentId, contentBlocks);
543
- }
544
- createToolResultKey(toolName, output, isError) {
545
- return createStreamingToolResultKey(toolName, output, isError);
546
- }
547
- async emitRunCreated(threadId, runId, payload) {
548
- return emitRunCreatedEvent({
549
- persistence: this.persistence,
550
- publishEvent: (event) => this.eventBus.publish(event),
551
- trackBackgroundTask: (task) => this.trackBackgroundTask(task),
552
- backgroundEventTypes: AgentHarnessRuntime.BACKGROUND_EVENT_TYPES,
553
- }, threadId, runId, payload);
554
- }
555
510
  async setRunStateAndEmit(threadId, runId, sequence, state, options) {
556
511
  return setRunStateAndEmitEvent({
557
512
  persistence: this.persistence,
@@ -568,25 +523,6 @@ export class AgentHarnessRuntime {
568
523
  backgroundEventTypes: AgentHarnessRuntime.BACKGROUND_EVENT_TYPES,
569
524
  }, threadId, runId, input, interruptContent, checkpointRef, sequence);
570
525
  }
571
- async emitSyntheticFallback(threadId, runId, selectedAgentId, error, sequence = 3) {
572
- await emitSyntheticFallbackEvent({
573
- persistence: this.persistence,
574
- publishEvent: (event) => this.eventBus.publish(event),
575
- trackBackgroundTask: (task) => this.trackBackgroundTask(task),
576
- backgroundEventTypes: AgentHarnessRuntime.BACKGROUND_EVENT_TYPES,
577
- }, threadId, runId, selectedAgentId, error, sequence);
578
- }
579
- async persistApproval(threadId, runId, checkpointRef, input, interruptContent) {
580
- return persistApproval({
581
- persistence: this.persistence,
582
- publishEvent: (event) => this.eventBus.publish(event),
583
- trackBackgroundTask: (task) => this.trackBackgroundTask(task),
584
- backgroundEventTypes: AgentHarnessRuntime.BACKGROUND_EVENT_TYPES,
585
- }, threadId, runId, checkpointRef, input, interruptContent);
586
- }
587
- async resolveApprovalRecord(options, thread) {
588
- return resolveHarnessApprovalRecord(this.persistence, options, thread);
589
- }
590
526
  isDecisionRun(options) {
591
527
  return "decision" in options;
592
528
  }
@@ -596,6 +532,55 @@ export class AgentHarnessRuntime {
596
532
  }
597
533
  await listener(value);
598
534
  }
535
+ async prepareRunStart(options, invocation, runCreatedPayload) {
536
+ const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
537
+ const binding = this.workspace.bindings.get(selectedAgentId);
538
+ if (!binding) {
539
+ throw new Error(`Unknown agent ${selectedAgentId}`);
540
+ }
541
+ const policyDecision = this.policyEngine.evaluate(binding);
542
+ if (!policyDecision.allowed) {
543
+ throw new Error(`Policy evaluation blocked agent ${selectedAgentId}: ${policyDecision.reasons.join(", ")}`);
544
+ }
545
+ const priority = normalizeRunPriority(options.priority);
546
+ const runRequest = buildPersistedRunRequest(options.input, invocation, priority);
547
+ const { threadId, runId, isNewThread } = await this.ensureThreadStarted(selectedAgentId, binding, options.input, runRequest, options.threadId);
548
+ return {
549
+ binding,
550
+ selectedAgentId,
551
+ priority,
552
+ threadId,
553
+ runId,
554
+ isNewThread,
555
+ runCreatedEventPromise: emitRunCreatedEvent({
556
+ persistence: this.persistence,
557
+ publishEvent: (event) => this.eventBus.publish(event),
558
+ trackBackgroundTask: (task) => this.trackBackgroundTask(task),
559
+ backgroundEventTypes: AgentHarnessRuntime.BACKGROUND_EVENT_TYPES,
560
+ }, threadId, runId, runCreatedPayload(binding, selectedAgentId)),
561
+ releaseRunSlotPromise: this.acquireRunSlot(threadId, runId, "running", priority),
562
+ };
563
+ }
564
+ createStartupRecoveryContext() {
565
+ return {
566
+ persistence: this.persistence,
567
+ workspace: this.workspace,
568
+ runtimeAdapter: this.runtimeAdapter,
569
+ recoveryConfig: this.recoveryConfig,
570
+ concurrencyConfig: this.concurrencyConfig,
571
+ getBinding: (agentId) => this.workspace.bindings.get(agentId),
572
+ acquireRunSlot: (threadId, runId, activeState = "running", priority = 0) => this.acquireRunSlot(threadId, runId, activeState, priority),
573
+ executeQueuedRun: (binding, input, threadId, runId, agentId, options = {}) => this.executeQueuedRun(binding, input, threadId, runId, agentId, options),
574
+ setRunStateAndEmit: (threadId, runId, sequence, state, options) => this.setRunStateAndEmit(threadId, runId, sequence, state, options),
575
+ emit: (threadId, runId, sequence, eventType, payload) => this.emit(threadId, runId, sequence, eventType, payload),
576
+ loadRunInput: (threadId, runId) => this.loadRunInput(threadId, runId),
577
+ finalizeContinuedRun: (binding, threadId, runId, input, actual, options) => this.finalizeContinuedRun(binding, threadId, runId, input, actual, options),
578
+ supportsRunningReplay: (binding) => this.supportsRunningReplay(binding),
579
+ isStaleRunningRun: (thread, nowMs = Date.now()) => this.isStaleRunningRun(thread, nowMs),
580
+ recordLlmSuccess: (startedAt) => this.recordLlmSuccess(startedAt),
581
+ recordLlmFailure: (startedAt) => this.recordLlmFailure(startedAt),
582
+ };
583
+ }
599
584
  async acquireRunSlot(threadId, runId, activeState = "running", priority = 0) {
600
585
  let stopHeartbeat = () => undefined;
601
586
  const beginLease = async (mode) => {
@@ -776,32 +761,12 @@ export class AgentHarnessRuntime {
776
761
  return this.dispatchRunListeners(this.streamEvents(options), resolvedListeners);
777
762
  }
778
763
  const invocation = normalizeInvocationEnvelope(options);
779
- const selectedAgentId = await resolveSelectedAgentId({
780
- workspace: this.workspace,
781
- input: options.input,
782
- requestedAgentId: options.agentId,
783
- threadId: options.threadId,
784
- preferredHostAgentId: this.routingDefaultAgentId ?? AgentHarnessRuntime.DEFAULT_HOST_AGENT_ID,
785
- getThreadSummary: (threadId) => this.getSession(threadId),
786
- });
787
- const binding = this.workspace.bindings.get(selectedAgentId);
788
- if (!binding) {
789
- throw new Error(`Unknown agent ${selectedAgentId}`);
790
- }
791
- const policyDecision = this.policyEngine.evaluate(binding);
792
- if (!policyDecision.allowed) {
793
- throw new Error(`Policy evaluation blocked agent ${selectedAgentId}: ${policyDecision.reasons.join(", ")}`);
794
- }
795
- const priority = normalizeRunPriority(options.priority);
796
- const runRequest = buildPersistedRunRequest(options.input, invocation, priority);
797
- const { threadId, runId, isNewThread } = await this.ensureThreadStarted(selectedAgentId, binding, options.input, runRequest, options.threadId);
798
- const runCreatedEventPromise = this.emitRunCreated(threadId, runId, {
799
- agentId: binding.agent.id,
764
+ const { binding, selectedAgentId, threadId, runId, isNewThread, runCreatedEventPromise, releaseRunSlotPromise, } = await this.prepareRunStart(options, invocation, (activeBinding, activeSelectedAgentId) => ({
765
+ agentId: activeBinding.agent.id,
800
766
  requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
801
- selectedAgentId,
802
- executionMode: getBindingAdapterKind(binding),
803
- });
804
- const releaseRunSlotPromise = this.acquireRunSlot(threadId, runId, "running", priority);
767
+ selectedAgentId: activeSelectedAgentId,
768
+ executionMode: getBindingAdapterKind(activeBinding),
769
+ }));
805
770
  await runCreatedEventPromise;
806
771
  const releaseRunSlot = await releaseRunSlotPromise;
807
772
  try {
@@ -821,14 +786,7 @@ export class AgentHarnessRuntime {
821
786
  }
822
787
  async *streamEvents(options) {
823
788
  const invocation = normalizeInvocationEnvelope(options);
824
- const selectedAgentId = await resolveSelectedAgentId({
825
- workspace: this.workspace,
826
- input: options.input,
827
- requestedAgentId: options.agentId,
828
- threadId: options.threadId,
829
- preferredHostAgentId: this.routingDefaultAgentId ?? AgentHarnessRuntime.DEFAULT_HOST_AGENT_ID,
830
- getThreadSummary: (threadId) => this.getSession(threadId),
831
- });
789
+ const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
832
790
  const binding = this.workspace.bindings.get(selectedAgentId);
833
791
  if (!binding) {
834
792
  const result = await this.run(options);
@@ -845,268 +803,268 @@ export class AgentHarnessRuntime {
845
803
  }
846
804
  let emitted = false;
847
805
  let streamActivityObserved = false;
848
- const priority = normalizeRunPriority(options.priority);
849
- const runRequest = buildPersistedRunRequest(options.input, invocation, priority);
850
- const { threadId, runId, isNewThread } = await this.ensureThreadStarted(selectedAgentId, binding, options.input, runRequest, options.threadId);
851
- const priorHistoryPromise = Promise.resolve(isNewThread ? [] : undefined).then((historyHint) => historyHint ?? this.loadPriorHistory(threadId, runId));
852
- const runCreatedEventPromise = this.emitRunCreated(threadId, runId, {
853
- agentId: selectedAgentId,
806
+ const { threadId, runId, isNewThread, runCreatedEventPromise, releaseRunSlotPromise, } = await this.prepareRunStart(options, invocation, (_binding, activeSelectedAgentId) => ({
807
+ agentId: activeSelectedAgentId,
854
808
  requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
855
- selectedAgentId,
809
+ selectedAgentId: activeSelectedAgentId,
856
810
  input: options.input,
857
811
  state: "running",
858
- });
812
+ }));
813
+ const priorHistoryPromise = Promise.resolve(isNewThread ? [] : undefined).then((historyHint) => historyHint ?? this.loadPriorHistory(threadId, runId));
859
814
  yield { type: "event", event: await runCreatedEventPromise };
860
- const releaseRunSlotPromise = this.acquireRunSlot(threadId, runId, "running", priority);
861
815
  let releaseRunSlot = async () => undefined;
816
+ const emitOutputDelta = (content) => emitStreamingOutputDeltaAndCreateItem((currentThreadId, currentRunId, sequence, eventType, payload) => this.emit(currentThreadId, currentRunId, sequence, eventType, payload), threadId, runId, selectedAgentId, content);
862
817
  try {
863
- try {
864
- const [priorHistory, acquiredReleaseRunSlot] = await Promise.all([
865
- priorHistoryPromise,
866
- releaseRunSlotPromise,
867
- ]).then(([loadedPriorHistory, resolvedReleaseRunSlot]) => [loadedPriorHistory, resolvedReleaseRunSlot]);
868
- releaseRunSlot = acquiredReleaseRunSlot;
869
- let assistantOutput = "";
870
- const toolErrors = [];
871
- let lastToolResultKey = null;
872
- for await (const chunk of this.runtimeAdapter.stream(binding, options.input, threadId, priorHistory, {
873
- context: invocation.context,
874
- state: invocation.state,
875
- files: invocation.files,
876
- runId,
877
- })) {
878
- if (chunk) {
879
- streamActivityObserved = true;
880
- const normalizedChunk = typeof chunk === "string"
881
- ? chunk.startsWith(AGENT_INTERRUPT_SENTINEL_PREFIX)
882
- ? { kind: "interrupt", content: chunk.slice(AGENT_INTERRUPT_SENTINEL_PREFIX.length) }
883
- : { kind: "content", content: chunk }
884
- : chunk;
885
- if (normalizedChunk.kind === "upstream-event") {
886
- yield {
887
- type: "upstream-event",
888
- threadId,
889
- runId,
890
- agentId: selectedAgentId,
891
- event: normalizedChunk.event.format === "langgraph-v2"
892
- ? normalizedChunk.event
893
- : normalizeUpstreamRuntimeEvent(normalizedChunk.event.raw ?? normalizedChunk.event),
894
- };
895
- continue;
896
- }
897
- if (normalizedChunk.kind === "interrupt") {
898
- const checkpointRef = `checkpoints/${threadId}/${runId}/cp-1`;
899
- const waitingEvent = await this.setRunStateAndEmit(threadId, runId, 6, "waiting_for_approval", {
900
- previousState: "running",
901
- checkpointRef,
902
- });
903
- const approvalRequest = await this.requestApprovalAndEmit(threadId, runId, options.input, normalizedChunk.content, checkpointRef, 7);
904
- yield {
905
- type: "event",
906
- event: waitingEvent,
907
- };
908
- yield {
909
- type: "event",
910
- event: approvalRequest.event,
911
- };
912
- yield {
913
- type: "result",
914
- result: {
915
- threadId,
916
- runId,
917
- agentId: selectedAgentId,
918
- state: "waiting_for_approval",
919
- output: assistantOutput,
920
- finalMessageText: assistantOutput,
921
- interruptContent: normalizedChunk.content,
922
- approvalId: approvalRequest.approval.approvalId,
923
- pendingActionId: approvalRequest.approval.pendingActionId,
924
- },
925
- };
926
- return;
927
- }
928
- if (normalizedChunk.kind === "reasoning") {
929
- await this.emit(threadId, runId, 3, "reasoning.delta", {
930
- content: normalizedChunk.content,
931
- });
932
- yield {
933
- type: "reasoning",
934
- threadId,
935
- runId,
936
- agentId: selectedAgentId,
937
- content: normalizedChunk.content,
938
- };
939
- continue;
940
- }
941
- if (normalizedChunk.kind === "step") {
942
- yield {
943
- type: "step",
818
+ const [priorHistory, acquiredReleaseRunSlot] = await Promise.all([
819
+ priorHistoryPromise,
820
+ releaseRunSlotPromise,
821
+ ]).then(([loadedPriorHistory, resolvedReleaseRunSlot]) => [loadedPriorHistory, resolvedReleaseRunSlot]);
822
+ releaseRunSlot = acquiredReleaseRunSlot;
823
+ let assistantOutput = "";
824
+ const toolErrors = [];
825
+ let lastToolResultKey = null;
826
+ for await (const chunk of this.runtimeAdapter.stream(binding, options.input, threadId, priorHistory, {
827
+ context: invocation.context,
828
+ state: invocation.state,
829
+ files: invocation.files,
830
+ runId,
831
+ })) {
832
+ if (chunk) {
833
+ streamActivityObserved = true;
834
+ const normalizedChunk = typeof chunk === "string"
835
+ ? chunk.startsWith(AGENT_INTERRUPT_SENTINEL_PREFIX)
836
+ ? { kind: "interrupt", content: chunk.slice(AGENT_INTERRUPT_SENTINEL_PREFIX.length) }
837
+ : { kind: "content", content: chunk }
838
+ : chunk;
839
+ if (normalizedChunk.kind === "upstream-event") {
840
+ yield {
841
+ type: "upstream-event",
842
+ threadId,
843
+ runId,
844
+ agentId: selectedAgentId,
845
+ event: normalizedChunk.event.format === "langgraph-v2"
846
+ ? normalizedChunk.event
847
+ : normalizeUpstreamRuntimeEvent(normalizedChunk.event.raw ?? normalizedChunk.event),
848
+ };
849
+ continue;
850
+ }
851
+ if (normalizedChunk.kind === "interrupt") {
852
+ const checkpointRef = `checkpoints/${threadId}/${runId}/cp-1`;
853
+ const waitingEvent = await this.setRunStateAndEmit(threadId, runId, 6, "waiting_for_approval", {
854
+ previousState: "running",
855
+ checkpointRef,
856
+ });
857
+ const approvalRequest = await this.requestApprovalAndEmit(threadId, runId, options.input, normalizedChunk.content, checkpointRef, 7);
858
+ yield {
859
+ type: "event",
860
+ event: waitingEvent,
861
+ };
862
+ yield {
863
+ type: "event",
864
+ event: approvalRequest.event,
865
+ };
866
+ yield {
867
+ type: "result",
868
+ result: {
944
869
  threadId,
945
870
  runId,
946
871
  agentId: selectedAgentId,
947
- content: normalizedChunk.content,
948
- };
872
+ state: "waiting_for_approval",
873
+ output: assistantOutput,
874
+ finalMessageText: assistantOutput,
875
+ interruptContent: normalizedChunk.content,
876
+ approvalId: approvalRequest.approval.approvalId,
877
+ pendingActionId: approvalRequest.approval.pendingActionId,
878
+ },
879
+ };
880
+ return;
881
+ }
882
+ if (normalizedChunk.kind === "reasoning") {
883
+ await this.emit(threadId, runId, 3, "reasoning.delta", {
884
+ content: normalizedChunk.content,
885
+ });
886
+ yield {
887
+ type: "reasoning",
888
+ threadId,
889
+ runId,
890
+ agentId: selectedAgentId,
891
+ content: normalizedChunk.content,
892
+ };
893
+ continue;
894
+ }
895
+ if (normalizedChunk.kind === "step") {
896
+ yield {
897
+ type: "step",
898
+ threadId,
899
+ runId,
900
+ agentId: selectedAgentId,
901
+ content: normalizedChunk.content,
902
+ };
903
+ continue;
904
+ }
905
+ if (normalizedChunk.kind === "tool-result") {
906
+ const toolResultKey = createStreamingToolResultKey(normalizedChunk.toolName, normalizedChunk.output, normalizedChunk.isError);
907
+ if (toolResultKey === lastToolResultKey) {
949
908
  continue;
950
909
  }
951
- if (normalizedChunk.kind === "tool-result") {
952
- const toolResultKey = this.createToolResultKey(normalizedChunk.toolName, normalizedChunk.output, normalizedChunk.isError);
953
- if (toolResultKey === lastToolResultKey) {
954
- continue;
955
- }
956
- lastToolResultKey = toolResultKey;
957
- if (normalizedChunk.isError) {
958
- toolErrors.push(renderToolFailure(normalizedChunk.toolName, normalizedChunk.output));
959
- }
960
- yield {
961
- type: "tool-result",
962
- threadId,
963
- runId,
964
- agentId: selectedAgentId,
965
- toolName: normalizedChunk.toolName,
966
- output: normalizedChunk.output,
967
- isError: normalizedChunk.isError,
968
- };
969
- continue;
910
+ lastToolResultKey = toolResultKey;
911
+ if (normalizedChunk.isError) {
912
+ toolErrors.push(renderToolFailure(normalizedChunk.toolName, normalizedChunk.output));
970
913
  }
971
- emitted = true;
972
- assistantOutput += normalizedChunk.content;
973
- yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, normalizedChunk.content);
914
+ yield {
915
+ type: "tool-result",
916
+ threadId,
917
+ runId,
918
+ agentId: selectedAgentId,
919
+ toolName: normalizedChunk.toolName,
920
+ output: normalizedChunk.output,
921
+ isError: normalizedChunk.isError,
922
+ };
923
+ continue;
974
924
  }
975
- }
976
- if (!assistantOutput && toolErrors.length > 0) {
977
- assistantOutput = toolErrors.join("\n\n");
978
925
  emitted = true;
979
- yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, assistantOutput);
926
+ assistantOutput += normalizedChunk.content;
927
+ yield await emitOutputDelta(normalizedChunk.content);
980
928
  }
981
- if (!assistantOutput) {
982
- const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
983
- if (Array.isArray(actual.contentBlocks) && actual.contentBlocks.length > 0) {
984
- yield this.createContentBlocksItem(threadId, runId, selectedAgentId, actual.contentBlocks);
985
- }
986
- if (actual.output) {
987
- assistantOutput = actual.output;
988
- emitted = true;
989
- yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, actual.output);
990
- }
929
+ }
930
+ if (!assistantOutput && toolErrors.length > 0) {
931
+ assistantOutput = toolErrors.join("\n\n");
932
+ emitted = true;
933
+ yield await emitOutputDelta(assistantOutput);
934
+ }
935
+ if (!assistantOutput) {
936
+ const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
937
+ if (Array.isArray(actual.contentBlocks) && actual.contentBlocks.length > 0) {
938
+ yield createStreamingContentBlocksItem(threadId, runId, selectedAgentId, actual.contentBlocks);
991
939
  }
992
- await this.appendAssistantMessage(threadId, runId, assistantOutput);
940
+ if (actual.output) {
941
+ assistantOutput = actual.output;
942
+ emitted = true;
943
+ yield await emitOutputDelta(actual.output);
944
+ }
945
+ }
946
+ await appendLifecycleAssistantMessage(this.persistence, threadId, runId, assistantOutput);
947
+ yield {
948
+ type: "result",
949
+ result: {
950
+ threadId,
951
+ runId,
952
+ agentId: selectedAgentId,
953
+ state: "completed",
954
+ output: assistantOutput,
955
+ finalMessageText: assistantOutput,
956
+ },
957
+ };
958
+ yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 6, "completed", {
959
+ previousState: "running",
960
+ }) };
961
+ return;
962
+ }
963
+ catch (error) {
964
+ if (emitted || streamActivityObserved) {
965
+ const runtimeFailure = renderRuntimeFailure(error);
966
+ yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 6, "failed", {
967
+ previousState: "running",
968
+ error: error instanceof Error ? error.message : String(error),
969
+ }) };
970
+ yield {
971
+ type: "content",
972
+ threadId,
973
+ runId,
974
+ agentId: selectedAgentId,
975
+ content: runtimeFailure,
976
+ };
993
977
  yield {
994
978
  type: "result",
995
979
  result: {
996
980
  threadId,
997
981
  runId,
998
982
  agentId: selectedAgentId,
999
- state: "completed",
1000
- output: assistantOutput,
1001
- finalMessageText: assistantOutput,
983
+ state: "failed",
984
+ output: runtimeFailure,
985
+ finalMessageText: runtimeFailure,
1002
986
  },
1003
987
  };
1004
- yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 6, "completed", {
1005
- previousState: "running",
1006
- }) };
1007
988
  return;
1008
989
  }
1009
- catch (error) {
1010
- if (emitted || streamActivityObserved) {
1011
- const runtimeFailure = renderRuntimeFailure(error);
1012
- yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 6, "failed", {
1013
- previousState: "running",
1014
- error: error instanceof Error ? error.message : String(error),
1015
- }) };
1016
- yield {
1017
- type: "content",
990
+ if (error instanceof RuntimeOperationTimeoutError && error.stage === "invoke") {
991
+ yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 6, "failed", {
992
+ previousState: "running",
993
+ error: error.message,
994
+ }) };
995
+ yield {
996
+ type: "content",
997
+ threadId,
998
+ runId,
999
+ agentId: selectedAgentId,
1000
+ content: renderRuntimeFailure(error),
1001
+ };
1002
+ yield {
1003
+ type: "result",
1004
+ result: {
1018
1005
  threadId,
1019
1006
  runId,
1020
1007
  agentId: selectedAgentId,
1021
- content: runtimeFailure,
1022
- };
1023
- yield {
1024
- type: "result",
1025
- result: {
1026
- threadId,
1027
- runId,
1028
- agentId: selectedAgentId,
1029
- state: "failed",
1030
- output: runtimeFailure,
1031
- finalMessageText: runtimeFailure,
1032
- },
1033
- };
1034
- return;
1008
+ state: "failed",
1009
+ output: renderRuntimeFailure(error),
1010
+ finalMessageText: renderRuntimeFailure(error),
1011
+ },
1012
+ };
1013
+ return;
1014
+ }
1015
+ try {
1016
+ const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
1017
+ await appendLifecycleAssistantMessage(this.persistence, threadId, runId, actual.output);
1018
+ if (Array.isArray(actual.contentBlocks) && actual.contentBlocks.length > 0) {
1019
+ yield createStreamingContentBlocksItem(threadId, runId, selectedAgentId, actual.contentBlocks);
1035
1020
  }
1036
- if (error instanceof RuntimeOperationTimeoutError && error.stage === "invoke") {
1037
- yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 6, "failed", {
1038
- previousState: "running",
1039
- error: error.message,
1040
- }) };
1041
- yield {
1042
- type: "content",
1021
+ if (actual.output) {
1022
+ yield await emitOutputDelta(actual.output);
1023
+ }
1024
+ yield {
1025
+ type: "result",
1026
+ result: {
1027
+ ...actual,
1043
1028
  threadId,
1044
1029
  runId,
1045
1030
  agentId: selectedAgentId,
1046
- content: renderRuntimeFailure(error),
1047
- };
1048
- yield {
1049
- type: "result",
1050
- result: {
1051
- threadId,
1052
- runId,
1053
- agentId: selectedAgentId,
1054
- state: "failed",
1055
- output: renderRuntimeFailure(error),
1056
- finalMessageText: renderRuntimeFailure(error),
1057
- },
1058
- };
1059
- return;
1060
- }
1061
- try {
1062
- const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
1063
- await this.appendAssistantMessage(threadId, runId, actual.output);
1064
- if (Array.isArray(actual.contentBlocks) && actual.contentBlocks.length > 0) {
1065
- yield this.createContentBlocksItem(threadId, runId, selectedAgentId, actual.contentBlocks);
1066
- }
1067
- if (actual.output) {
1068
- yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, actual.output);
1069
- }
1070
- yield {
1071
- type: "result",
1072
- result: {
1073
- ...actual,
1074
- threadId,
1075
- runId,
1076
- agentId: selectedAgentId,
1077
- },
1078
- };
1079
- yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 6, actual.state, {
1080
- previousState: "running",
1081
- }) };
1082
- return;
1083
- }
1084
- catch (invokeError) {
1085
- await this.emitSyntheticFallback(threadId, runId, selectedAgentId, invokeError);
1086
- yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 6, "failed", {
1087
- previousState: "running",
1088
- error: invokeError instanceof Error ? invokeError.message : String(invokeError),
1089
- }) };
1090
- yield {
1091
- type: "content",
1031
+ },
1032
+ };
1033
+ yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 6, actual.state, {
1034
+ previousState: "running",
1035
+ }) };
1036
+ return;
1037
+ }
1038
+ catch (invokeError) {
1039
+ await emitSyntheticFallbackEvent({
1040
+ persistence: this.persistence,
1041
+ publishEvent: (event) => this.eventBus.publish(event),
1042
+ trackBackgroundTask: (task) => this.trackBackgroundTask(task),
1043
+ backgroundEventTypes: AgentHarnessRuntime.BACKGROUND_EVENT_TYPES,
1044
+ }, threadId, runId, selectedAgentId, invokeError);
1045
+ yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 6, "failed", {
1046
+ previousState: "running",
1047
+ error: invokeError instanceof Error ? invokeError.message : String(invokeError),
1048
+ }) };
1049
+ yield {
1050
+ type: "content",
1051
+ threadId,
1052
+ runId,
1053
+ agentId: selectedAgentId,
1054
+ content: renderRuntimeFailure(invokeError),
1055
+ };
1056
+ yield {
1057
+ type: "result",
1058
+ result: {
1092
1059
  threadId,
1093
1060
  runId,
1094
1061
  agentId: selectedAgentId,
1095
- content: renderRuntimeFailure(invokeError),
1096
- };
1097
- yield {
1098
- type: "result",
1099
- result: {
1100
- threadId,
1101
- runId,
1102
- agentId: selectedAgentId,
1103
- state: "failed",
1104
- output: renderRuntimeFailure(invokeError),
1105
- finalMessageText: renderRuntimeFailure(invokeError),
1106
- },
1107
- };
1108
- return;
1109
- }
1062
+ state: "failed",
1063
+ output: renderRuntimeFailure(invokeError),
1064
+ finalMessageText: renderRuntimeFailure(invokeError),
1065
+ },
1066
+ };
1067
+ return;
1110
1068
  }
1111
1069
  }
1112
1070
  finally {
@@ -1124,14 +1082,14 @@ export class AgentHarnessRuntime {
1124
1082
  if (!thread) {
1125
1083
  throw new Error("resume requires either threadId or approvalId");
1126
1084
  }
1127
- const approval = approvalById ?? await this.resolveApprovalRecord(options, thread);
1085
+ const approval = approvalById ?? await resolveHarnessApprovalRecord(this.persistence, options, thread);
1128
1086
  const threadId = approval.threadId;
1129
1087
  const runId = approval.runId;
1130
1088
  const binding = this.workspace.bindings.get(thread.agentId);
1131
1089
  if (!binding) {
1132
1090
  throw new Error(`Unknown agent ${thread.agentId}`);
1133
1091
  }
1134
- const resumePayload = this.buildResumePayload(binding, approval, options);
1092
+ const resumePayload = buildHarnessResumePayload(binding, approval, options);
1135
1093
  const cancellation = await this.getRunCancellation(runId);
1136
1094
  if (cancellation.requested) {
1137
1095
  return this.finalizeCancelledRun(threadId, runId, thread.status, cancellation.reason);
@@ -1192,9 +1150,6 @@ export class AgentHarnessRuntime {
1192
1150
  await releaseRunSlot();
1193
1151
  }
1194
1152
  }
1195
- buildResumePayload(binding, approval, options) {
1196
- return buildHarnessResumePayload(binding, approval, options);
1197
- }
1198
1153
  async restartConversation(options) {
1199
1154
  const thread = await this.getSession(options.threadId);
1200
1155
  if (!thread) {
@@ -1270,136 +1225,14 @@ export class AgentHarnessRuntime {
1270
1225
  }
1271
1226
  await this.reclaimExpiredClaimedRuns();
1272
1227
  const threads = await this.persistence.listSessions();
1228
+ const recoveryContext = this.createStartupRecoveryContext();
1273
1229
  for (const thread of threads) {
1274
- if (thread.status === "queued") {
1275
- const runMeta = await this.persistence.getRunMeta(thread.threadId, thread.latestRunId);
1276
- const binding = this.workspace.bindings.get(runMeta.agentId);
1277
- if (!binding) {
1278
- continue;
1279
- }
1280
- const request = await this.persistence.getRunRequest(thread.threadId, thread.latestRunId);
1281
- if (!request) {
1282
- await this.setRunStateAndEmit(thread.threadId, thread.latestRunId, 100, "failed", {
1283
- previousState: "queued",
1284
- error: "missing persisted run request for queued run recovery",
1285
- });
1286
- continue;
1287
- }
1288
- const releaseRunSlot = await this.acquireRunSlot(thread.threadId, thread.latestRunId, "running", normalizeRunPriority(request.priority));
1289
- try {
1290
- await this.executeQueuedRun(binding, request.input, thread.threadId, thread.latestRunId, runMeta.agentId, {
1291
- context: request.invocation?.context,
1292
- state: request.invocation?.inputs,
1293
- files: request.invocation?.attachments,
1294
- previousState: "queued",
1295
- stateSequence: 103,
1296
- approvalSequence: 104,
1297
- });
1298
- }
1299
- finally {
1300
- await releaseRunSlot();
1301
- }
1302
- continue;
1303
- }
1304
- if (thread.status === "running") {
1305
- const isStale = await this.isStaleRunningRun(thread);
1306
- if (!isStale) {
1307
- continue;
1308
- }
1309
- const runMeta = await this.persistence.getRunMeta(thread.threadId, thread.latestRunId);
1310
- const binding = this.workspace.bindings.get(runMeta.agentId);
1311
- if (!binding) {
1312
- continue;
1313
- }
1314
- if (!this.supportsRunningReplay(binding)) {
1315
- await this.setRunStateAndEmit(thread.threadId, thread.latestRunId, 100, "failed", {
1316
- previousState: "running",
1317
- error: "stale running run cannot be replayed safely",
1318
- });
1319
- await this.persistence.releaseRunClaim(thread.latestRunId);
1320
- continue;
1321
- }
1322
- const request = await this.persistence.getRunRequest(thread.threadId, thread.latestRunId);
1323
- if (!request) {
1324
- await this.setRunStateAndEmit(thread.threadId, thread.latestRunId, 100, "failed", {
1325
- previousState: "running",
1326
- error: "missing persisted run request for stale running run recovery",
1327
- });
1328
- await this.persistence.releaseRunClaim(thread.latestRunId);
1329
- continue;
1330
- }
1331
- const releaseRunSlot = await this.acquireRunSlot(thread.threadId, thread.latestRunId, "running", normalizeRunPriority(request.priority));
1332
- try {
1333
- await this.emit(thread.threadId, thread.latestRunId, 100, "run.resumed", {
1334
- resumeKind: "startup-running-recovery",
1335
- state: "running",
1336
- });
1337
- await this.executeQueuedRun(binding, request.input, thread.threadId, thread.latestRunId, runMeta.agentId, {
1338
- context: request.invocation?.context,
1339
- state: request.invocation?.inputs,
1340
- files: request.invocation?.attachments,
1341
- previousState: "running",
1342
- stateSequence: 103,
1343
- approvalSequence: 104,
1344
- });
1345
- }
1346
- finally {
1347
- await releaseRunSlot();
1348
- }
1349
- continue;
1350
- }
1351
- if (thread.status !== "resuming" || !this.recoveryConfig.resumeResumingRunsOnStartup) {
1352
- continue;
1353
- }
1354
- const binding = this.workspace.bindings.get(thread.agentId);
1355
- if (!binding) {
1356
- continue;
1357
- }
1358
- const recoveryIntent = await this.persistence.getRecoveryIntent(thread.threadId, thread.latestRunId);
1359
- if (!recoveryIntent || recoveryIntent.kind !== "approval-decision") {
1360
- continue;
1361
- }
1362
- if (recoveryIntent.attempts >= this.recoveryConfig.maxRecoveryAttempts) {
1363
- await this.persistence.setRunState(thread.threadId, thread.latestRunId, "failed", recoveryIntent.checkpointRef);
1364
- await this.persistence.clearRecoveryIntent(thread.threadId, thread.latestRunId);
1230
+ const handled = await recoverQueuedStartupRun(recoveryContext, thread) ||
1231
+ await recoverRunningStartupRun(recoveryContext, thread) ||
1232
+ await recoverResumingStartupRun(recoveryContext, thread);
1233
+ if (handled) {
1365
1234
  continue;
1366
1235
  }
1367
- await this.persistence.saveRecoveryIntent(thread.threadId, thread.latestRunId, {
1368
- ...recoveryIntent,
1369
- attempts: recoveryIntent.attempts + 1,
1370
- });
1371
- await this.emit(thread.threadId, thread.latestRunId, 100, "run.resumed", {
1372
- resumeKind: "startup-recovery",
1373
- checkpointRef: recoveryIntent.checkpointRef,
1374
- state: "resuming",
1375
- });
1376
- const history = await this.persistence.listThreadMessages(thread.threadId);
1377
- const priorHistory = history.filter((message) => message.runId !== thread.latestRunId);
1378
- const runInput = await this.loadRunInput(thread.threadId, thread.latestRunId);
1379
- const startedAt = Date.now();
1380
- try {
1381
- const actual = await this.runtimeAdapter.invoke(binding, "", thread.threadId, thread.latestRunId, recoveryIntent.resumePayload, priorHistory);
1382
- this.recordLlmSuccess(startedAt);
1383
- await this.persistence.clearRecoveryIntent(thread.threadId, thread.latestRunId);
1384
- await this.finalizeContinuedRun(binding, thread.threadId, thread.latestRunId, runInput, actual, {
1385
- previousState: "resuming",
1386
- stateSequence: 101,
1387
- approvalSequence: 102,
1388
- });
1389
- }
1390
- catch (error) {
1391
- this.recordLlmFailure(startedAt);
1392
- if (recoveryIntent.attempts + 1 >= this.recoveryConfig.maxRecoveryAttempts) {
1393
- await this.persistence.setRunState(thread.threadId, thread.latestRunId, "failed", recoveryIntent.checkpointRef);
1394
- await this.persistence.clearRecoveryIntent(thread.threadId, thread.latestRunId);
1395
- await this.emit(thread.threadId, thread.latestRunId, 101, "run.state.changed", {
1396
- previousState: "resuming",
1397
- state: "failed",
1398
- checkpointRef: recoveryIntent.checkpointRef,
1399
- error: error instanceof Error ? error.message : String(error),
1400
- });
1401
- }
1402
- }
1403
1236
  }
1404
1237
  }
1405
1238
  async reclaimExpiredClaimedRuns(nowIso = new Date().toISOString()) {