@botbotgo/agent-harness 0.0.41 → 0.0.43

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.
Files changed (30) hide show
  1. package/README.md +105 -86
  2. package/dist/config/agents/direct.yaml +8 -7
  3. package/dist/config/agents/orchestra.yaml +8 -6
  4. package/dist/contracts/types.d.ts +35 -2
  5. package/dist/package-version.d.ts +1 -1
  6. package/dist/package-version.js +1 -1
  7. package/dist/persistence/file-store.d.ts +8 -6
  8. package/dist/persistence/file-store.js +2 -0
  9. package/dist/runtime/agent-runtime-adapter.d.ts +1 -1
  10. package/dist/runtime/agent-runtime-adapter.js +68 -80
  11. package/dist/runtime/checkpoint-maintenance.js +1 -1
  12. package/dist/runtime/declared-middleware.d.ts +8 -1
  13. package/dist/runtime/declared-middleware.js +18 -1
  14. package/dist/runtime/harness.d.ts +6 -0
  15. package/dist/runtime/harness.js +390 -256
  16. package/dist/runtime/inventory.js +2 -1
  17. package/dist/runtime/support/compiled-binding.d.ts +15 -0
  18. package/dist/runtime/support/compiled-binding.js +56 -0
  19. package/dist/runtime/support/harness-support.d.ts +2 -2
  20. package/dist/runtime/support/harness-support.js +14 -12
  21. package/dist/runtime/support/runtime-factories.d.ts +1 -1
  22. package/dist/runtime/support/runtime-factories.js +3 -0
  23. package/dist/workspace/agent-binding-compiler.js +61 -33
  24. package/dist/workspace/object-loader.js +50 -4
  25. package/dist/workspace/support/agent-capabilities.d.ts +7 -0
  26. package/dist/workspace/support/agent-capabilities.js +30 -0
  27. package/dist/workspace/support/workspace-ref-utils.d.ts +4 -0
  28. package/dist/workspace/support/workspace-ref-utils.js +15 -1
  29. package/dist/workspace/validate.js +14 -8
  30. package/package.json +2 -2
@@ -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";
@@ -15,6 +15,7 @@ import { FileBackedStore } from "./store.js";
15
15
  import { CheckpointMaintenanceLoop, discoverCheckpointMaintenanceTargets, readCheckpointMaintenanceConfig, } from "./checkpoint-maintenance.js";
16
16
  import { extractMessageText, normalizeMessageContent } from "../utils/message-content.js";
17
17
  import { createToolMcpServerFromTools, serveToolsOverStdioFromHarness } from "../mcp.js";
