@botbotgo/agent-harness 0.0.41 → 0.0.42

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.
@@ -5,7 +5,7 @@ import { AGENT_INTERRUPT_SENTINEL_PREFIX, AgentRuntimeAdapter, RuntimeOperationT
5
5
  import { createResourceBackendResolver, createResourceToolResolver } from "../resource/resource.js";
6
6
  import { EventBus } from "./event-bus.js";
7
7
  import { PolicyEngine } from "./policy-engine.js";
8
- import { getRecoveryConfig, getRoutingDefaultAgentId, getRoutingRules, getRoutingSystemPrompt, isModelRoutingEnabled, matchRoutingRule, } from "../workspace/support/workspace-ref-utils.js";
8
+ import { getConcurrencyConfig, getRecoveryConfig, getRoutingDefaultAgentId, getRoutingRules, getRoutingSystemPrompt, isModelRoutingEnabled, matchRoutingRule, } from "../workspace/support/workspace-ref-utils.js";
9
9
  import { createHarnessEvent, createPendingApproval, heuristicRoute, inferRoutingBindings, renderRuntimeFailure, renderToolFailure, } from "./support/harness-support.js";
10
10
  import { createCheckpointerForConfig, createStoreForConfig } from "./support/runtime-factories.js";
11
11
  import { resolveCompiledEmbeddingModel, resolveCompiledEmbeddingModelRef } from "./support/embedding-models.js";
@@ -36,6 +36,9 @@ export class AgentHarnessRuntime {
36
36
  resolvedRuntimeAdapterOptions;
37
37
  checkpointMaintenance;
38
38
  recoveryConfig;
39
+ concurrencyConfig;
40
+ activeRunSlots = 0;
41
+ pendingRunSlots = [];
39
42
  listHostBindings() {
40
43
  return inferRoutingBindings(this.workspace).hostBindings;
41
44
  }
@@ -138,6 +141,10 @@ export class AgentHarnessRuntime {
138
141
  return existing;
139
142
  }
140
143
  const resolvedConfig = binding.harnessRuntime.checkpointer ?? { kind: "FileCheckpointer", path: "checkpoints.json" };
144
+ if (typeof resolvedConfig === "boolean") {
145
+ this.checkpointers.set(key, resolvedConfig);
146
+ return resolvedConfig;
147
+ }
141
148
  const saver = createCheckpointerForConfig(resolvedConfig, binding.harnessRuntime.runRoot);
142
149
  this.checkpointers.set(key, saver);
143
150
  return saver;
@@ -159,6 +166,7 @@ export class AgentHarnessRuntime {
159
166
  ? new CheckpointMaintenanceLoop(discoverCheckpointMaintenanceTargets(workspace), checkpointMaintenanceConfig)
160
167
  : null;
161
168
  this.recoveryConfig = getRecoveryConfig(workspace.refs);
169
+ this.concurrencyConfig = getConcurrencyConfig(workspace.refs);
162
170
  }
163
171
  async initialize() {
164
172
  await this.persistence.initialize();
@@ -468,6 +476,28 @@ export class AgentHarnessRuntime {
468
476
  }
469
477
  await listener(value);
470
478
  }
479
+ async acquireRunSlot() {
480
+ const maxConcurrentRuns = this.concurrencyConfig.maxConcurrentRuns;
481
+ if (!maxConcurrentRuns) {
482
+ return () => undefined;
483
+ }
484
+ if (this.activeRunSlots >= maxConcurrentRuns) {
485
+ await new Promise((resolve) => {
486
+ this.pendingRunSlots.push(resolve);
487
+ });
488
+ }
489
+ this.activeRunSlots += 1;
490
+ let released = false;
491
+ return () => {
492
+ if (released) {
493
+ return;
494
+ }
495
+ released = true;
496
+ this.activeRunSlots = Math.max(0, this.activeRunSlots - 1);
497
+ const next = this.pendingRunSlots.shift();
498
+ next?.();
499
+ };
500
+ }
471
501
  async dispatchRunListeners(stream, listeners) {
472
502
  let latestEvent;
473
503
  let output = "";
@@ -529,285 +559,303 @@ export class AgentHarnessRuntime {
529
559
  if (options.listeners) {
530
560
  return this.dispatchRunListeners(this.streamEvents(options), options.listeners);
531
561
  }
532
- const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
533
- const binding = this.workspace.bindings.get(selectedAgentId);
534
- if (!binding) {
535
- throw new Error(`Unknown agent ${selectedAgentId}`);
536
- }
537
- const policyDecision = this.policyEngine.evaluate(binding);
538
- if (!policyDecision.allowed) {
539
- throw new Error(`Policy evaluation blocked agent ${selectedAgentId}: ${policyDecision.reasons.join(", ")}`);
540
- }
541
- const { threadId, runId } = await this.ensureThreadStarted(selectedAgentId, binding, options.input, options.threadId);
542
- await this.emitRunCreated(threadId, runId, {
543
- agentId: binding.agent.id,
544
- requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
545
- selectedAgentId,
546
- executionMode: binding.agent.executionMode,
547
- });
562
+ const releaseRunSlot = await this.acquireRunSlot();
548
563
  try {
549
- const actual = await this.invokeWithHistory(binding, options.input, threadId, runId, undefined, {
550
- context: options.context,
551
- state: options.state,
552
- files: options.files,
553
- });
554
- const finalized = await this.finalizeContinuedRun(threadId, runId, options.input, actual, {
555
- previousState: null,
556
- stateSequence: 3,
557
- approvalSequence: 4,
558
- });
559
- return {
560
- ...finalized,
561
- agentId: selectedAgentId,
562
- };
563
- }
564
- catch (error) {
565
- await this.emitSyntheticFallback(threadId, runId, selectedAgentId, error);
566
- await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
567
- previousState: null,
568
- error: error instanceof Error ? error.message : String(error),
569
- });
570
- return {
571
- threadId,
572
- runId,
573
- agentId: selectedAgentId,
574
- state: "failed",
575
- output: renderRuntimeFailure(error),
576
- };
577
- }
578
- }
579
- async *streamEvents(options) {
580
- const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
581
- const binding = this.workspace.bindings.get(selectedAgentId);
582
- if (!binding) {
583
- const result = await this.run(options);
584
- for (const line of result.output.split("\n")) {
585
- yield {
586
- type: "content",
587
- threadId: result.threadId,
588
- runId: result.runId,
589
- agentId: result.agentId ?? selectedAgentId,
590
- content: `${line}\n`,
591
- };
564
+ const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
565
+ const binding = this.workspace.bindings.get(selectedAgentId);
566
+ if (!binding) {
567
+ throw new Error(`Unknown agent ${selectedAgentId}`);
592
568
  }
593
- return;
594
- }
595
- let emitted = false;
596
- const { threadId, runId } = await this.ensureThreadStarted(selectedAgentId, binding, options.input, options.threadId);
597
- yield { type: "event", event: await this.emitRunCreated(threadId, runId, {
598
- agentId: selectedAgentId,
569
+ const policyDecision = this.policyEngine.evaluate(binding);
570
+ if (!policyDecision.allowed) {
571
+ throw new Error(`Policy evaluation blocked agent ${selectedAgentId}: ${policyDecision.reasons.join(", ")}`);
572
+ }
573
+ const { threadId, runId } = await this.ensureThreadStarted(selectedAgentId, binding, options.input, options.threadId);
574
+ await this.emitRunCreated(threadId, runId, {
575
+ agentId: binding.agent.id,
599
576
  requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
600
577
  selectedAgentId,
601
- input: options.input,
602
- state: "running",
603
- }) };
604
- try {
605
- const priorHistory = await this.loadPriorHistory(threadId, runId);
606
- let assistantOutput = "";
607
- const toolErrors = [];
608
- for await (const chunk of this.runtimeAdapter.stream(binding, options.input, threadId, priorHistory, {
609
- context: options.context,
610
- state: options.state,
611
- files: options.files,
612
- })) {
613
- if (chunk) {
614
- const normalizedChunk = typeof chunk === "string"
615
- ? chunk.startsWith(AGENT_INTERRUPT_SENTINEL_PREFIX)
616
- ? { kind: "interrupt", content: chunk.slice(AGENT_INTERRUPT_SENTINEL_PREFIX.length) }
617
- : { kind: "content", content: chunk }
618
- : chunk;
619
- if (normalizedChunk.kind === "interrupt") {
620
- const checkpointRef = `checkpoints/${threadId}/${runId}/cp-1`;
621
- const waitingEvent = await this.setRunStateAndEmit(threadId, runId, 4, "waiting_for_approval", {
622
- previousState: null,
623
- checkpointRef,
624
- });
625
- const approvalRequest = await this.requestApprovalAndEmit(threadId, runId, options.input, normalizedChunk.content, checkpointRef, 5);
626
- yield {
627
- type: "event",
628
- event: waitingEvent,
629
- };
630
- yield {
631
- type: "event",
632
- event: approvalRequest.event,
633
- };
634
- return;
635
- }
636
- if (normalizedChunk.kind === "reasoning") {
637
- await this.emit(threadId, runId, 3, "reasoning.delta", {
638
- content: normalizedChunk.content,
639
- });
640
- yield {
641
- type: "reasoning",
642
- threadId,
643
- runId,
644
- agentId: selectedAgentId,
645
- content: normalizedChunk.content,
646
- };
647
- continue;
648
- }
649
- if (normalizedChunk.kind === "step") {
650
- yield {
651
- type: "step",
652
- threadId,
653
- runId,
654
- agentId: selectedAgentId,
655
- content: normalizedChunk.content,
656
- };
657
- continue;
658
- }
659
- if (normalizedChunk.kind === "tool-result") {
660
- if (normalizedChunk.isError) {
661
- toolErrors.push(renderToolFailure(normalizedChunk.toolName, normalizedChunk.output));
662
- }
663
- yield {
664
- type: "tool-result",
665
- threadId,
666
- runId,
667
- agentId: selectedAgentId,
668
- toolName: normalizedChunk.toolName,
669
- output: normalizedChunk.output,
670
- isError: normalizedChunk.isError,
671
- };
672
- continue;
673
- }
674
- emitted = true;
675
- assistantOutput += normalizedChunk.content;
676
- yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, normalizedChunk.content);
677
- }
678
- }
679
- if (!assistantOutput && toolErrors.length > 0) {
680
- assistantOutput = toolErrors.join("\n\n");
681
- emitted = true;
682
- yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, assistantOutput);
683
- }
684
- if (!assistantOutput) {
685
- const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
686
- if (actual.output) {
687
- assistantOutput = actual.output;
688
- emitted = true;
689
- yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, actual.output);
690
- }
691
- }
692
- await this.appendAssistantMessage(threadId, runId, assistantOutput);
693
- yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "completed", {
578
+ executionMode: binding.agent.executionMode,
579
+ });
580
+ try {
581
+ const actual = await this.invokeWithHistory(binding, options.input, threadId, runId, undefined, {
582
+ context: options.context,
583
+ state: options.state,
584
+ files: options.files,
585
+ });
586
+ const finalized = await this.finalizeContinuedRun(threadId, runId, options.input, actual, {
694
587
  previousState: null,
695
- }) };
696
- return;
697
- }
698
- catch (error) {
699
- if (emitted) {
700
- yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
701
- previousState: null,
702
- error: error instanceof Error ? error.message : String(error),
703
- }) };
704
- return;
588
+ stateSequence: 3,
589
+ approvalSequence: 4,
590
+ });
591
+ return {
592
+ ...finalized,
593
+ agentId: selectedAgentId,
594
+ };
705
595
  }
