@botbotgo/agent-harness 0.0.45 → 0.0.47
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 +323 -29
- package/dist/api.d.ts +1 -0
- package/dist/api.js +3 -0
- package/dist/config/workspace.yaml +20 -18
- package/dist/contracts/types.d.ts +4 -2
- package/dist/extensions.js +3 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/persistence/file-store.d.ts +14 -1
- package/dist/persistence/file-store.js +54 -0
- package/dist/resource/resource-impl.d.ts +1 -0
- package/dist/runtime/harness.d.ts +6 -0
- package/dist/runtime/harness.js +298 -112
- package/dist/runtime/thread-memory-sync.js +2 -0
- package/dist/tool-modules.d.ts +1 -0
- package/dist/tool-modules.js +11 -0
- package/dist/tools.d.ts +2 -0
- package/dist/workspace/object-loader.js +1 -0
- package/dist/workspace/resource-compilers.js +1 -0
- package/dist/workspace/support/workspace-ref-utils.d.ts +1 -1
- package/dist/workspace/support/workspace-ref-utils.js +1 -1
- package/dist/workspace/tool-hydration.js +1 -0
- package/package.json +1 -1
|
@@ -7,6 +7,18 @@ export class FilePersistence {
|
|
|
7
7
|
constructor(runRoot) {
|
|
8
8
|
this.runRoot = runRoot;
|
|
9
9
|
}
|
|
10
|
+
threadIndexPath(threadId) {
|
|
11
|
+
return path.join(this.runRoot, "indexes", "threads", `${threadId}.json`);
|
|
12
|
+
}
|
|
13
|
+
runIndexPath(runId) {
|
|
14
|
+
return path.join(this.runRoot, "indexes", "runs", `${runId}.json`);
|
|
15
|
+
}
|
|
16
|
+
approvalIndexPath(approvalId) {
|
|
17
|
+
return path.join(this.runRoot, "indexes", "approvals", `${approvalId}.json`);
|
|
18
|
+
}
|
|
19
|
+
delegationIndexPath(delegationId) {
|
|
20
|
+
return path.join(this.runRoot, "indexes", "delegations", `${delegationId}.json`);
|
|
21
|
+
}
|
|
10
22
|
async initialize() {
|
|
11
23
|
await Promise.all([
|
|
12
24
|
"indexes/threads",
|
|
@@ -266,6 +278,48 @@ export class FilePersistence {
|
|
|
266
278
|
async getRunLifecycle(threadId, runId) {
|
|
267
279
|
return readJson(path.join(this.runDir(threadId, runId), "lifecycle.json"));
|
|
268
280
|
}
|
|
281
|
+
async deleteThread(threadId) {
|
|
282
|
+
const threadDir = this.threadDir(threadId);
|
|
283
|
+
const threadIndexPath = this.threadIndexPath(threadId);
|
|
284
|
+
if (!(await fileExists(threadDir)) && !(await fileExists(threadIndexPath))) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
const [runIndexes, approvals, delegations] = await Promise.all([
|
|
288
|
+
this.listRunIndexes(),
|
|
289
|
+
this.listApprovals(),
|
|
290
|
+
this.listDelegations(),
|
|
291
|
+
]);
|
|
292
|
+
await Promise.all([
|
|
293
|
+
...runIndexes
|
|
294
|
+
.filter((record) => record.threadId === threadId)
|
|
295
|
+
.map((record) => rm(this.runIndexPath(record.runId), { force: true })),
|
|
296
|
+
...approvals
|
|
297
|
+
.filter((record) => record.threadId === threadId)
|
|
298
|
+
.map((record) => rm(this.approvalIndexPath(record.approvalId), { force: true })),
|
|
299
|
+
...delegations
|
|
300
|
+
.filter((record) => record.threadId === threadId)
|
|
301
|
+
.map((record) => rm(this.delegationIndexPath(record.delegationId), { force: true })),
|
|
302
|
+
rm(threadIndexPath, { force: true }),
|
|
303
|
+
rm(threadDir, { recursive: true, force: true }),
|
|
304
|
+
]);
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
async saveRunRequest(threadId, runId, request) {
|
|
308
|
+
await writeJson(path.join(this.runDir(threadId, runId), "request.json"), request);
|
|
309
|
+
}
|
|
310
|
+
async getRunRequest(threadId, runId) {
|
|
311
|
+
const requestPath = path.join(this.runDir(threadId, runId), "request.json");
|
|
312
|
+
if (!(await fileExists(requestPath))) {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
return readJson(requestPath);
|
|
316
|
+
}
|
|
317
|
+
async clearRunRequest(threadId, runId) {
|
|
318
|
+
const requestPath = path.join(this.runDir(threadId, runId), "request.json");
|
|
319
|
+
if (await fileExists(requestPath)) {
|
|
320
|
+
await rm(requestPath, { force: true });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
269
323
|
async listDelegations() {
|
|
270
324
|
const delegationsDir = path.join(this.runRoot, "indexes", "delegations");
|
|
271
325
|
if (!(await fileExists(delegationsDir))) {
|
|
@@ -26,6 +26,7 @@ export declare class AgentHarnessRuntime {
|
|
|
26
26
|
private readonly pendingRunSlots;
|
|
27
27
|
private toPublicApprovalRecord;
|
|
28
28
|
private normalizeInvocationEnvelope;
|
|
29
|
+
private isTerminalRunState;
|
|
29
30
|
private listHostBindings;
|
|
30
31
|
private defaultRunRoot;
|
|
31
32
|
private heuristicRoute;
|
|
@@ -40,6 +41,7 @@ export declare class AgentHarnessRuntime {
|
|
|
40
41
|
private getBinding;
|
|
41
42
|
private listAgentTools;
|
|
42
43
|
private resolveAgentTools;
|
|
44
|
+
private supportsRunningReplay;
|
|
43
45
|
listThreads(filter?: {
|
|
44
46
|
agentId?: string;
|
|
45
47
|
}): Promise<ThreadSummary[]>;
|
|
@@ -51,6 +53,8 @@ export declare class AgentHarnessRuntime {
|
|
|
51
53
|
runId?: string;
|
|
52
54
|
}): Promise<ApprovalRecord[]>;
|
|
53
55
|
getApproval(approvalId: string): Promise<ApprovalRecord | null>;
|
|
56
|
+
private deleteThreadCheckpoints;
|
|
57
|
+
deleteThread(threadId: string): Promise<boolean>;
|
|
54
58
|
createToolMcpServer(options: ToolMcpServerOptions): Promise<import("@modelcontextprotocol/sdk/server/mcp.js").McpServer>;
|
|
55
59
|
serveToolsOverStdio(options: ToolMcpServerOptions): Promise<import("@modelcontextprotocol/sdk/server/mcp.js").McpServer>;
|
|
56
60
|
routeAgent(input: MessageContent, options?: {
|
|
@@ -62,6 +66,8 @@ export declare class AgentHarnessRuntime {
|
|
|
62
66
|
private loadRunInput;
|
|
63
67
|
private appendAssistantMessage;
|
|
64
68
|
private invokeWithHistory;
|
|
69
|
+
private buildPersistedRunRequest;
|
|
70
|
+
private executeQueuedRun;
|
|
65
71
|
private checkpointRefForState;
|
|
66
72
|
private finalizeContinuedRun;
|
|
67
73
|
private emitOutputDeltaAndCreateItem;
|
package/dist/runtime/harness.js
CHANGED
|
@@ -53,6 +53,9 @@ export class AgentHarnessRuntime {
|
|
|
53
53
|
invocation,
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
|
+
isTerminalRunState(state) {
|
|
57
|
+
return state === "completed" || state === "failed";
|
|
58
|
+
}
|
|
56
59
|
listHostBindings() {
|
|
57
60
|
return inferRoutingBindings(this.workspace).hostBindings;
|
|
58
61
|
}
|
|
@@ -213,6 +216,10 @@ export class AgentHarnessRuntime {
|
|
|
213
216
|
resolvedTool: resolvedTools[index],
|
|
214
217
|
}));
|
|
215
218
|
}
|
|
219
|
+
supportsRunningReplay(binding) {
|
|
220
|
+
const tools = getBindingPrimaryTools(binding);
|
|
221
|
+
return tools.every((tool) => tool.retryable === true);
|
|
222
|
+
}
|
|
216
223
|
async listThreads(filter) {
|
|
217
224
|
const threadSummaries = await this.persistence.listSessions();
|
|
218
225
|
if (!filter?.agentId) {
|
|
@@ -279,6 +286,39 @@ export class AgentHarnessRuntime {
|
|
|
279
286
|
const approval = await this.persistence.getApproval(approvalId);
|
|
280
287
|
return approval ? this.toPublicApprovalRecord(approval) : null;
|
|
281
288
|
}
|
|
289
|
+
async deleteThreadCheckpoints(threadId) {
|
|
290
|
+
const resolver = this.resolvedRuntimeAdapterOptions.checkpointerResolver;
|
|
291
|
+
if (!resolver) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const seen = new Set();
|
|
295
|
+
for (const binding of this.workspace.bindings.values()) {
|
|
296
|
+
const saver = resolver(binding);
|
|
297
|
+
if (!saver || seen.has(saver)) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
seen.add(saver);
|
|
301
|
+
const maybeDeleteThread = saver.deleteThread;
|
|
302
|
+
if (typeof maybeDeleteThread === "function") {
|
|
303
|
+
await maybeDeleteThread.call(saver, threadId);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
async deleteThread(threadId) {
|
|
308
|
+
const thread = await this.getThread(threadId);
|
|
309
|
+
if (!thread) {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
const activeRun = thread.runs.find((run) => !this.isTerminalRunState(run.state));
|
|
313
|
+
if (activeRun) {
|
|
314
|
+
throw new Error(`Cannot delete thread ${threadId} while run ${activeRun.runId} is ${activeRun.state}`);
|
|
315
|
+
}
|
|
316
|
+
const deleted = await this.persistence.deleteThread(threadId);
|
|
317
|
+
if (deleted) {
|
|
318
|
+
await this.deleteThreadCheckpoints(threadId);
|
|
319
|
+
}
|
|
320
|
+
return deleted;
|
|
321
|
+
}
|
|
282
322
|
async createToolMcpServer(options) {
|
|
283
323
|
const tools = this.resolveAgentTools(options.agentId).map(({ compiledTool, resolvedTool }) => ({
|
|
284
324
|
compiledTool,
|
|
@@ -385,6 +425,72 @@ export class AgentHarnessRuntime {
|
|
|
385
425
|
const priorHistory = await this.loadPriorHistory(threadId, runId);
|
|
386
426
|
return this.runtimeAdapter.invoke(binding, input, threadId, runId, resumePayload, priorHistory, options);
|
|
387
427
|
}
|
|
428
|
+
buildPersistedRunRequest(input, invocation) {
|
|
429
|
+
const envelope = invocation.invocation ?? {
|
|
430
|
+
...(invocation.context ? { context: invocation.context } : {}),
|
|
431
|
+
...(invocation.state ? { inputs: invocation.state } : {}),
|
|
432
|
+
...(invocation.files ? { attachments: invocation.files } : {}),
|
|
433
|
+
};
|
|
434
|
+
return {
|
|
435
|
+
input: normalizeMessageContent(input),
|
|
436
|
+
invocation: envelope && Object.keys(envelope).length > 0
|
|
437
|
+
? {
|
|
438
|
+
...(envelope.context ? { context: envelope.context } : {}),
|
|
439
|
+
...(envelope.inputs ? { inputs: envelope.inputs } : {}),
|
|
440
|
+
...(envelope.attachments ? { attachments: envelope.attachments } : {}),
|
|
441
|
+
...(envelope.capabilities ? { capabilities: envelope.capabilities } : {}),
|
|
442
|
+
}
|
|
443
|
+
: undefined,
|
|
444
|
+
savedAt: new Date().toISOString(),
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
async executeQueuedRun(binding, input, threadId, runId, agentId, options = {}) {
|
|
448
|
+
const previousState = options.previousState ?? "running";
|
|
449
|
+
if (previousState === "queued") {
|
|
450
|
+
await this.emit(threadId, runId, 101, "run.dequeued", {
|
|
451
|
+
queuePosition: 0,
|
|
452
|
+
activeRunCount: this.activeRunSlots,
|
|
453
|
+
maxConcurrentRuns: this.concurrencyConfig.maxConcurrentRuns,
|
|
454
|
+
recoveredOnStartup: true,
|
|
455
|
+
});
|
|
456
|
+
await this.setRunStateAndEmit(threadId, runId, 102, "running", {
|
|
457
|
+
previousState: "queued",
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
try {
|
|
461
|
+
const actual = await this.invokeWithHistory(binding, input, threadId, runId, undefined, {
|
|
462
|
+
context: options.context,
|
|
463
|
+
state: options.state,
|
|
464
|
+
files: options.files,
|
|
465
|
+
});
|
|
466
|
+
const finalized = await this.finalizeContinuedRun(threadId, runId, input, actual, {
|
|
467
|
+
previousState: previousState === "queued" ? "running" : previousState,
|
|
468
|
+
stateSequence: options.stateSequence ?? 103,
|
|
469
|
+
approvalSequence: options.approvalSequence ?? 104,
|
|
470
|
+
});
|
|
471
|
+
return {
|
|
472
|
+
...finalized,
|
|
473
|
+
agentId,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
await this.emitSyntheticFallback(threadId, runId, agentId, error, 103);
|
|
478
|
+
await this.setRunStateAndEmit(threadId, runId, 104, "failed", {
|
|
479
|
+
previousState: previousState === "queued" ? "running" : previousState,
|
|
480
|
+
error: error instanceof Error ? error.message : String(error),
|
|
481
|
+
});
|
|
482
|
+
return {
|
|
483
|
+
threadId,
|
|
484
|
+
runId,
|
|
485
|
+
agentId,
|
|
486
|
+
state: "failed",
|
|
487
|
+
output: renderRuntimeFailure(error),
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
finally {
|
|
491
|
+
await this.persistence.clearRunRequest(threadId, runId);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
388
494
|
checkpointRefForState(threadId, runId, state) {
|
|
389
495
|
return state === "waiting_for_approval" ? `checkpoints/${threadId}/${runId}/cp-1` : null;
|
|
390
496
|
}
|
|
@@ -492,17 +598,56 @@ export class AgentHarnessRuntime {
|
|
|
492
598
|
}
|
|
493
599
|
await listener(value);
|
|
494
600
|
}
|
|
495
|
-
async acquireRunSlot() {
|
|
601
|
+
async acquireRunSlot(threadId, runId, activeState = "running") {
|
|
496
602
|
const maxConcurrentRuns = this.concurrencyConfig.maxConcurrentRuns;
|
|
497
603
|
if (!maxConcurrentRuns) {
|
|
498
604
|
return () => undefined;
|
|
499
605
|
}
|
|
500
|
-
if (this.activeRunSlots
|
|
501
|
-
|
|
502
|
-
|
|
606
|
+
if (this.activeRunSlots < maxConcurrentRuns) {
|
|
607
|
+
this.activeRunSlots += 1;
|
|
608
|
+
let released = false;
|
|
609
|
+
return () => {
|
|
610
|
+
if (released) {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
released = true;
|
|
614
|
+
this.activeRunSlots = Math.max(0, this.activeRunSlots - 1);
|
|
615
|
+
const next = this.pendingRunSlots.shift();
|
|
616
|
+
void next?.();
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
if (threadId && runId) {
|
|
620
|
+
const queuePosition = this.pendingRunSlots.length + 1;
|
|
621
|
+
await this.setRunStateAndEmit(threadId, runId, 2, "queued", {
|
|
622
|
+
previousState: activeState,
|
|
623
|
+
});
|
|
624
|
+
await this.emit(threadId, runId, 3, "run.queued", {
|
|
625
|
+
queuePosition,
|
|
626
|
+
activeRunCount: this.activeRunSlots,
|
|
627
|
+
maxConcurrentRuns,
|
|
503
628
|
});
|
|
504
629
|
}
|
|
505
|
-
|
|
630
|
+
await new Promise((resolve, reject) => {
|
|
631
|
+
this.pendingRunSlots.push(async () => {
|
|
632
|
+
try {
|
|
633
|
+
this.activeRunSlots += 1;
|
|
634
|
+
if (threadId && runId) {
|
|
635
|
+
await this.emit(threadId, runId, 4, "run.dequeued", {
|
|
636
|
+
queuePosition: 0,
|
|
637
|
+
activeRunCount: this.activeRunSlots,
|
|
638
|
+
maxConcurrentRuns,
|
|
639
|
+
});
|
|
640
|
+
await this.setRunStateAndEmit(threadId, runId, 5, activeState, {
|
|
641
|
+
previousState: "queued",
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
resolve();
|
|
645
|
+
}
|
|
646
|
+
catch (error) {
|
|
647
|
+
reject(error);
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
});
|
|
506
651
|
let released = false;
|
|
507
652
|
return () => {
|
|
508
653
|
if (released) {
|
|
@@ -511,7 +656,7 @@ export class AgentHarnessRuntime {
|
|
|
511
656
|
released = true;
|
|
512
657
|
this.activeRunSlots = Math.max(0, this.activeRunSlots - 1);
|
|
513
658
|
const next = this.pendingRunSlots.shift();
|
|
514
|
-
next?.();
|
|
659
|
+
void next?.();
|
|
515
660
|
};
|
|
516
661
|
}
|
|
517
662
|
async dispatchRunListeners(stream, listeners) {
|
|
@@ -587,88 +732,68 @@ export class AgentHarnessRuntime {
|
|
|
587
732
|
if (options.listeners) {
|
|
588
733
|
return this.dispatchRunListeners(this.streamEvents(options), options.listeners);
|
|
589
734
|
}
|
|
590
|
-
const
|
|
735
|
+
const invocation = this.normalizeInvocationEnvelope(options);
|
|
736
|
+
const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
|
|
737
|
+
const binding = this.workspace.bindings.get(selectedAgentId);
|
|
738
|
+
if (!binding) {
|
|
739
|
+
throw new Error(`Unknown agent ${selectedAgentId}`);
|
|
740
|
+
}
|
|
741
|
+
const policyDecision = this.policyEngine.evaluate(binding);
|
|
742
|
+
if (!policyDecision.allowed) {
|
|
743
|
+
throw new Error(`Policy evaluation blocked agent ${selectedAgentId}: ${policyDecision.reasons.join(", ")}`);
|
|
744
|
+
}
|
|
745
|
+
const { threadId, runId } = await this.ensureThreadStarted(selectedAgentId, binding, options.input, options.threadId);
|
|
746
|
+
await this.persistence.saveRunRequest(threadId, runId, this.buildPersistedRunRequest(options.input, invocation));
|
|
747
|
+
await this.emitRunCreated(threadId, runId, {
|
|
748
|
+
agentId: binding.agent.id,
|
|
749
|
+
requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
|
|
750
|
+
selectedAgentId,
|
|
751
|
+
executionMode: getBindingAdapterKind(binding),
|
|
752
|
+
});
|
|
753
|
+
const releaseRunSlot = await this.acquireRunSlot(threadId, runId);
|
|
591
754
|
try {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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),
|
|
755
|
+
return await this.executeQueuedRun(binding, options.input, threadId, runId, selectedAgentId, {
|
|
756
|
+
context: invocation.context,
|
|
757
|
+
state: invocation.state,
|
|
758
|
+
files: invocation.files,
|
|
759
|
+
previousState: "running",
|
|
760
|
+
stateSequence: 6,
|
|
761
|
+
approvalSequence: 7,
|
|
608
762
|
});
|
|
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
|
-
}
|
|
639
763
|
}
|
|
640
764
|
finally {
|
|
641
765
|
releaseRunSlot();
|
|
642
766
|
}
|
|
643
767
|
}
|
|
644
768
|
async *streamEvents(options) {
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
content: `${line}\n`,
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
|
-
return;
|
|
769
|
+
const invocation = this.normalizeInvocationEnvelope(options);
|
|
770
|
+
const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
|
|
771
|
+
const binding = this.workspace.bindings.get(selectedAgentId);
|
|
772
|
+
if (!binding) {
|
|
773
|
+
const result = await this.run(options);
|
|
774
|
+
for (const line of result.output.split("\n")) {
|
|
775
|
+
yield {
|
|
776
|
+
type: "content",
|
|
777
|
+
threadId: result.threadId,
|
|
778
|
+
runId: result.runId,
|
|
779
|
+
agentId: result.agentId ?? selectedAgentId,
|
|
780
|
+
content: `${line}\n`,
|
|
781
|
+
};
|
|
662
782
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
let emitted = false;
|
|
786
|
+
const { threadId, runId } = await this.ensureThreadStarted(selectedAgentId, binding, options.input, options.threadId);
|
|
787
|
+
await this.persistence.saveRunRequest(threadId, runId, this.buildPersistedRunRequest(options.input, invocation));
|
|
788
|
+
yield { type: "event", event: await this.emitRunCreated(threadId, runId, {
|
|
789
|
+
agentId: selectedAgentId,
|
|
790
|
+
requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
|
|
791
|
+
selectedAgentId,
|
|
792
|
+
input: options.input,
|
|
793
|
+
state: "running",
|
|
794
|
+
}) };
|
|
795
|
+
const releaseRunSlot = await this.acquireRunSlot(threadId, runId);
|
|
796
|
+
try {
|
|
672
797
|
try {
|
|
673
798
|
const priorHistory = await this.loadPriorHistory(threadId, runId);
|
|
674
799
|
let assistantOutput = "";
|
|
@@ -686,11 +811,11 @@ export class AgentHarnessRuntime {
|
|
|
686
811
|
: chunk;
|
|
687
812
|
if (normalizedChunk.kind === "interrupt") {
|
|
688
813
|
const checkpointRef = `checkpoints/${threadId}/${runId}/cp-1`;
|
|
689
|
-
const waitingEvent = await this.setRunStateAndEmit(threadId, runId,
|
|
690
|
-
previousState:
|
|
814
|
+
const waitingEvent = await this.setRunStateAndEmit(threadId, runId, 6, "waiting_for_approval", {
|
|
815
|
+
previousState: "running",
|
|
691
816
|
checkpointRef,
|
|
692
817
|
});
|
|
693
|
-
const approvalRequest = await this.requestApprovalAndEmit(threadId, runId, options.input, normalizedChunk.content, checkpointRef,
|
|
818
|
+
const approvalRequest = await this.requestApprovalAndEmit(threadId, runId, options.input, normalizedChunk.content, checkpointRef, 7);
|
|
694
819
|
yield {
|
|
695
820
|
type: "event",
|
|
696
821
|
event: waitingEvent,
|
|
@@ -783,22 +908,22 @@ export class AgentHarnessRuntime {
|
|
|
783
908
|
finalMessageText: assistantOutput,
|
|
784
909
|
},
|
|
785
910
|
};
|
|
786
|
-
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId,
|
|
787
|
-
previousState:
|
|
911
|
+
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 6, "completed", {
|
|
912
|
+
previousState: "running",
|
|
788
913
|
}) };
|
|
789
914
|
return;
|
|
790
915
|
}
|
|
791
916
|
catch (error) {
|
|
792
917
|
if (emitted) {
|
|
793
|
-
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId,
|
|
794
|
-
previousState:
|
|
918
|
+
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 6, "failed", {
|
|
919
|
+
previousState: "running",
|
|
795
920
|
error: error instanceof Error ? error.message : String(error),
|
|
796
921
|
}) };
|
|
797
922
|
return;
|
|
798
923
|
}
|
|
799
924
|
if (error instanceof RuntimeOperationTimeoutError && error.stage === "invoke") {
|
|
800
|
-
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId,
|
|
801
|
-
previousState:
|
|
925
|
+
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 6, "failed", {
|
|
926
|
+
previousState: "running",
|
|
802
927
|
error: error.message,
|
|
803
928
|
}) };
|
|
804
929
|
yield {
|
|
@@ -836,15 +961,15 @@ export class AgentHarnessRuntime {
|
|
|
836
961
|
agentId: selectedAgentId,
|
|
837
962
|
},
|
|
838
963
|
};
|
|
839
|
-
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId,
|
|
840
|
-
previousState:
|
|
964
|
+
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 6, actual.state, {
|
|
965
|
+
previousState: "running",
|
|
841
966
|
}) };
|
|
842
967
|
return;
|
|
843
968
|
}
|
|
844
969
|
catch (invokeError) {
|
|
845
970
|
await this.emitSyntheticFallback(threadId, runId, selectedAgentId, invokeError);
|
|
846
|
-
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId,
|
|
847
|
-
previousState:
|
|
971
|
+
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 6, "failed", {
|
|
972
|
+
previousState: "running",
|
|
848
973
|
error: invokeError instanceof Error ? invokeError.message : String(invokeError),
|
|
849
974
|
}) };
|
|
850
975
|
yield {
|
|
@@ -870,29 +995,30 @@ export class AgentHarnessRuntime {
|
|
|
870
995
|
}
|
|
871
996
|
}
|
|
872
997
|
finally {
|
|
998
|
+
await this.persistence.clearRunRequest(threadId, runId);
|
|
873
999
|
releaseRunSlot();
|
|
874
1000
|
}
|
|
875
1001
|
}
|
|
876
1002
|
async resume(options) {
|
|
877
|
-
const
|
|
1003
|
+
const approvalById = options.approvalId ? await this.persistence.getApproval(options.approvalId) : null;
|
|
1004
|
+
const thread = options.threadId
|
|
1005
|
+
? await this.getSession(options.threadId)
|
|
1006
|
+
: approvalById
|
|
1007
|
+
? await this.getSession(approvalById.threadId)
|
|
1008
|
+
: null;
|
|
1009
|
+
if (!thread) {
|
|
1010
|
+
throw new Error("resume requires either threadId or approvalId");
|
|
1011
|
+
}
|
|
1012
|
+
const approval = approvalById ?? await this.resolveApprovalRecord(options, thread);
|
|
1013
|
+
const threadId = approval.threadId;
|
|
1014
|
+
const runId = approval.runId;
|
|
1015
|
+
const binding = this.workspace.bindings.get(thread.agentId);
|
|
1016
|
+
if (!binding) {
|
|
1017
|
+
throw new Error(`Unknown agent ${thread.agentId}`);
|
|
1018
|
+
}
|
|
1019
|
+
await this.persistence.setRunState(threadId, runId, "resuming", `checkpoints/${threadId}/${runId}/cp-1`);
|
|
1020
|
+
const releaseRunSlot = await this.acquireRunSlot(threadId, runId, "resuming");
|
|
878
1021
|
try {
|
|
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
1022
|
await this.persistence.saveRecoveryIntent(threadId, runId, {
|
|
897
1023
|
kind: "approval-decision",
|
|
898
1024
|
savedAt: new Date().toISOString(),
|
|
@@ -978,12 +1104,72 @@ export class AgentHarnessRuntime {
|
|
|
978
1104
|
await this.close();
|
|
979
1105
|
}
|
|
980
1106
|
async recoverStartupRuns() {
|
|
981
|
-
if (!this.recoveryConfig.enabled
|
|
1107
|
+
if (!this.recoveryConfig.enabled) {
|
|
982
1108
|
return;
|
|
983
1109
|
}
|
|
984
1110
|
const threads = await this.persistence.listSessions();
|
|
985
1111
|
for (const thread of threads) {
|
|
986
|
-
if (thread.status
|
|
1112
|
+
if (thread.status === "queued") {
|
|
1113
|
+
const runMeta = await this.persistence.getRunMeta(thread.threadId, thread.latestRunId);
|
|
1114
|
+
const binding = this.workspace.bindings.get(runMeta.agentId);
|
|
1115
|
+
if (!binding) {
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
const request = await this.persistence.getRunRequest(thread.threadId, thread.latestRunId);
|
|
1119
|
+
if (!request) {
|
|
1120
|
+
await this.setRunStateAndEmit(thread.threadId, thread.latestRunId, 100, "failed", {
|
|
1121
|
+
previousState: "queued",
|
|
1122
|
+
error: "missing persisted run request for queued run recovery",
|
|
1123
|
+
});
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
const releaseRunSlot = await this.acquireRunSlot();
|
|
1127
|
+
try {
|
|
1128
|
+
await this.executeQueuedRun(binding, request.input, thread.threadId, thread.latestRunId, runMeta.agentId, {
|
|
1129
|
+
context: request.invocation?.context,
|
|
1130
|
+
state: request.invocation?.inputs,
|
|
1131
|
+
files: request.invocation?.attachments,
|
|
1132
|
+
previousState: "queued",
|
|
1133
|
+
stateSequence: 103,
|
|
1134
|
+
approvalSequence: 104,
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
finally {
|
|
1138
|
+
releaseRunSlot();
|
|
1139
|
+
}
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1142
|
+
if (thread.status === "running") {
|
|
1143
|
+
const runMeta = await this.persistence.getRunMeta(thread.threadId, thread.latestRunId);
|
|
1144
|
+
const binding = this.workspace.bindings.get(runMeta.agentId);
|
|
1145
|
+
if (!binding || !this.supportsRunningReplay(binding)) {
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1148
|
+
const request = await this.persistence.getRunRequest(thread.threadId, thread.latestRunId);
|
|
1149
|
+
if (!request) {
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
const releaseRunSlot = await this.acquireRunSlot();
|
|
1153
|
+
try {
|
|
1154
|
+
await this.emit(thread.threadId, thread.latestRunId, 100, "run.resumed", {
|
|
1155
|
+
resumeKind: "startup-running-recovery",
|
|
1156
|
+
state: "running",
|
|
1157
|
+
});
|
|
1158
|
+
await this.executeQueuedRun(binding, request.input, thread.threadId, thread.latestRunId, runMeta.agentId, {
|
|
1159
|
+
context: request.invocation?.context,
|
|
1160
|
+
state: request.invocation?.inputs,
|
|
1161
|
+
files: request.invocation?.attachments,
|
|
1162
|
+
previousState: "running",
|
|
1163
|
+
stateSequence: 103,
|
|
1164
|
+
approvalSequence: 104,
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
finally {
|
|
1168
|
+
releaseRunSlot();
|
|
1169
|
+
}
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
1172
|
+
if (thread.status !== "resuming" || !this.recoveryConfig.resumeResumingRunsOnStartup) {
|
|
987
1173
|
continue;
|
|
988
1174
|
}
|
|
989
1175
|
const binding = this.workspace.bindings.get(thread.agentId);
|
package/dist/tool-modules.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type LoadedToolModule = {
|
|
|
9
9
|
invoke: (input: unknown, context?: Record<string, unknown>) => Promise<unknown> | unknown;
|
|
10
10
|
schema: SchemaLike;
|
|
11
11
|
description: string;
|
|
12
|
+
retryable?: boolean;
|
|
12
13
|
};
|
|
13
14
|
export declare function isSupportedToolModulePath(filePath: string): boolean;
|
|
14
15
|
export declare function discoverAnnotatedFunctionNames(sourceText: string): string[];
|