18
+ import { getBindingAdapterKind, getBindingPrimaryTools, getBindingStoreConfig } from "./support/compiled-binding.js";
18
19
  export class AgentHarnessRuntime {
19
20
  workspace;
20
21
  runtimeAdapterOptions;
@@ -36,6 +37,22 @@ export class AgentHarnessRuntime {
36
37
  resolvedRuntimeAdapterOptions;
37
38
  checkpointMaintenance;
38
39
  recoveryConfig;
40
+ concurrencyConfig;
41
+ activeRunSlots = 0;
42
+ pendingRunSlots = [];
43
+ toPublicApprovalRecord(approval) {
44
+ const { toolCallId: _toolCallId, checkpointRef: _checkpointRef, eventRefs: _eventRefs, ...publicApproval } = approval;
45
+ return publicApproval;
46
+ }
47
+ normalizeInvocationEnvelope(options) {
48
+ const invocation = options.invocation;
49
+ return {
50
+ context: invocation?.context ?? options.context,
51
+ state: invocation?.inputs ?? options.state,
52
+ files: invocation?.attachments ?? options.files,
53
+ invocation,
54
+ };
55
+ }
39
56
  listHostBindings() {
40
57
  return inferRoutingBindings(this.workspace).hostBindings;
41
58
  }
@@ -76,7 +93,7 @@ export class AgentHarnessRuntime {
76
93
  return requestedAgentId;
77
94
  }
78
95
  resolveStore(binding) {
79
- const storeConfig = binding?.deepAgentParams?.store ?? binding?.harnessRuntime.store;
96
+ const storeConfig = binding ? getBindingStoreConfig(binding) : undefined;
80
97
  const cacheKey = storeConfig ? JSON.stringify(storeConfig) : undefined;
81
98
  if (!storeConfig) {
82
99
  return this.defaultStore;
@@ -138,6 +155,10 @@ export class AgentHarnessRuntime {
138
155
  return existing;
139
156
  }
140
157
  const resolvedConfig = binding.harnessRuntime.checkpointer ?? { kind: "FileCheckpointer", path: "checkpoints.json" };
158
+ if (typeof resolvedConfig === "boolean") {
159
+ this.checkpointers.set(key, resolvedConfig);
160
+ return resolvedConfig;
161
+ }
141
162
  const saver = createCheckpointerForConfig(resolvedConfig, binding.harnessRuntime.runRoot);
142
163
  this.checkpointers.set(key, saver);
143
164
  return saver;
@@ -159,6 +180,7 @@ export class AgentHarnessRuntime {
159
180
  ? new CheckpointMaintenanceLoop(discoverCheckpointMaintenanceTargets(workspace), checkpointMaintenanceConfig)
160
181
  : null;
161
182
  this.recoveryConfig = getRecoveryConfig(workspace.refs);
183
+ this.concurrencyConfig = getConcurrencyConfig(workspace.refs);
162
184
  }
163
185
  async initialize() {
164
186
  await this.persistence.initialize();
@@ -176,7 +198,7 @@ export class AgentHarnessRuntime {
176
198
  if (!binding) {
177
199
  throw new Error(`Unknown agent ${agentId}`);
178
200
  }
179
- return binding.langchainAgentParams?.tools ?? binding.deepAgentParams?.tools ?? [];
201
+ return getBindingPrimaryTools(binding);
180
202
  }
181
203
  resolveAgentTools(agentId) {
182
204
  const binding = this.getBinding(agentId);
@@ -251,10 +273,11 @@ export class AgentHarnessRuntime {
251
273
  return false;
252
274
  }
253
275
  return true;
254
- });
276
+ }).map((approval) => this.toPublicApprovalRecord(approval));
255
277
  }
256
278
  async getApproval(approvalId) {
257
- return this.persistence.getApproval(approvalId);
279
+ const approval = await this.persistence.getApproval(approvalId);
280
+ return approval ? this.toPublicApprovalRecord(approval) : null;
258
281
  }
259
282
  async createToolMcpServer(options) {
260
283
  const tools = this.resolveAgentTools(options.agentId).map(({ compiledTool, resolvedTool }) => ({
@@ -332,7 +355,8 @@ export class AgentHarnessRuntime {
332
355
  threadId,
333
356
  runId,
334
357
  agentId: binding.agent.id,
335
- executionMode: binding.agent.executionMode,
358
+ executionMode: getBindingAdapterKind(binding),
359
+ adapterKind: getBindingAdapterKind(binding),
336
360
  createdAt,
337
361
  });
338
362
  return { threadId, runId, createdAt };
@@ -468,8 +492,31 @@ export class AgentHarnessRuntime {
468
492
  }
469
493
  await listener(value);
470
494
  }
495
+ async acquireRunSlot() {
496
+ const maxConcurrentRuns = this.concurrencyConfig.maxConcurrentRuns;
497
+ if (!maxConcurrentRuns) {
498
+ return () => undefined;
499
+ }
500
+ if (this.activeRunSlots >= maxConcurrentRuns) {
501
+ await new Promise((resolve) => {
502
+ this.pendingRunSlots.push(resolve);
503
+ });
504
+ }
505
+ this.activeRunSlots += 1;
506
+ let released = false;
507
+ return () => {
508
+ if (released) {
509
+ return;
510
+ }
511
+ released = true;
512
+ this.activeRunSlots = Math.max(0, this.activeRunSlots - 1);
513
+ const next = this.pendingRunSlots.shift();
514
+ next?.();
515
+ };
516
+ }
471
517
  async dispatchRunListeners(stream, listeners) {
472
518
  let latestEvent;
519
+ let latestResult;
473
520
  let output = "";
474
521
  for await (const item of stream) {
475
522
  if (item.type === "event") {
@@ -477,6 +524,10 @@ export class AgentHarnessRuntime {
477
524
  await this.notifyListener(listeners.onEvent, item.event);
478
525
  continue;
479
526
  }
527
+ if (item.type === "result") {
528
+ latestResult = item.result;
529
+ continue;
530
+ }
480
531
  if (item.type === "content") {
481
532
  output += item.content;
482
533
  await this.notifyListener(listeners.onChunk, item.content);
@@ -501,6 +552,13 @@ export class AgentHarnessRuntime {
501
552
  if (!latestEvent) {
502
553
  throw new Error("run did not emit any events");
503
554
  }
555
+ if (latestResult) {
556
+ return {
557
+ ...latestResult,
558
+ output: latestResult.output || output,
559
+ finalMessageText: latestResult.finalMessageText ?? latestResult.output ?? output,
560
+ };
561
+ }
504
562
  const thread = await this.getThread(latestEvent.threadId);
505
563
  if (!thread) {
506
564
  throw new Error(`Unknown thread ${latestEvent.threadId}`);
@@ -529,285 +587,361 @@ export class AgentHarnessRuntime {
529
587
  if (options.listeners) {
530
588
  return this.dispatchRunListeners(this.streamEvents(options), options.listeners);
531
589
  }
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
- });
590
+ const releaseRunSlot = await this.acquireRunSlot();
548
591
  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,
592
+ const invocation = this.normalizeInvocationEnvelope(options);
593
+ const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
594
+ const binding = this.workspace.bindings.get(selectedAgentId);
595
+ if (!binding) {
596
+ throw new Error(`Unknown agent ${selectedAgentId}`);
597
+ }
598
+ const policyDecision = this.policyEngine.evaluate(binding);
599
+ if (!policyDecision.allowed) {
600
+ throw new Error(`Policy evaluation blocked agent ${selectedAgentId}: ${policyDecision.reasons.join(", ")}`);
601
+ }
602
+ const { threadId, runId } = await this.ensureThreadStarted(selectedAgentId, binding, options.input, options.threadId);
603
+ await this.emitRunCreated(threadId, runId, {
604
+ agentId: binding.agent.id,
605
+ requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
606
+ selectedAgentId,
607
+ executionMode: getBindingAdapterKind(binding),
558
608
  });
559
- return {
560
- ...finalized,
561
- agentId: selectedAgentId,
562
- };
609
+ try {
610
+ const actual = await this.invokeWithHistory(binding, options.input, threadId, runId, undefined, {
611
+ context: invocation.context,
612
+ state: invocation.state,
613
+ files: invocation.files,
614
+ });
615
+ const finalized = await this.finalizeContinuedRun(threadId, runId, options.input, actual, {
616
+ previousState: null,
617
+ stateSequence: 3,
618
+ approvalSequence: 4,
619
+ });
620
+ return {
621
+ ...finalized,
622
+ agentId: selectedAgentId,
623
+ };
624
+ }
625
+ catch (error) {
626
+ await this.emitSyntheticFallback(threadId, runId, selectedAgentId, error);
627
+ await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
628
+ previousState: null,
629
+ error: error instanceof Error ? error.message : String(error),
630
+ });
631
+ return {
632
+ threadId,
633
+ runId,
634
+ agentId: selectedAgentId,
635
+ state: "failed",
636
+ output: renderRuntimeFailure(error),
637
+ };
638
+ }
563
639
  }
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
- };
640
+ finally {
641
+ releaseRunSlot();
577
642
  }
578
643
  }
579
644
  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")) {
645
+ const releaseRunSlot = await this.acquireRunSlot();
646
+ try {
647
+ const invocation = this.normalizeInvocationEnvelope(options);
648
+ const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
649
+ const binding = this.workspace.bindings.get(selectedAgentId);
650
+ if (!binding) {
651
+ const result = await this.run(options);
652
+ for (const line of result.output.split("\n")) {
653
+ yield {
654
+ type: "content",
655
+ threadId: result.threadId,
656
+ runId: result.runId,
657
+ agentId: result.agentId ?? selectedAgentId,
658
+ content: `${line}\n`,
659
+ };
660
+ }
661
+ return;
662
+ }
663
+ let emitted = false;
664
+ const { threadId, runId } = await this.ensureThreadStarted(selectedAgentId, binding, options.input, options.threadId);
665
+ yield { type: "event", event: await this.emitRunCreated(threadId, runId, {
666
+ agentId: selectedAgentId,
667
+ requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
668
+ selectedAgentId,
669
+ input: options.input,
670
+ state: "running",
671
+ }) };
672
+ try {
673
+ const priorHistory = await this.loadPriorHistory(threadId, runId);
674
+ let assistantOutput = "";
675
+ const toolErrors = [];
676
+ for await (const chunk of this.runtimeAdapter.stream(binding, options.input, threadId, priorHistory, {
677
+ context: invocation.context,
678
+ state: invocation.state,
679
+ files: invocation.files,
680
+ })) {
681
+ if (chunk) {
682
+ const normalizedChunk = typeof chunk === "string"
683
+ ? chunk.startsWith(AGENT_INTERRUPT_SENTINEL_PREFIX)
684
+ ? { kind: "interrupt", content: chunk.slice(AGENT_INTERRUPT_SENTINEL_PREFIX.length) }
685
+ : { kind: "content", content: chunk }
686
+ : chunk;
687
+ if (normalizedChunk.kind === "interrupt") {
688
+ const checkpointRef = `checkpoints/${threadId}/${runId}/cp-1`;
689
+ const waitingEvent = await this.setRunStateAndEmit(threadId, runId, 4, "waiting_for_approval", {
690
+ previousState: null,
691
+ checkpointRef,
692
+ });
693
+ const approvalRequest = await this.requestApprovalAndEmit(threadId, runId, options.input, normalizedChunk.content, checkpointRef, 5);
694
+ yield {
695
+ type: "event",
696
+ event: waitingEvent,
697
+ };
698
+ yield {
699
+ type: "event",
700
+ event: approvalRequest.event,
701
+ };
702
+ yield {
703
+ type: "result",
704
+ result: {
705
+ threadId,
706
+ runId,
707
+ agentId: selectedAgentId,
708
+ state: "waiting_for_approval",
709
+ output: assistantOutput,
710
+ finalMessageText: assistantOutput,
711
+ interruptContent: normalizedChunk.content,
712
+ approvalId: approvalRequest.approval.approvalId,
713
+ pendingActionId: approvalRequest.approval.pendingActionId,
714
+ },
715
+ };
716
+ return;
717
+ }
718
+ if (normalizedChunk.kind === "reasoning") {
719
+ await this.emit(threadId, runId, 3, "reasoning.delta", {
720
+ content: normalizedChunk.content,
721
+ });
722
+ yield {
723
+ type: "reasoning",
724
+ threadId,
725
+ runId,
726
+ agentId: selectedAgentId,
727
+ content: normalizedChunk.content,
728
+ };
729
+ continue;
730
+ }
731
+ if (normalizedChunk.kind === "step") {
732
+ yield {
733
+ type: "step",
734
+ threadId,
735
+ runId,
736
+ agentId: selectedAgentId,
737
+ content: normalizedChunk.content,
738
+ };
739
+ continue;
740
+ }
741
+ if (normalizedChunk.kind === "tool-result") {
742
+ if (normalizedChunk.isError) {
743
+ toolErrors.push(renderToolFailure(normalizedChunk.toolName, normalizedChunk.output));
744
+ }
745
+ yield {
746
+ type: "tool-result",
747
+ threadId,
748
+ runId,
749
+ agentId: selectedAgentId,
750
+ toolName: normalizedChunk.toolName,
751
+ output: normalizedChunk.output,
752
+ isError: normalizedChunk.isError,
753
+ };
754
+ continue;
755
+ }
756
+ emitted = true;
757
+ assistantOutput += normalizedChunk.content;
758
+ yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, normalizedChunk.content);
759
+ }
760
+ }
761
+ if (!assistantOutput && toolErrors.length > 0) {
762
+ assistantOutput = toolErrors.join("\n\n");
763
+ emitted = true;
764
+ yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, assistantOutput);
765
+ }
766
+ if (!assistantOutput) {
767
+ const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
768
+ if (actual.output) {
769
+ assistantOutput = actual.output;
770
+ emitted = true;
771
+ yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, actual.output);
772
+ }
773
+ }
774
+ await this.appendAssistantMessage(threadId, runId, assistantOutput);
585
775
  yield {
586
- type: "content",
587
- threadId: result.threadId,
588
- runId: result.runId,
589
- agentId: result.agentId ?? selectedAgentId,
590
- content: `${line}\n`,
776
+ type: "result",
777
+ result: {
778
+ threadId,
779
+ runId,
780
+ agentId: selectedAgentId,
781
+ state: "completed",
782
+ output: assistantOutput,
783
+ finalMessageText: assistantOutput,
784
+ },
591
785
  };
786
+ yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "completed", {
787
+ previousState: null,
788
+ }) };
789
+ return;
592
790
  }
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,
599
- requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
600
- 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", {
791
+ catch (error) {
792
+ if (emitted) {
793
+ yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
622
794
  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",
795
+ error: error instanceof Error ? error.message : String(error),
796
+ }) };
797
+ return;
798
+ }
799
+ if (error instanceof RuntimeOperationTimeoutError && error.stage === "invoke") {
800
+ yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
801
+ previousState: null,
802
+ error: error.message,
803
+ }) };
804
+ yield {
805
+ type: "content",
806
+ threadId,
807
+ runId,
808
+ agentId: selectedAgentId,
809
+ content: renderRuntimeFailure(error),
810
+ };
811
+ yield {
812
+ type: "result",
813
+ result: {
642
814
  threadId,
643
815
  runId,
644
816
  agentId: selectedAgentId,
645
- content: normalizedChunk.content,
646
- };
647
- continue;
817
+ state: "failed",
818
+ output: renderRuntimeFailure(error),
819
+ finalMessageText: renderRuntimeFailure(error),
820
+ },
821
+ };
822
+ return;
823
+ }
824
+ try {
825
+ const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
826
+ await this.appendAssistantMessage(threadId, runId, actual.output);
827
+ if (actual.output) {
828
+ yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, actual.output);
648
829
  }