706
- if (error instanceof RuntimeOperationTimeoutError && error.stage === "invoke") {
707
- yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
708
- previousState: null,
709
- error: error.message,
710
- }) };
711
- yield {
712
- type: "content",
596
+ catch (error) {
597
+ await this.emitSyntheticFallback(threadId, runId, selectedAgentId, error);
598
+ await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
599
+ previousState: null,
600
+ error: error instanceof Error ? error.message : String(error),
601
+ });
602
+ return {
713
603
  threadId,
714
604
  runId,
715
605
  agentId: selectedAgentId,
716
- content: renderRuntimeFailure(error),
606
+ state: "failed",
607
+ output: renderRuntimeFailure(error),
717
608
  };
609
+ }
610
+ }
611
+ finally {
612
+ releaseRunSlot();
613
+ }
614
+ }
615
+ async *streamEvents(options) {
616
+ const releaseRunSlot = await this.acquireRunSlot();
617
+ try {
618
+ const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
619
+ const binding = this.workspace.bindings.get(selectedAgentId);
620
+ if (!binding) {
621
+ const result = await this.run(options);
622
+ for (const line of result.output.split("\n")) {
623
+ yield {
624
+ type: "content",
625
+ threadId: result.threadId,
626
+ runId: result.runId,
627
+ agentId: result.agentId ?? selectedAgentId,
628
+ content: `${line}\n`,
629
+ };
630
+ }
718
631
  return;
719
632
  }
633
+ let emitted = false;
634
+ const { threadId, runId } = await this.ensureThreadStarted(selectedAgentId, binding, options.input, options.threadId);
635
+ yield { type: "event", event: await this.emitRunCreated(threadId, runId, {
636
+ agentId: selectedAgentId,
637
+ requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
638
+ selectedAgentId,
639
+ input: options.input,
640
+ state: "running",
641
+ }) };
720
642
  try {
721
- const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
722
- await this.appendAssistantMessage(threadId, runId, actual.output);
723
- if (actual.output) {
724
- yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, actual.output);
643
+ const priorHistory = await this.loadPriorHistory(threadId, runId);
644
+ let assistantOutput = "";
645
+ const toolErrors = [];
646
+ for await (const chunk of this.runtimeAdapter.stream(binding, options.input, threadId, priorHistory, {
647
+ context: options.context,
648
+ state: options.state,
649
+ files: options.files,
650
+ })) {
651
+ if (chunk) {
652
+ const normalizedChunk = typeof chunk === "string"
653
+ ? chunk.startsWith(AGENT_INTERRUPT_SENTINEL_PREFIX)
654
+ ? { kind: "interrupt", content: chunk.slice(AGENT_INTERRUPT_SENTINEL_PREFIX.length) }
655
+ : { kind: "content", content: chunk }
656
+ : chunk;
657
+ if (normalizedChunk.kind === "interrupt") {
658
+ const checkpointRef = `checkpoints/${threadId}/${runId}/cp-1`;
659
+ const waitingEvent = await this.setRunStateAndEmit(threadId, runId, 4, "waiting_for_approval", {
660
+ previousState: null,
661
+ checkpointRef,
662
+ });
663
+ const approvalRequest = await this.requestApprovalAndEmit(threadId, runId, options.input, normalizedChunk.content, checkpointRef, 5);
664
+ yield {
665
+ type: "event",
666
+ event: waitingEvent,
667
+ };
668
+ yield {
669
+ type: "event",
670
+ event: approvalRequest.event,
671
+ };
672
+ return;
673
+ }
674
+ if (normalizedChunk.kind === "reasoning") {
675
+ await this.emit(threadId, runId, 3, "reasoning.delta", {
676
+ content: normalizedChunk.content,
677
+ });
678
+ yield {
679
+ type: "reasoning",
680
+ threadId,
681
+ runId,
682
+ agentId: selectedAgentId,
683
+ content: normalizedChunk.content,
684
+ };
685
+ continue;
686
+ }
687
+ if (normalizedChunk.kind === "step") {
688
+ yield {
689
+ type: "step",
690
+ threadId,
691
+ runId,
692
+ agentId: selectedAgentId,
693
+ content: normalizedChunk.content,
694
+ };
695
+ continue;
696
+ }
697
+ if (normalizedChunk.kind === "tool-result") {
698
+ if (normalizedChunk.isError) {
699
+ toolErrors.push(renderToolFailure(normalizedChunk.toolName, normalizedChunk.output));
700
+ }
701
+ yield {
702
+ type: "tool-result",
703
+ threadId,
704
+ runId,
705
+ agentId: selectedAgentId,
706
+ toolName: normalizedChunk.toolName,
707
+ output: normalizedChunk.output,
708
+ isError: normalizedChunk.isError,
709
+ };
710
+ continue;
711
+ }
712
+ emitted = true;
713
+ assistantOutput += normalizedChunk.content;
714
+ yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, normalizedChunk.content);
715
+ }
716
+ }
717
+ if (!assistantOutput && toolErrors.length > 0) {
718
+ assistantOutput = toolErrors.join("\n\n");
719
+ emitted = true;
720
+ yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, assistantOutput);
725
721
  }
726
- yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, actual.state, {
722
+ if (!assistantOutput) {
723
+ const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
724
+ if (actual.output) {
725
+ assistantOutput = actual.output;
726
+ emitted = true;
727
+ yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, actual.output);
728
+ }
729
+ }
730
+ await this.appendAssistantMessage(threadId, runId, assistantOutput);
731
+ yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "completed", {
727
732
  previousState: null,
728
733
  }) };
