@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.
- package/README.md +105 -86
- package/dist/config/agents/direct.yaml +8 -7
- package/dist/config/agents/orchestra.yaml +8 -6
- package/dist/contracts/types.d.ts +35 -2
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/persistence/file-store.d.ts +8 -6
- package/dist/persistence/file-store.js +2 -0
- package/dist/runtime/agent-runtime-adapter.d.ts +1 -1
- package/dist/runtime/agent-runtime-adapter.js +68 -80
- package/dist/runtime/checkpoint-maintenance.js +1 -1
- package/dist/runtime/declared-middleware.d.ts +8 -1
- package/dist/runtime/declared-middleware.js +18 -1
- package/dist/runtime/harness.d.ts +6 -0
- package/dist/runtime/harness.js +390 -256
- package/dist/runtime/inventory.js +2 -1
- package/dist/runtime/support/compiled-binding.d.ts +15 -0
- package/dist/runtime/support/compiled-binding.js +56 -0
- package/dist/runtime/support/harness-support.d.ts +2 -2
- package/dist/runtime/support/harness-support.js +14 -12
- package/dist/runtime/support/runtime-factories.d.ts +1 -1
- package/dist/runtime/support/runtime-factories.js +3 -0
- package/dist/workspace/agent-binding-compiler.js +61 -33
- package/dist/workspace/object-loader.js +50 -4
- package/dist/workspace/support/agent-capabilities.d.ts +7 -0
- package/dist/workspace/support/agent-capabilities.js +30 -0
- package/dist/workspace/support/workspace-ref-utils.d.ts +4 -0
- package/dist/workspace/support/workspace-ref-utils.js +15 -1
- package/dist/workspace/validate.js +14 -8
- package/package.json +2 -2
package/dist/runtime/harness.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
-
|
|
565
|
-
|
|
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
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
const
|
|
584
|
-
|
|
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: "
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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
|
-
|
|
646
|
-
|
|
647
|
-
|
|
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
|
-
|
|
650
|
-
|
|
651
|
-
|
|
830
|
+
yield {
|
|
831
|
+
type: "result",
|
|
832
|
+
result: {
|
|
833
|
+
...actual,
|
|
652
834
|
threadId,
|
|
653
835
|
runId,
|
|
654
836
|
agentId: selectedAgentId,
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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
|
-
|
|
669
|
-
output:
|
|
670
|
-
|
|
671
|
-
}
|
|
672
|
-
|
|
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
|
-
|
|
699
|
-
|
|
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
|
|
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
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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
|
-
|
|
810
|
-
|
|
943
|
+
finally {
|
|
944
|
+
releaseRunSlot();
|
|
811
945
|
}
|
|
812
946
|
}
|
|
813
947
|
async restartConversation(options) {
|