649
- if (normalizedChunk.kind === "step") {
650
- yield {
651
- type: "step",
830
+ yield {
831
+ type: "result",
832
+ result: {
833
+ ...actual,
652
834
  threadId,
653
835
  runId,
654
836
  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",
837
+ },
838
+ };
839
+ yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, actual.state, {
840
+ previousState: null,
841
+ }) };
842
+ return;
843
+ }
844
+ catch (invokeError) {
845
+ await this.emitSyntheticFallback(threadId, runId, selectedAgentId, invokeError);
846
+ yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
847
+ previousState: null,
848
+ error: invokeError instanceof Error ? invokeError.message : String(invokeError),
849
+ }) };
850
+ yield {
851
+ type: "content",
852
+ threadId,
853
+ runId,
854
+ agentId: selectedAgentId,
855
+ content: renderRuntimeFailure(invokeError),
856
+ };
857
+ yield {
858
+ type: "result",
859
+ result: {
665
860
  threadId,
666
861
  runId,
667
862
  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);
863
+ state: "failed",
864
+ output: renderRuntimeFailure(invokeError),
865
+ finalMessageText: renderRuntimeFailure(invokeError),
866
+ },
867
+ };
868
+ return;
690
869
  }
691
870
  }
692
- await this.appendAssistantMessage(threadId, runId, assistantOutput);
693
- yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "completed", {
694
- previousState: null,
695
- }) };
696
- return;
697
871
  }
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;
705
- }
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",
713
- threadId,
714
- runId,
715
- agentId: selectedAgentId,
716
- content: renderRuntimeFailure(error),
717
- };
718
- return;
719
- }
720
- 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);
725
- }
726
- yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, actual.state, {
727
- previousState: null,
728
- }) };
729
- return;
730
- }
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;
745
- }
872
+ finally {
873
+ releaseRunSlot();
746
874
  }