729
734
  return;
730
735
  }
731
- catch (invokeError) {
732
- await this.emitSyntheticFallback(threadId, runId, selectedAgentId, invokeError);
733
- yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
734
- previousState: null,
735
- error: invokeError instanceof Error ? invokeError.message : String(invokeError),
736
- }) };
737
- yield {
738
- type: "content",
739
- threadId,
740
- runId,
741
- agentId: selectedAgentId,
742
- content: renderRuntimeFailure(invokeError),
743
- };
744
- return;
736
+ catch (error) {
737
+ if (emitted) {
738
+ yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
739
+ previousState: null,
740
+ error: error instanceof Error ? error.message : String(error),
741
+ }) };
742
+ return;
743
+ }
744
+ if (error instanceof RuntimeOperationTimeoutError && error.stage === "invoke") {
745
+ yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
746
+ previousState: null,
747
+ error: error.message,
748
+ }) };
749
+ yield {
750
+ type: "content",
751
+ threadId,
752
+ runId,
753
+ agentId: selectedAgentId,
754
+ content: renderRuntimeFailure(error),
755
+ };
756
+ return;
757
+ }
758
+ try {
759
+ const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
760
+ await this.appendAssistantMessage(threadId, runId, actual.output);
761
+ if (actual.output) {
762
+ yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, actual.output);
763
+ }
764
+ yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, actual.state, {
765
+ previousState: null,
766
+ }) };
767
+ return;
768
+ }
769
+ catch (invokeError) {
770
+ await this.emitSyntheticFallback(threadId, runId, selectedAgentId, invokeError);
771
+ yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
772
+ previousState: null,
773
+ error: invokeError instanceof Error ? invokeError.message : String(invokeError),
774
+ }) };
775
+ yield {
776
+ type: "content",
777
+ threadId,
778
+ runId,
779
+ agentId: selectedAgentId,
780
+ content: renderRuntimeFailure(invokeError),
781
+ };
782
+ return;
783
+ }
745
784
  }