747
875
  }
748
876
  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");
877
+ const releaseRunSlot = await this.acquireRunSlot();
795
878
  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,
879
+ const approvalById = options.approvalId ? await this.persistence.getApproval(options.approvalId) : null;
880
+ const thread = options.threadId
881
+ ? await this.getSession(options.threadId)
882
+ : approvalById
883
+ ? await this.getSession(approvalById.threadId)
884
+ : null;
885
+ if (!thread) {
886
+ throw new Error("resume requires either threadId or approvalId");
887
+ }
888
+ const approval = approvalById ?? await this.resolveApprovalRecord(options, thread);
889
+ const threadId = approval.threadId;
890
+ const runId = approval.runId;
891
+ const binding = this.workspace.bindings.get(thread.agentId);
892
+ if (!binding) {
893
+ throw new Error(`Unknown agent ${thread.agentId}`);
894
+ }
895
+ await this.persistence.setRunState(threadId, runId, "resuming", `checkpoints/${threadId}/${runId}/cp-1`);
896
+ await this.persistence.saveRecoveryIntent(threadId, runId, {
897
+ kind: "approval-decision",
898
+ savedAt: new Date().toISOString(),
899
+ checkpointRef: `checkpoints/${threadId}/${runId}/cp-1`,
900
+ resumePayload: options.decision === "edit" && options.editedInput
901
+ ? { decision: "edit", editedInput: options.editedInput }
902
+ : (options.decision ?? "approve"),
903
+ attempts: 0,
802
904
  });
803
- return {
804
- ...finalized,
805
- approvalId: finalized.approvalId ?? approval.approvalId,
806
- pendingActionId: finalized.pendingActionId ?? approval.pendingActionId,
807
- };
905
+ await this.emit(threadId, runId, 5, "run.resumed", {
906
+ resumeKind: "cross-restart",
907
+ checkpointRef: `checkpoints/${threadId}/${runId}/cp-1`,
908
+ state: "resuming",
909
+ approvalId: approval.approvalId,
910
+ pendingActionId: approval.pendingActionId,
911
+ });
912
+ await this.persistence.resolveApproval(threadId, runId, approval.approvalId, options.decision === "reject" ? "rejected" : options.decision === "edit" ? "edited" : "approved");
913
+ await this.emit(threadId, runId, 6, "approval.resolved", {
914
+ approvalId: approval.approvalId,
915
+ pendingActionId: approval.pendingActionId,
916
+ decision: options.decision ?? "approve",
917
+ toolName: approval.toolName,
918
+ });
919
+ const history = await this.persistence.listThreadMessages(threadId);
920
+ const priorHistory = history.filter((message) => message.runId !== runId);
921
+ const runInput = await this.loadRunInput(threadId, runId);
922
+ const resumeDecision = options.decision === "edit" && options.editedInput
923
+ ? { decision: "edit", editedInput: options.editedInput }
924
+ : (options.decision ?? "approve");
925
+ try {
926
+ const actual = await this.runtimeAdapter.invoke(binding, "", threadId, runId, resumeDecision, priorHistory);
927
+ await this.persistence.clearRecoveryIntent(threadId, runId);
928
+ const finalized = await this.finalizeContinuedRun(threadId, runId, runInput, actual, {
929
+ previousState: "resuming",
930
+ stateSequence: 7,
931
+ approvalSequence: 8,
932
+ });
933
+ return {
934
+ ...finalized,
935
+ approvalId: finalized.approvalId ?? approval.approvalId,
936
+ pendingActionId: finalized.pendingActionId ?? approval.pendingActionId,
937
+ };
938
+ }
939
+ catch (error) {
940
+ throw error;
941
+ }
808
942
  }
809
- catch (error) {
810
- throw error;
943
+ finally {
944
+ releaseRunSlot();
811
945
  }
812
946
  }
813
947
  async restartConversation(options) {