746
785
  }
786
+ finally {
787
+ releaseRunSlot();
788
+ }
747
789
  }
748
790
  async resume(options) {
749
- const approvalById = options.approvalId ? await this.persistence.getApproval(options.approvalId) : null;
750
- const thread = options.threadId
751
- ? await this.getSession(options.threadId)
752
- : approvalById
753
- ? await this.getSession(approvalById.threadId)
754
- : null;
755
- if (!thread) {
756
- throw new Error("resume requires either threadId or approvalId");
757
- }
758
- const approval = approvalById ?? await this.resolveApprovalRecord(options, thread);
759
- const threadId = approval.threadId;
760
- const runId = approval.runId;
761
- const binding = this.workspace.bindings.get(thread.agentId);
762
- if (!binding) {
763
- throw new Error(`Unknown agent ${thread.agentId}`);
764
- }
765
- await this.persistence.setRunState(threadId, runId, "resuming", `checkpoints/${threadId}/${runId}/cp-1`);
766
- await this.persistence.saveRecoveryIntent(threadId, runId, {
767
- kind: "approval-decision",
768
- savedAt: new Date().toISOString(),
769
- checkpointRef: `checkpoints/${threadId}/${runId}/cp-1`,
770
- resumePayload: options.decision === "edit" && options.editedInput
771
- ? { decision: "edit", editedInput: options.editedInput }
772
- : (options.decision ?? "approve"),
773
- attempts: 0,
774
- });
775
- await this.emit(threadId, runId, 5, "run.resumed", {
776
- resumeKind: "cross-restart",
777
- checkpointRef: `checkpoints/${threadId}/${runId}/cp-1`,
778
- state: "resuming",
779
- approvalId: approval.approvalId,
780
- pendingActionId: approval.pendingActionId,
781
- });
782
- await this.persistence.resolveApproval(threadId, runId, approval.approvalId, options.decision === "reject" ? "rejected" : options.decision === "edit" ? "edited" : "approved");
783
- await this.emit(threadId, runId, 6, "approval.resolved", {
784
- approvalId: approval.approvalId,
785
- pendingActionId: approval.pendingActionId,
786
- decision: options.decision ?? "approve",
787
- toolName: approval.toolName,
788
- });
789
- const history = await this.persistence.listThreadMessages(threadId);
790
- const priorHistory = history.filter((message) => message.runId !== runId);
791
- const runInput = await this.loadRunInput(threadId, runId);
792
- const resumeDecision = options.decision === "edit" && options.editedInput
793
- ? { decision: "edit", editedInput: options.editedInput }
794
- : (options.decision ?? "approve");
791
+ const releaseRunSlot = await this.acquireRunSlot();
795
792
  try {
796
- const actual = await this.runtimeAdapter.invoke(binding, "", threadId, runId, resumeDecision, priorHistory);
797
- await this.persistence.clearRecoveryIntent(threadId, runId);
798
- const finalized = await this.finalizeContinuedRun(threadId, runId, runInput, actual, {
799
- previousState: "resuming",
800
- stateSequence: 7,
801
- approvalSequence: 8,
793
+ const approvalById = options.approvalId ? await this.persistence.getApproval(options.approvalId) : null;
794
+ const thread = options.threadId
795
+ ? await this.getSession(options.threadId)
796
+ : approvalById
797
+ ? await this.getSession(approvalById.threadId)
798
+ : null;
799
+ if (!thread) {
800
+ throw new Error("resume requires either threadId or approvalId");
801
+ }
802
+ const approval = approvalById ?? await this.resolveApprovalRecord(options, thread);
803
+ const threadId = approval.threadId;
804
+ const runId = approval.runId;
805
+ const binding = this.workspace.bindings.get(thread.agentId);
806
+ if (!binding) {
807
+ throw new Error(`Unknown agent ${thread.agentId}`);
808
+ }
809
+ await this.persistence.setRunState(threadId, runId, "resuming", `checkpoints/${threadId}/${runId}/cp-1`);
810
+ await this.persistence.saveRecoveryIntent(threadId, runId, {
811
+ kind: "approval-decision",
812
+ savedAt: new Date().toISOString(),
813
+ checkpointRef: `checkpoints/${threadId}/${runId}/cp-1`,
814
+ resumePayload: options.decision === "edit" && options.editedInput
815
+ ? { decision: "edit", editedInput: options.editedInput }
816
+ : (options.decision ?? "approve"),
817
+ attempts: 0,
802
818
  });
803
- return {
804
- ...finalized,
805
- approvalId: finalized.approvalId ?? approval.approvalId,
806
- pendingActionId: finalized.pendingActionId ?? approval.pendingActionId,
807
- };
819
+ await this.emit(threadId, runId, 5, "run.resumed", {
820
+ resumeKind: "cross-restart",
821
+ checkpointRef: `checkpoints/${threadId}/${runId}/cp-1`,
822
+ state: "resuming",
823
+ approvalId: approval.approvalId,
824
+ pendingActionId: approval.pendingActionId,
825
+ });
826
+ await this.persistence.resolveApproval(threadId, runId, approval.approvalId, options.decision === "reject" ? "rejected" : options.decision === "edit" ? "edited" : "approved");
827
+ await this.emit(threadId, runId, 6, "approval.resolved", {
828
+ approvalId: approval.approvalId,
829
+ pendingActionId: approval.pendingActionId,
830
+ decision: options.decision ?? "approve",
831
+ toolName: approval.toolName,
832
+ });
833
+ const history = await this.persistence.listThreadMessages(threadId);
834
+ const priorHistory = history.filter((message) => message.runId !== runId);
835
+ const runInput = await this.loadRunInput(threadId, runId);
836
+ const resumeDecision = options.decision === "edit" && options.editedInput
837
+ ? { decision: "edit", editedInput: options.editedInput }
838
+ : (options.decision ?? "approve");
839
+ try {
840
+ const actual = await this.runtimeAdapter.invoke(binding, "", threadId, runId, resumeDecision, priorHistory);
841
+ await this.persistence.clearRecoveryIntent(threadId, runId);
842
+ const finalized = await this.finalizeContinuedRun(threadId, runId, runInput, actual, {
843
+ previousState: "resuming",
844
+ stateSequence: 7,
845
+ approvalSequence: 8,
846
+ });
847
+ return {
848
+ ...finalized,
849
+ approvalId: finalized.approvalId ?? approval.approvalId,
850
+ pendingActionId: finalized.pendingActionId ?? approval.pendingActionId,
851
+ };
852
+ }
853
+ catch (error) {
854
+ throw error;
855
+ }
808
856
  }
809
- catch (error) {
810
- throw error;
857
+ finally {
858
+ releaseRunSlot();
811
859
  }
812
860
  }
813
861
  async restartConversation(options) {
@@ -1,3 +1,3 @@
1
1
  import { type StoreLike } from "../store.js";
2
2
  export declare function createStoreForConfig(storeConfig: Record<string, unknown>, runRoot: string): StoreLike;
3
- export declare function createCheckpointerForConfig(checkpointerConfig: Record<string, unknown>, runRoot: string): unknown;
3
+ export declare function createCheckpointerForConfig(checkpointerConfig: Record<string, unknown> | boolean, runRoot: string): unknown;
@@ -22,6 +22,9 @@ export function createStoreForConfig(storeConfig, runRoot) {
22
22
  }
23
23
  }
24
24
  export function createCheckpointerForConfig(checkpointerConfig, runRoot) {
25
+ if (typeof checkpointerConfig === "boolean") {
26
+ return checkpointerConfig;
27
+ }
25
28
  const kind = typeof checkpointerConfig.kind === "string" ? checkpointerConfig.kind : "FileCheckpointer";
26
29
  switch (kind) {
27
30
  case "MemorySaver":