@tangle-network/agent-runtime 0.314.1 → 0.316.0
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 +6 -0
- package/dist/{activation-BpMnOlw5.d.ts → activation-BFDmjaKP.d.ts} +2 -2
- package/dist/{activation-Ci20OAsG.js → activation-CvdE2HIU.js} +3 -3
- package/dist/{activation-Ci20OAsG.js.map → activation-CvdE2HIU.js.map} +1 -1
- package/dist/agent.d.ts +2 -1
- package/dist/agent.js +2 -2
- package/dist/{bundle-BK3XBY6q.js → bundle-RgHpAFDY.js} +2 -2
- package/dist/{bundle-BK3XBY6q.js.map → bundle-RgHpAFDY.js.map} +1 -1
- package/dist/candidate-execution/index.js +5 -5
- package/dist/{coordination-driver-CyJF37L8.js → coordination-driver-DGhN8GO9.js} +3 -3
- package/dist/{coordination-driver-CyJF37L8.js.map → coordination-driver-DGhN8GO9.js.map} +1 -1
- package/dist/{delegate-CTezpiM2.js → delegate-B5pmSbQa.js} +2 -2
- package/dist/{delegate-CTezpiM2.js.map → delegate-B5pmSbQa.js.map} +1 -1
- package/dist/durable.d.ts +1 -1
- package/dist/durable.js +4 -4
- package/dist/{graph-DGDrO_ZW.js → graph-G0j1ysqe.js} +4 -4
- package/dist/{graph-DGDrO_ZW.js.map → graph-G0j1ysqe.js.map} +1 -1
- package/dist/{improvement-cycle-DReg-Qi4.js → improvement-cycle-D7yR6GC2.js} +6 -6
- package/dist/{improvement-cycle-DReg-Qi4.js.map → improvement-cycle-D7yR6GC2.js.map} +1 -1
- package/dist/{index-B89bMKhD.d.ts → index-CUh40yUJ.d.ts} +13 -90
- package/dist/index.d.ts +4 -4
- package/dist/index.js +9 -9
- package/dist/integrations-DopfCC2a.d.ts +190 -0
- package/dist/integrations-dFi-ALk7.js +132 -0
- package/dist/integrations-dFi-ALk7.js.map +1 -0
- package/dist/intelligence.d.ts +2 -2
- package/dist/intelligence.js +7 -7
- package/dist/kernel.d.ts +2 -2
- package/dist/kernel.js +9 -9
- package/dist/{loop-runner-bin-eoycPwY3.js → loop-runner-bin-CFVX4oi0.js} +3 -3
- package/dist/{loop-runner-bin-eoycPwY3.js.map → loop-runner-bin-CFVX4oi0.js.map} +1 -1
- package/dist/{loop-runner-bin-BM1in-RT.d.ts → loop-runner-bin-DQKLUt0l.d.ts} +2 -2
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +74 -33
- package/dist/mcp/index.js +129 -6
- package/dist/mcp/index.js.map +1 -1
- package/dist/{openai-tools-xPO6dzf4.d.ts → openai-tools-u3gWBi3x.d.ts} +2 -2
- package/dist/placement.d.ts +132 -0
- package/dist/placement.js +319 -0
- package/dist/placement.js.map +1 -0
- package/dist/platform.d.ts +1 -188
- package/dist/platform.js +1 -129
- package/dist/platform.js.map +1 -1
- package/dist/{prepare-8A1CAWG8.js → prepare-CD9kR0gS.js} +3 -3
- package/dist/{prepare-8A1CAWG8.js.map → prepare-CD9kR0gS.js.map} +1 -1
- package/dist/{profile-BhQShsM1.js → profile-CHXYmpGd.js} +2 -2
- package/dist/{profile-BhQShsM1.js.map → profile-CHXYmpGd.js.map} +1 -1
- package/dist/profile-materialization-Cca615eu.d.ts +90 -0
- package/dist/profiles.d.ts +1 -1
- package/dist/{protected-model-port-ug52OmvB.js → protected-model-port-B0pHd65J.js} +4 -4
- package/dist/{protected-model-port-ug52OmvB.js.map → protected-model-port-B0pHd65J.js.map} +1 -1
- package/dist/{provision-supervisor-B6D3XRbL.js → provision-supervisor-BQLLrUtn.js} +4 -4
- package/dist/{provision-supervisor-B6D3XRbL.js.map → provision-supervisor-BQLLrUtn.js.map} +1 -1
- package/dist/{runtime-NlIVxRXu.js → runtime-B9BOFyEp.js} +10 -9
- package/dist/runtime-B9BOFyEp.js.map +1 -0
- package/dist/{search-executor-DFs-79tK.js → search-executor-B5ZVFsDV.js} +3 -3
- package/dist/{search-executor-DFs-79tK.js.map → search-executor-B5ZVFsDV.js.map} +1 -1
- package/dist/{server-B8ubWXpW.js → server-TwpH9aik.js} +4 -4
- package/dist/{server-B8ubWXpW.js.map → server-TwpH9aik.js.map} +1 -1
- package/dist/{supervise-BE9ta8bl.js → supervise-CU6AuxWY.js} +119 -29
- package/dist/supervise-CU6AuxWY.js.map +1 -0
- package/dist/{supervisor-DmSuKksw.js → supervisor-CbFyf48U.js} +3 -3
- package/dist/{supervisor-DmSuKksw.js.map → supervisor-CbFyf48U.js.map} +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +14 -14
- package/dist/tool-loop.d.ts +35 -5
- package/dist/tool-loop.js +48 -21
- package/dist/tool-loop.js.map +1 -1
- package/dist/tui/index.d.ts +1 -1
- package/dist/tui/index.js +1 -1
- package/dist/workspace-store/index.d.ts +206 -0
- package/dist/workspace-store/index.js +855 -0
- package/dist/workspace-store/index.js.map +1 -0
- package/dist/{workspace-streams-BEvSX97P.js → workspace-streams-DkijayqX.js} +16 -3
- package/dist/workspace-streams-DkijayqX.js.map +1 -0
- package/package.json +14 -4
- package/skills/profile-authoring/SKILL.md +114 -171
- package/dist/runtime-NlIVxRXu.js.map +0 -1
- package/dist/supervise-BE9ta8bl.js.map +0 -1
- package/dist/workspace-streams-BEvSX97P.js.map +0 -1
package/dist/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Av as
|
|
1
|
+
import { Av as ToolLoopCallContext, Bt as RunGraphTestOptions, Dc as superviseWithTestBrain, Tc as SuperviseTestOptions, Wt as runGraphWithTestBrain, jv as ToolLoopChat, mn as driverAgent, pn as DriverAgentOptions, ul as SupervisorAgentTestDeps, yl as supervisorAgentWithTestBrain } from "./index-CUh40yUJ.js";
|
|
2
2
|
import { AgentImprovementProposal, AgentProfile, AgentProfileImprovementMeasuredComparison, SandboxSizePreset } from "@tangle-network/agent-interface";
|
|
3
3
|
//#region src/testing/index.d.ts
|
|
4
4
|
/** A proposal produced by Runtime's opaque profile-improvement path. */
|
package/dist/testing.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { f as verifyAgentImprovementProposal } from "./improvement-cycle-
|
|
3
|
-
import { i as applyExactAgentProfileDiff, p as parseExactAgentProfile } from "./profile-
|
|
4
|
-
import { r as driverAgent } from "./coordination-driver-
|
|
5
|
-
import { a as superviseWithTestBrain, m as supervisorAgentWithTestBrain } from "./supervise-
|
|
6
|
-
import { i as runGraphWithTestBrain } from "./graph-
|
|
1
|
+
import { I as immutableCandidateValue, M as canonicalCandidateDigest$1 } from "./workspace-streams-DkijayqX.js";
|
|
2
|
+
import { f as verifyAgentImprovementProposal } from "./improvement-cycle-D7yR6GC2.js";
|
|
3
|
+
import { i as applyExactAgentProfileDiff, p as parseExactAgentProfile } from "./profile-CHXYmpGd.js";
|
|
4
|
+
import { r as driverAgent } from "./coordination-driver-DGhN8GO9.js";
|
|
5
|
+
import { a as superviseWithTestBrain, m as supervisorAgentWithTestBrain } from "./supervise-CU6AuxWY.js";
|
|
6
|
+
import { i as runGraphWithTestBrain } from "./graph-G0j1ysqe.js";
|
|
7
7
|
import { SANDBOX_SIZE_PRESET_NAMES, canonicalAgentProfileDigest } from "@tangle-network/agent-interface";
|
|
8
8
|
//#region src/testing/fixtures/agent-improvement-proposal.json
|
|
9
9
|
var agent_improvement_proposal_default = {
|
|
10
10
|
changedSurfaces: ["prompt"],
|
|
11
|
-
digest: "sha256:
|
|
11
|
+
digest: "sha256:a9a15f7a7a0d46045f8ec065659d19154e192e4e158a56869dd4f6d68f7b0cd5",
|
|
12
12
|
evaluation: {
|
|
13
13
|
"decision": {
|
|
14
14
|
"contributingChecks": [
|
|
@@ -4579,7 +4579,7 @@ var agent_improvement_proposal_default = {
|
|
|
4579
4579
|
],
|
|
4580
4580
|
"metadata": {
|
|
4581
4581
|
"fixture": "agent-improvement-proposal",
|
|
4582
|
-
"runtimeVersion": "0.
|
|
4582
|
+
"runtimeVersion": "0.316.0"
|
|
4583
4583
|
},
|
|
4584
4584
|
"objectives": [
|
|
4585
4585
|
{
|
|
@@ -4690,8 +4690,8 @@ var agent_improvement_proposal_default = {
|
|
|
4690
4690
|
"baselineContentHash": "sha256:5c21ee53e513fc604cb09754e21c392b24a424da0ef37dbf8f1ee4a8a0b08f09",
|
|
4691
4691
|
"candidateContentHash": "sha256:60fcbb1c728194bd51d7d19cb732d1c3f1881dce7e0a6266b41c8b98cfd65693",
|
|
4692
4692
|
"kind": "agent-eval-loop",
|
|
4693
|
-
"recordDigest": "sha256:
|
|
4694
|
-
"runId": "agent-runtime-0.
|
|
4693
|
+
"recordDigest": "sha256:2220856da7165d815cd6182b0d333b3db190ac767616f2493011b3656223d3da",
|
|
4694
|
+
"runId": "agent-runtime-0.316.0-proposal-fixture",
|
|
4695
4695
|
"schema": "agent-candidate-experiment"
|
|
4696
4696
|
}
|
|
4697
4697
|
},
|
|
@@ -4714,13 +4714,13 @@ var agent_improvement_proposal_default = {
|
|
|
4714
4714
|
}],
|
|
4715
4715
|
kind: "agent-improvement-proposal",
|
|
4716
4716
|
proposedAt: "2026-07-10T01:00:00.000Z",
|
|
4717
|
-
runId: "agent-runtime-0.
|
|
4717
|
+
runId: "agent-runtime-0.316.0-proposal-fixture"
|
|
4718
4718
|
};
|
|
4719
4719
|
//#endregion
|
|
4720
4720
|
//#region src/testing/fixtures/agent-profile-improvement-proposal.json
|
|
4721
4721
|
var agent_profile_improvement_proposal_default = {
|
|
4722
4722
|
changedSurfaces: ["prompt", "skills"],
|
|
4723
|
-
digest: "sha256:
|
|
4723
|
+
digest: "sha256:9ac9d2ca85137901e2368e4b8829392b658a026c0ec9b6ca4a74805acd7a9da2",
|
|
4724
4724
|
evaluation: {
|
|
4725
4725
|
"decision": {
|
|
4726
4726
|
"contributingChecks": [
|
|
@@ -6354,7 +6354,7 @@ var agent_profile_improvement_proposal_default = {
|
|
|
6354
6354
|
],
|
|
6355
6355
|
"metadata": {
|
|
6356
6356
|
"fixture": "agent-profile-improvement-proposal",
|
|
6357
|
-
"runtimeVersion": "0.
|
|
6357
|
+
"runtimeVersion": "0.316.0"
|
|
6358
6358
|
},
|
|
6359
6359
|
"objectives": [
|
|
6360
6360
|
{
|
|
@@ -6465,7 +6465,7 @@ var agent_profile_improvement_proposal_default = {
|
|
|
6465
6465
|
"baselineContentHash": "sha256:21c495a37c418c10bde64fbaa188beddeed31f1f051ea60a6a6582a9ee0db704",
|
|
6466
6466
|
"candidateContentHash": "sha256:103f77bc8481601eef1ad5fe6ba84a40dffabc3a44f421f8c8559121edab84e9",
|
|
6467
6467
|
"kind": "agent-eval-loop",
|
|
6468
|
-
"recordDigest": "sha256:
|
|
6468
|
+
"recordDigest": "sha256:3b5bd31ff4221182c11d47ab2ddcfc34ed49c05b3790eead4226cc89a5a0972a",
|
|
6469
6469
|
"runId": "profile-improvement-1",
|
|
6470
6470
|
"schema": "agent-profile-improvement-experiment"
|
|
6471
6471
|
}
|
package/dist/tool-loop.d.ts
CHANGED
|
@@ -48,6 +48,28 @@ type ToolLoopMessage = {
|
|
|
48
48
|
tool_calls?: ToolLoopAssistantToolCall[];
|
|
49
49
|
tool_call_id?: string;
|
|
50
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* One progress report from a running tool, shaped like an MCP `notifications/progress`
|
|
53
|
+
* payload: a free-text `message`, a monotonic `progress` count with an optional
|
|
54
|
+
* `total`, and any structured `data` the caller's UI understands.
|
|
55
|
+
*/
|
|
56
|
+
interface ToolProgressUpdate {
|
|
57
|
+
message?: string;
|
|
58
|
+
progress?: number;
|
|
59
|
+
total?: number;
|
|
60
|
+
data?: unknown;
|
|
61
|
+
}
|
|
62
|
+
/** What the loop hands a running tool besides its call. */
|
|
63
|
+
interface ToolCallContext {
|
|
64
|
+
/**
|
|
65
|
+
* Report progress while the call runs. The streaming loop yields each update as a
|
|
66
|
+
* `tool_progress` step, in order, before the call's `tool_result`; updates sent after
|
|
67
|
+
* the call settles are dropped.
|
|
68
|
+
*/
|
|
69
|
+
progress: (update: ToolProgressUpdate) => void;
|
|
70
|
+
}
|
|
71
|
+
/** Executes one tool call. Implementations that take only the call stay valid. */
|
|
72
|
+
type ToolCallExecutor = (call: ToolLoopCall, context: ToolCallContext) => Promise<ToolCallOutcome>;
|
|
51
73
|
type ToolLoopEvent = {
|
|
52
74
|
type: 'text';
|
|
53
75
|
text: string;
|
|
@@ -81,7 +103,9 @@ interface RunToolLoopOptions {
|
|
|
81
103
|
userMessage: string;
|
|
82
104
|
priorMessages?: ToolLoopMessage[];
|
|
83
105
|
streamTurn: (messages: ToolLoopMessage[]) => AsyncIterable<ToolLoopEvent>;
|
|
84
|
-
executeToolCall:
|
|
106
|
+
executeToolCall: ToolCallExecutor;
|
|
107
|
+
/** Receives each progress update a running tool reports, in order. */
|
|
108
|
+
onToolProgress?: (call: ToolLoopCall, update: ToolProgressUpdate) => void;
|
|
85
109
|
isExecutableTool: (toolName: string) => boolean;
|
|
86
110
|
/** Runaway-backstop cap. Default 200 — set far above any legitimate workflow.
|
|
87
111
|
* For per-workflow limits, use `maxCostUsd` or `deadlineMs` instead. */
|
|
@@ -106,6 +130,11 @@ declare function runToolLoop(opts: RunToolLoopOptions): Promise<ToolLoopResult>;
|
|
|
106
130
|
type StreamToolLoopYield<Raw> = {
|
|
107
131
|
kind: 'event';
|
|
108
132
|
event: Raw;
|
|
133
|
+
} | {
|
|
134
|
+
kind: 'tool_progress';
|
|
135
|
+
toolName: string;
|
|
136
|
+
toolCallId?: string;
|
|
137
|
+
update: ToolProgressUpdate;
|
|
109
138
|
} | {
|
|
110
139
|
kind: 'tool_result';
|
|
111
140
|
toolName: string;
|
|
@@ -125,7 +154,7 @@ interface StreamToolLoopOptions<Raw> {
|
|
|
125
154
|
extractText: (event: Raw) => string;
|
|
126
155
|
extractToolCall: (event: Raw) => ToolLoopCall | null;
|
|
127
156
|
isExecutableTool: (toolName: string) => boolean;
|
|
128
|
-
executeToolCall:
|
|
157
|
+
executeToolCall: ToolCallExecutor;
|
|
129
158
|
/** Runaway-backstop cap. Default 200 — set far above any legitimate workflow. */
|
|
130
159
|
maxToolTurns?: number;
|
|
131
160
|
/** Wall-clock deadline in ms since epoch (Date.now()-based). */
|
|
@@ -141,9 +170,10 @@ interface StreamToolLoopOptions<Raw> {
|
|
|
141
170
|
hooks?: RuntimeHooks;
|
|
142
171
|
}
|
|
143
172
|
/** Streaming bounded tool loop: yields each raw turn event (the caller maps +
|
|
144
|
-
* telemetries + re-emits it)
|
|
145
|
-
* `capped` if it stops for any
|
|
173
|
+
* telemetries + re-emits it), each `tool_progress` a running tool reports, and
|
|
174
|
+
* each executed `tool_result`; emits one `capped` if it stops for any
|
|
175
|
+
* non-completed reason with calls still pending. */
|
|
146
176
|
declare function streamToolLoop<Raw>(opts: StreamToolLoopOptions<Raw>): AsyncGenerator<StreamToolLoopYield<Raw>, void, unknown>;
|
|
147
177
|
//#endregion
|
|
148
|
-
export { RunToolLoopOptions, StreamToolLoopOptions, StreamToolLoopYield, ToolCallOutcome, ToolLoopAssistantToolCall, ToolLoopCall, ToolLoopEvent, ToolLoopMessage, ToolLoopResult, ToolLoopStopReason, runToolLoop, streamToolLoop };
|
|
178
|
+
export { RunToolLoopOptions, StreamToolLoopOptions, StreamToolLoopYield, ToolCallContext, ToolCallExecutor, ToolCallOutcome, ToolLoopAssistantToolCall, ToolLoopCall, ToolLoopEvent, ToolLoopMessage, ToolLoopResult, ToolLoopStopReason, ToolProgressUpdate, runToolLoop, streamToolLoop };
|
|
149
179
|
//# sourceMappingURL=tool-loop.d.ts.map
|
package/dist/tool-loop.js
CHANGED
|
@@ -151,16 +151,11 @@ async function runToolLoop(opts) {
|
|
|
151
151
|
};
|
|
152
152
|
}
|
|
153
153
|
const callEventId = observer.toolCallBefore(toolTurn, turnEventId, callIndex, call);
|
|
154
|
-
let
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
ok: false,
|
|
160
|
-
code: "executor_error",
|
|
161
|
-
message: err instanceof Error ? err.message : String(err)
|
|
162
|
-
};
|
|
163
|
-
}
|
|
154
|
+
let settled = false;
|
|
155
|
+
const outcome = await executeGuarded(opts.executeToolCall, call, (update) => {
|
|
156
|
+
if (!settled) opts.onToolProgress?.(call, update);
|
|
157
|
+
});
|
|
158
|
+
settled = true;
|
|
164
159
|
if (opts.maxCostUsd !== void 0 && opts.costOf !== void 0) {
|
|
165
160
|
accumulatedCostUsd += opts.costOf(call, outcome);
|
|
166
161
|
if (accumulatedCostUsd >= opts.maxCostUsd) {
|
|
@@ -236,8 +231,9 @@ async function runToolLoop(opts) {
|
|
|
236
231
|
};
|
|
237
232
|
}
|
|
238
233
|
/** Streaming bounded tool loop: yields each raw turn event (the caller maps +
|
|
239
|
-
* telemetries + re-emits it)
|
|
240
|
-
* `capped` if it stops for any
|
|
234
|
+
* telemetries + re-emits it), each `tool_progress` a running tool reports, and
|
|
235
|
+
* each executed `tool_result`; emits one `capped` if it stops for any
|
|
236
|
+
* non-completed reason with calls still pending. */
|
|
241
237
|
async function* streamToolLoop(opts) {
|
|
242
238
|
const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS;
|
|
243
239
|
const render = opts.renderResult ?? defaultRender;
|
|
@@ -334,16 +330,35 @@ async function* streamToolLoop(opts) {
|
|
|
334
330
|
return;
|
|
335
331
|
}
|
|
336
332
|
const callEventId = observer.toolCallBefore(toolTurn, turnEventId, callIndex, call);
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
333
|
+
const progress = [];
|
|
334
|
+
let wake;
|
|
335
|
+
let settled = false;
|
|
336
|
+
const running = executeGuarded(opts.executeToolCall, call, (update) => {
|
|
337
|
+
if (settled) return;
|
|
338
|
+
progress.push(update);
|
|
339
|
+
wake?.();
|
|
340
|
+
}).then((result) => {
|
|
341
|
+
settled = true;
|
|
342
|
+
wake?.();
|
|
343
|
+
return result;
|
|
344
|
+
});
|
|
345
|
+
while (!settled || progress.length > 0) {
|
|
346
|
+
const update = progress.shift();
|
|
347
|
+
if (update !== void 0) {
|
|
348
|
+
yield {
|
|
349
|
+
kind: "tool_progress",
|
|
350
|
+
toolName: call.toolName,
|
|
351
|
+
toolCallId: call.toolCallId,
|
|
352
|
+
update
|
|
353
|
+
};
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
await new Promise((resolve) => {
|
|
357
|
+
wake = resolve;
|
|
358
|
+
});
|
|
359
|
+
wake = void 0;
|
|
346
360
|
}
|
|
361
|
+
const outcome = await running;
|
|
347
362
|
if (opts.maxCostUsd !== void 0 && opts.costOf !== void 0) {
|
|
348
363
|
accumulatedCostUsd += opts.costOf(call, outcome);
|
|
349
364
|
if (accumulatedCostUsd >= opts.maxCostUsd) {
|
|
@@ -408,6 +423,18 @@ async function* streamToolLoop(opts) {
|
|
|
408
423
|
});
|
|
409
424
|
}
|
|
410
425
|
}
|
|
426
|
+
/** Run one call; a thrown executor becomes an `executor_error` outcome. */
|
|
427
|
+
async function executeGuarded(execute, call, progress) {
|
|
428
|
+
try {
|
|
429
|
+
return await execute(call, { progress });
|
|
430
|
+
} catch (err) {
|
|
431
|
+
return {
|
|
432
|
+
ok: false,
|
|
433
|
+
code: "executor_error",
|
|
434
|
+
message: err instanceof Error ? err.message : String(err)
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
411
438
|
function createToolLoopObserver(hooks, runId, scenarioId) {
|
|
412
439
|
const loopEventId = `${runId}:agent.run`;
|
|
413
440
|
return {
|
package/dist/tool-loop.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-loop.js","names":[],"sources":["../src/tool-loop.ts"],"sourcesContent":["/**\n * Bounded turn-level tool-dispatch loop.\n *\n * `runAgentTaskStream` runs ONE model turn; `runAgentRounds` orchestrates DELEGATED\n * multi-agent topologies (refine / fanout-vote). Neither is the everyday\n * interactive shape: a chat turn where the model may emit tool calls, each is\n * executed, the results are folded back, and the turn re-runs until the model\n * stops (or a turn cap). Every agent app hand-rolls that loop — this is it,\n * as a reusable primitive.\n *\n * Substrate-neutral by design: the caller supplies `streamTurn` (wrapping\n * whatever backend / `runAgentTaskStream` it uses) and `executeToolCall`\n * (routing to its executors). This module owns the LOOP; the caller owns the\n * model and the executors. `Raw` (streaming variant) is the caller's own\n * event type. The only imported contract is the runtime hook type: hooks are\n * execution-scoped observers, not part of the agent profile.\n */\n\nimport type { RuntimeDecisionEvidenceRef, RuntimeHooks } from './runtime-hooks'\nimport { notifyRuntimeDecisionPoint, notifyRuntimeHookEvent } from './runtime-hooks'\n\nexport interface ToolLoopCall {\n toolCallId?: string\n toolName: string\n args: Record<string, unknown>\n}\n\n/** Outcome of one tool dispatch — structurally compatible with a hub/integration\n * tool-outcome union, so callers can fold either through the loop. */\nexport type ToolCallOutcome =\n | { ok: true; result: unknown }\n | { ok: false; code: string; message: string; status?: number }\n\n/** Runaway-backstop: stops an infinite tool loop where cost is unmetered. Set\n * far above any legitimate workflow — this is a watchdog, not a policy cap.\n * Legitimate per-call budgets come from `maxCostUsd` + `costOf`. */\nconst RUNAWAY_BACKSTOP_TURNS = 200\nconst DEFAULT_DECISION_CONTEXT_CHARS = 12_000\nconst FAILURE_RECOVERY_ACTIONS = ['retry', 'verify', 'continue', 'stop']\n/** Consecutive identical calls (same tool + canonical-JSON args) that trigger\n * stuck-loop detection. The window resets on any different call. */\nconst STUCK_LOOP_THRESHOLD = 3\n\n/** One OpenAI-shaped tool-call entry carried on an assistant message. */\nexport interface ToolLoopAssistantToolCall {\n id: string\n type: 'function'\n function: { name: string; arguments: string }\n}\n\n/**\n * A message in the running conversation the loop sends to `streamTurn`.\n *\n * The base `{ role, content }` covers `system` / `user` / plain `assistant`\n * turns. Two optional fields carry the OpenAI function-calling contract so a\n * strict model (Claude, and any OpenAI-compatible provider that validates tool\n * history) reads its own tool use back instead of re-issuing the same call:\n *\n * - an assistant turn that emitted tool calls carries `tool_calls`, and its\n * `content` is `null` when the turn was tool-only;\n * - each tool result is its own `{ role: 'tool', tool_call_id, content }`\n * message keyed to the call that produced it.\n *\n * Widening is additive: a `streamTurn` that reads only `role` + `content` still\n * works; one that forwards the whole message to an OpenAI-compatible endpoint\n * now sends correct tool history.\n */\nexport type ToolLoopMessage = {\n role: string\n content: string | null\n tool_calls?: ToolLoopAssistantToolCall[]\n tool_call_id?: string\n}\n\n/** A tool-call id is required to key a `role: 'tool'` result back to its call.\n * When the model omitted one, derive a stable id from the tool name so the\n * assistant `tool_calls` entry and its `tool` result still match. */\nfunction toolCallId(call: ToolLoopCall): string {\n return call.toolCallId ?? `call_${call.toolName}`\n}\n\n/** The assistant turn that emitted `pending`, in OpenAI shape: text content\n * (null when the turn was tool-only) plus its `tool_calls` array. */\nfunction assistantToolCallMessage(turnText: string, pending: ToolLoopCall[]): ToolLoopMessage {\n return {\n role: 'assistant',\n content: turnText.trim() || null,\n tool_calls: pending.map((call) => ({\n id: toolCallId(call),\n type: 'function',\n function: { name: call.toolName, arguments: JSON.stringify(call.args) },\n })),\n }\n}\n\n/** One `role: 'tool'` result message keyed to its call by `tool_call_id`. */\nfunction toolResultMessage(call: ToolLoopCall, content: string): ToolLoopMessage {\n return { role: 'tool', tool_call_id: toolCallId(call), content }\n}\n\nfunction defaultRender(label: string, outcome: ToolCallOutcome): string {\n if (outcome.ok) return `- ${label} → ok: ${JSON.stringify(outcome.result)}`\n return `- ${label} → failed (${outcome.code}): ${outcome.message}`\n}\n\n// ── Awaitable variant (drain-only callers, tests) ──────────────────────────\n\nexport type ToolLoopEvent =\n | { type: 'text'; text: string }\n | { type: 'tool_call'; call: ToolLoopCall }\n | { type: 'other'; event: unknown }\n\n/** Why the loop stopped. `completed` = model finished naturally; `stuck-loop` =\n * ≥3 consecutive identical tool calls (same tool + args); `backstop` = hit the\n * runaway-backstop cap (200 by default); `deadline` = wall-clock deadlineMs\n * exceeded; `budget` = maxCostUsd exhausted. Non-`completed` stops are infra /\n * resource outcomes — eval scoring must distinguish them from capability failure. */\nexport type ToolLoopStopReason = 'completed' | 'stuck-loop' | 'backstop' | 'deadline' | 'budget'\n\nexport interface ToolLoopResult {\n finalText: string\n toolResults: Array<{ call: ToolLoopCall; label: string; outcome: ToolCallOutcome }>\n turns: number\n stopReason: ToolLoopStopReason\n /** @deprecated Use `stopReason !== 'completed'` instead. */\n cappedOut: boolean\n}\n\nexport interface RunToolLoopOptions {\n systemPrompt: string\n userMessage: string\n priorMessages?: ToolLoopMessage[]\n streamTurn: (messages: ToolLoopMessage[]) => AsyncIterable<ToolLoopEvent>\n executeToolCall: (call: ToolLoopCall) => Promise<ToolCallOutcome>\n isExecutableTool: (toolName: string) => boolean\n /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow.\n * For per-workflow limits, use `maxCostUsd` or `deadlineMs` instead. */\n maxToolTurns?: number\n /** Wall-clock deadline in ms since epoch (Date.now()-based). When exceeded the\n * loop stops with stopReason `deadline`. */\n deadlineMs?: number\n /** Maximum total cost in USD. Requires `costOf` to meter each tool call. */\n maxCostUsd?: number\n /** Return the USD cost of one outcome. Required for `maxCostUsd` to work. */\n costOf?: (call: ToolLoopCall, outcome: ToolCallOutcome) => number\n renderResult?: (label: string, outcome: ToolCallOutcome) => string\n labelFor?: (call: ToolLoopCall) => string\n runId?: string\n scenarioId?: string\n hooks?: RuntimeHooks\n}\n\n/** Run the bounded tool loop and return the final text + every executed tool\n * outcome. Awaitable — callers needing to stream events to a UI use\n * {@link streamToolLoop}. */\nexport async function runToolLoop(opts: RunToolLoopOptions): Promise<ToolLoopResult> {\n const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS\n const render = opts.renderResult ?? defaultRender\n const labelFor = opts.labelFor ?? ((c: ToolLoopCall) => c.toolName)\n const runId = opts.runId ?? `agent-run-${randomSuffix()}`\n const messages: ToolLoopMessage[] = [\n { role: 'system', content: opts.systemPrompt },\n ...(opts.priorMessages ?? []),\n { role: 'user', content: opts.userMessage },\n ]\n const observer = createToolLoopObserver(opts.hooks, runId, opts.scenarioId)\n const toolResults: ToolLoopResult['toolResults'] = []\n let finalText = ''\n let turns = 0\n let accumulatedCostUsd = 0\n // Stuck-loop detection: track the last canonical-JSON call signature and how\n // many consecutive times we've seen it.\n let lastCallHash: string | null = null\n let consecutiveCount = 0\n\n observer.loopBefore(backstop, messages.length)\n\n for (let toolTurn = 0; ; toolTurn++) {\n turns++\n\n // Wall-clock deadline check — before every new turn.\n if (opts.deadlineMs !== undefined && Date.now() >= opts.deadlineMs) {\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'deadline' })\n return { finalText, toolResults, turns, stopReason: 'deadline', cappedOut: true }\n }\n\n let turnText = ''\n const pending: ToolLoopCall[] = []\n const turnEventId = observer.turnBefore(toolTurn, messages.length)\n for await (const ev of opts.streamTurn([...messages])) {\n if (ev.type === 'text') {\n turnText += ev.text\n finalText += ev.text\n } else if (ev.type === 'tool_call' && opts.isExecutableTool(ev.call.toolName)) {\n pending.push(ev.call)\n }\n }\n if (pending.length === 0) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: 0,\n finalTextChars: finalText.length,\n })\n break\n }\n\n // Runaway backstop — the model keeps emitting calls past the safety ceiling.\n if (toolTurn >= backstop) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'backstop',\n })\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'backstop' })\n return { finalText, toolResults, turns, stopReason: 'backstop', cappedOut: true }\n }\n\n // The assistant turn that emitted the calls, carrying its tool_calls array,\n // so a strict model reads its own tool use back in OpenAI shape.\n messages.push(assistantToolCallMessage(turnText, pending))\n const outcomes: ExecutedToolCall[] = []\n for (const [callIndex, call] of pending.entries()) {\n // Stuck-loop detection: hash the first pending call each turn (a model\n // stuck in a loop re-issues the same call repeatedly, not alternating calls).\n const callHash = canonicalCallHash(call)\n if (callHash === lastCallHash) {\n consecutiveCount++\n } else {\n lastCallHash = callHash\n consecutiveCount = 1\n }\n if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'stuck-loop',\n })\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'stuck-loop' })\n return { finalText, toolResults, turns, stopReason: 'stuck-loop', cappedOut: true }\n }\n\n const callEventId = observer.toolCallBefore(toolTurn, turnEventId, callIndex, call)\n let outcome: ToolCallOutcome\n try {\n outcome = await opts.executeToolCall(call)\n } catch (err) {\n outcome = {\n ok: false,\n code: 'executor_error',\n message: err instanceof Error ? err.message : String(err),\n }\n }\n\n // Budget check after each tool call.\n if (opts.maxCostUsd !== undefined && opts.costOf !== undefined) {\n accumulatedCostUsd += opts.costOf(call, outcome)\n if (accumulatedCostUsd >= opts.maxCostUsd) {\n const label = labelFor(call)\n toolResults.push({ call, label, outcome })\n messages.push(toolResultMessage(call, render(label, outcome)))\n observer.toolCallAfter(toolTurn, callEventId, call, outcome)\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'budget',\n })\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'budget' })\n return { finalText, toolResults, turns, stopReason: 'budget', cappedOut: true }\n }\n }\n\n const label = labelFor(call)\n const rendered = render(label, outcome)\n toolResults.push({ call, label, outcome })\n outcomes.push({ call, label, outcome, rendered })\n // One role:'tool' message per result, keyed to its call by tool_call_id.\n messages.push(toolResultMessage(call, rendered))\n observer.toolCallAfter(toolTurn, callEventId, call, outcome)\n }\n observer.failureRecovery({\n toolTurn,\n messages,\n turnText,\n outcomes,\n })\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n toolResults: outcomes.map((item) => ({\n toolName: item.call.toolName,\n toolCallId: item.call.toolCallId,\n ok: item.outcome.ok,\n })),\n failedToolCalls: outcomes.filter((item) => !item.outcome.ok).length,\n })\n }\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'completed' })\n return { finalText, toolResults, turns, stopReason: 'completed', cappedOut: false }\n}\n\n// ── Streaming variant (SSE chat runtimes + per-event telemetry) ────────────\n\nexport type StreamToolLoopYield<Raw> =\n | { kind: 'event'; event: Raw }\n | {\n kind: 'tool_result'\n toolName: string\n toolCallId?: string\n label: string\n outcome: ToolCallOutcome\n }\n | { kind: 'capped'; pending: number; stopReason: Exclude<ToolLoopStopReason, 'completed'> }\n\nexport interface StreamToolLoopOptions<Raw> {\n systemPrompt: string\n userMessage: string\n priorMessages?: ToolLoopMessage[]\n streamTurn: (messages: ToolLoopMessage[]) => AsyncIterable<Raw>\n extractText: (event: Raw) => string\n extractToolCall: (event: Raw) => ToolLoopCall | null\n isExecutableTool: (toolName: string) => boolean\n executeToolCall: (call: ToolLoopCall) => Promise<ToolCallOutcome>\n /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow. */\n maxToolTurns?: number\n /** Wall-clock deadline in ms since epoch (Date.now()-based). */\n deadlineMs?: number\n /** Maximum total cost in USD. Requires `costOf` to meter each tool call. */\n maxCostUsd?: number\n /** Return the USD cost of one outcome. Required for `maxCostUsd` to work. */\n costOf?: (call: ToolLoopCall, outcome: ToolCallOutcome) => number\n renderResult?: (label: string, outcome: ToolCallOutcome) => string\n labelFor?: (call: ToolLoopCall) => string\n runId?: string\n scenarioId?: string\n hooks?: RuntimeHooks\n}\n\n/** Streaming bounded tool loop: yields each raw turn event (the caller maps +\n * telemetries + re-emits it) and each executed `tool_result`; emits one\n * `capped` if it stops for any non-completed reason with calls still pending. */\nexport async function* streamToolLoop<Raw>(\n opts: StreamToolLoopOptions<Raw>,\n): AsyncGenerator<StreamToolLoopYield<Raw>, void, unknown> {\n const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS\n const render = opts.renderResult ?? defaultRender\n const labelFor = opts.labelFor ?? ((c: ToolLoopCall) => c.toolName)\n const runId = opts.runId ?? `agent-run-${randomSuffix()}`\n const messages: ToolLoopMessage[] = [\n { role: 'system', content: opts.systemPrompt },\n ...(opts.priorMessages ?? []),\n { role: 'user', content: opts.userMessage },\n ]\n const observer = createToolLoopObserver(opts.hooks, runId, opts.scenarioId)\n let accumulatedCostUsd = 0\n let lastCallHash: string | null = null\n let consecutiveCount = 0\n\n observer.loopBefore(backstop, messages.length)\n\n for (let toolTurn = 0; ; toolTurn++) {\n // Wall-clock deadline check before every new turn.\n if (opts.deadlineMs !== undefined && Date.now() >= opts.deadlineMs) {\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'deadline' })\n yield { kind: 'capped', pending: 0, stopReason: 'deadline' }\n return\n }\n\n let turnText = ''\n const pending: ToolLoopCall[] = []\n const turnEventId = observer.turnBefore(toolTurn, messages.length)\n for await (const event of opts.streamTurn([...messages])) {\n yield { kind: 'event', event }\n turnText += opts.extractText(event)\n const call = opts.extractToolCall(event)\n if (call && opts.isExecutableTool(call.toolName)) pending.push(call)\n }\n if (pending.length === 0) {\n observer.turnAfter(toolTurn, turnEventId, { pendingToolCalls: 0 })\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'completed' })\n return\n }\n\n // Runaway backstop.\n if (toolTurn >= backstop) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'backstop',\n })\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'backstop' })\n yield { kind: 'capped', pending: pending.length, stopReason: 'backstop' }\n return\n }\n\n // The assistant turn that emitted the calls, carrying its tool_calls array,\n // so a strict model reads its own tool use back in OpenAI shape.\n messages.push(assistantToolCallMessage(turnText, pending))\n const outcomes: ExecutedToolCall[] = []\n for (const [callIndex, call] of pending.entries()) {\n // Stuck-loop detection.\n const callHash = canonicalCallHash(call)\n if (callHash === lastCallHash) {\n consecutiveCount++\n } else {\n lastCallHash = callHash\n consecutiveCount = 1\n }\n if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'stuck-loop',\n })\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'stuck-loop' })\n yield { kind: 'capped', pending: pending.length, stopReason: 'stuck-loop' }\n return\n }\n\n const callEventId = observer.toolCallBefore(toolTurn, turnEventId, callIndex, call)\n let outcome: ToolCallOutcome\n try {\n outcome = await opts.executeToolCall(call)\n } catch (err) {\n outcome = {\n ok: false,\n code: 'executor_error',\n message: err instanceof Error ? err.message : String(err),\n }\n }\n\n // Budget check after each tool call.\n if (opts.maxCostUsd !== undefined && opts.costOf !== undefined) {\n accumulatedCostUsd += opts.costOf(call, outcome)\n if (accumulatedCostUsd >= opts.maxCostUsd) {\n const label = labelFor(call)\n yield {\n kind: 'tool_result',\n toolName: call.toolName,\n toolCallId: call.toolCallId,\n label,\n outcome,\n }\n messages.push(toolResultMessage(call, render(label, outcome)))\n observer.toolCallAfter(toolTurn, callEventId, call, outcome)\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'budget',\n })\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'budget' })\n yield { kind: 'capped', pending: pending.length, stopReason: 'budget' }\n return\n }\n }\n\n const label = labelFor(call)\n yield {\n kind: 'tool_result',\n toolName: call.toolName,\n toolCallId: call.toolCallId,\n label,\n outcome,\n }\n const rendered = render(label, outcome)\n outcomes.push({ call, label, outcome, rendered })\n // One role:'tool' message per result, keyed to its call by tool_call_id.\n messages.push(toolResultMessage(call, rendered))\n observer.toolCallAfter(toolTurn, callEventId, call, outcome)\n }\n observer.failureRecovery({\n toolTurn,\n messages,\n turnText,\n outcomes,\n })\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n toolResults: outcomes.map((item) => ({\n toolName: item.call.toolName,\n toolCallId: item.call.toolCallId,\n ok: item.outcome.ok,\n })),\n failedToolCalls: outcomes.filter((item) => !item.outcome.ok).length,\n })\n }\n}\n\ninterface ExecutedToolCall {\n call: ToolLoopCall\n label: string\n outcome: ToolCallOutcome\n rendered: string\n}\n\ninterface NotifyToolFailureRecoveryOptions {\n hooks?: RuntimeHooks\n runId: string\n scenarioId?: string\n stepIndex: number\n messages: ToolLoopMessage[]\n turnText: string\n outcomes: ExecutedToolCall[]\n}\n\ninterface NotifyToolLoopEventOptions {\n hooks?: RuntimeHooks\n runId: string\n scenarioId?: string\n target: 'agent.run' | 'agent.turn' | 'agent.tool_call'\n phase: 'before' | 'after' | 'error' | 'event'\n id?: string\n stepIndex?: number\n parentId?: string\n payload?: Record<string, unknown>\n metadata?: Record<string, unknown>\n}\n\ninterface ToolLoopObserver {\n loopBefore(maxToolTurns: number, messageCount: number): void\n loopAfter(payload: Record<string, unknown>): void\n turnBefore(toolTurn: number, messageCount: number): string\n turnAfter(toolTurn: number, turnEventId: string, payload: Record<string, unknown>): void\n toolCallBefore(\n toolTurn: number,\n turnEventId: string,\n callIndex: number,\n call: ToolLoopCall,\n ): string\n toolCallAfter(\n toolTurn: number,\n callEventId: string,\n call: ToolLoopCall,\n outcome: ToolCallOutcome,\n ): void\n failureRecovery(options: {\n toolTurn: number\n messages: ToolLoopMessage[]\n turnText: string\n outcomes: ExecutedToolCall[]\n }): void\n}\n\nfunction createToolLoopObserver(\n hooks: RuntimeHooks | undefined,\n runId: string,\n scenarioId: string | undefined,\n): ToolLoopObserver {\n const loopEventId = `${runId}:agent.run`\n return {\n loopBefore: (maxToolTurns, messageCount) => {\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.run',\n phase: 'before',\n id: `${loopEventId}:before`,\n payload: { maxToolTurns, messageCount },\n })\n },\n loopAfter: (payload) => {\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.run',\n phase: 'after',\n id: `${loopEventId}:after`,\n payload,\n })\n },\n turnBefore: (toolTurn, messageCount) => {\n const turnEventId = `${loopEventId}:${toolTurn}`\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.turn',\n phase: 'before',\n id: turnEventId,\n stepIndex: toolTurn,\n parentId: loopEventId,\n payload: { messageCount },\n })\n return turnEventId\n },\n turnAfter: (toolTurn, turnEventId, payload) => {\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.turn',\n phase: 'after',\n id: `${turnEventId}:after`,\n stepIndex: toolTurn,\n parentId: turnEventId,\n payload,\n })\n },\n toolCallBefore: (toolTurn, turnEventId, callIndex, call) => {\n const callEventId = `${turnEventId}:tool-call:${callIndex}`\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.tool_call',\n phase: 'before',\n id: callEventId,\n stepIndex: toolTurn,\n parentId: turnEventId,\n payload: toolCallPayload(call),\n })\n return callEventId\n },\n toolCallAfter: (toolTurn, callEventId, call, outcome) => {\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.tool_call',\n phase: 'after',\n id: `${callEventId}:after`,\n stepIndex: toolTurn,\n parentId: callEventId,\n payload: { ...toolCallPayload(call), outcome: outcomePayload(outcome) },\n })\n },\n failureRecovery: (options) => {\n notifyToolFailureRecovery({\n hooks,\n runId,\n scenarioId,\n stepIndex: options.toolTurn,\n messages: options.messages,\n turnText: options.turnText,\n outcomes: options.outcomes,\n })\n },\n }\n}\n\nfunction notifyToolLoopEvent(options: NotifyToolLoopEventOptions): void {\n notifyRuntimeHookEvent(options.hooks, {\n id: options.id ?? `${options.runId}:${options.target}:${options.phase}`,\n runId: options.runId,\n scenarioId: options.scenarioId,\n target: options.target,\n phase: options.phase,\n timestamp: Date.now(),\n stepIndex: options.stepIndex,\n parentId: options.parentId,\n payload: options.payload,\n metadata: { producer: 'tool-loop', ...options.metadata },\n })\n}\n\nfunction notifyToolFailureRecovery(options: NotifyToolFailureRecoveryOptions): void {\n const failed = options.outcomes.filter((item) => !item.outcome.ok)\n if (failed.length === 0) return\n\n const evidence: RuntimeDecisionEvidenceRef[] = []\n for (const item of failed) {\n const id = item.call.toolCallId ?? `${options.stepIndex}:${item.label}`\n evidence.push({\n source: 'tool_call',\n id,\n detail: `${item.call.toolName} ${stringifySafe(item.call.args, 2_000)}`,\n metadata: { toolName: item.call.toolName, label: item.label },\n })\n evidence.push({\n source: 'tool_result',\n id: `${id}:result`,\n detail: item.rendered,\n metadata: failureMetadata(item.outcome),\n })\n }\n\n notifyRuntimeDecisionPoint(options.hooks, {\n id: `${options.runId}:agent.turn:${options.stepIndex}:failure-recovery`,\n runId: options.runId,\n scenarioId: options.scenarioId,\n stepIndex: options.stepIndex,\n kind: 'retry',\n candidateActions: [...FAILURE_RECOVERY_ACTIONS],\n context: renderDecisionContext(options.messages, options.turnText, options.outcomes),\n evidence,\n metadata: {\n target: 'failure-recovery',\n source: 'agent.turn',\n failedToolCount: failed.length,\n toolNames: failed.map((item) => item.call.toolName),\n },\n })\n}\n\nfunction toolCallPayload(call: ToolLoopCall): Record<string, unknown> {\n return {\n toolName: call.toolName,\n toolCallId: call.toolCallId,\n argsPreview: stringifySafe(call.args, 2_000),\n }\n}\n\nfunction outcomePayload(outcome: ToolCallOutcome): Record<string, unknown> {\n if (!outcome.ok) {\n return {\n ok: false,\n code: outcome.code,\n message: trimText(outcome.message, 2_000),\n status: outcome.status,\n }\n }\n return {\n ok: true,\n resultPreview: stringifySafe(outcome.result, 2_000),\n }\n}\n\nfunction failureMetadata(outcome: ToolCallOutcome): Record<string, unknown> | undefined {\n if (outcome.ok) return undefined\n return {\n code: outcome.code,\n message: outcome.message,\n status: outcome.status,\n }\n}\n\nfunction renderDecisionContext(\n messages: ToolLoopMessage[],\n turnText: string,\n outcomes: ExecutedToolCall[],\n): string {\n const recent = messages.slice(-6).map((message) => `[${message.role}]\\n${message.content ?? ''}`)\n const assistant = turnText.trim() ? [`[assistant]\\n${turnText}`] : []\n const toolResults = [`[tool results]\\n${outcomes.map((item) => item.rendered).join('\\n')}`]\n return trimText(\n [...recent, ...assistant, ...toolResults].join('\\n\\n'),\n DEFAULT_DECISION_CONTEXT_CHARS,\n )\n}\n\n/** Canonical identifier for a tool call used by stuck-loop detection.\n * Keys are sorted so `{b:1,a:2}` and `{a:2,b:1}` produce the same hash. */\nfunction canonicalCallHash(call: ToolLoopCall): string {\n const sortedArgs = Object.fromEntries(\n Object.entries(call.args).sort(([a], [b]) => a.localeCompare(b)),\n )\n return `${call.toolName}:${JSON.stringify(sortedArgs)}`\n}\n\nfunction stringifySafe(value: unknown, max: number): string {\n let text: string\n try {\n text = JSON.stringify(value) ?? String(value)\n } catch {\n text = String(value)\n }\n return trimText(text, max)\n}\n\nfunction trimText(text: string, max: number): string {\n if (text.length <= max) return text\n return `${text.slice(0, max)}…`\n}\n\nfunction randomSuffix(len = 8): string {\n return Math.random()\n .toString(36)\n .slice(2, 2 + len)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tool-loop.js","names":[],"sources":["../src/tool-loop.ts"],"sourcesContent":["/**\n * Bounded turn-level tool-dispatch loop.\n *\n * `runAgentTaskStream` runs ONE model turn; `runAgentRounds` orchestrates DELEGATED\n * multi-agent topologies (refine / fanout-vote). Neither is the everyday\n * interactive shape: a chat turn where the model may emit tool calls, each is\n * executed, the results are folded back, and the turn re-runs until the model\n * stops (or a turn cap). Every agent app hand-rolls that loop — this is it,\n * as a reusable primitive.\n *\n * Substrate-neutral by design: the caller supplies `streamTurn` (wrapping\n * whatever backend / `runAgentTaskStream` it uses) and `executeToolCall`\n * (routing to its executors). This module owns the LOOP; the caller owns the\n * model and the executors. `Raw` (streaming variant) is the caller's own\n * event type. The only imported contract is the runtime hook type: hooks are\n * execution-scoped observers, not part of the agent profile.\n */\n\nimport type { RuntimeDecisionEvidenceRef, RuntimeHooks } from './runtime-hooks'\nimport { notifyRuntimeDecisionPoint, notifyRuntimeHookEvent } from './runtime-hooks'\n\nexport interface ToolLoopCall {\n toolCallId?: string\n toolName: string\n args: Record<string, unknown>\n}\n\n/** Outcome of one tool dispatch — structurally compatible with a hub/integration\n * tool-outcome union, so callers can fold either through the loop. */\nexport type ToolCallOutcome =\n | { ok: true; result: unknown }\n | { ok: false; code: string; message: string; status?: number }\n\n/** Runaway-backstop: stops an infinite tool loop where cost is unmetered. Set\n * far above any legitimate workflow — this is a watchdog, not a policy cap.\n * Legitimate per-call budgets come from `maxCostUsd` + `costOf`. */\nconst RUNAWAY_BACKSTOP_TURNS = 200\nconst DEFAULT_DECISION_CONTEXT_CHARS = 12_000\nconst FAILURE_RECOVERY_ACTIONS = ['retry', 'verify', 'continue', 'stop']\n/** Consecutive identical calls (same tool + canonical-JSON args) that trigger\n * stuck-loop detection. The window resets on any different call. */\nconst STUCK_LOOP_THRESHOLD = 3\n\n/** One OpenAI-shaped tool-call entry carried on an assistant message. */\nexport interface ToolLoopAssistantToolCall {\n id: string\n type: 'function'\n function: { name: string; arguments: string }\n}\n\n/**\n * A message in the running conversation the loop sends to `streamTurn`.\n *\n * The base `{ role, content }` covers `system` / `user` / plain `assistant`\n * turns. Two optional fields carry the OpenAI function-calling contract so a\n * strict model (Claude, and any OpenAI-compatible provider that validates tool\n * history) reads its own tool use back instead of re-issuing the same call:\n *\n * - an assistant turn that emitted tool calls carries `tool_calls`, and its\n * `content` is `null` when the turn was tool-only;\n * - each tool result is its own `{ role: 'tool', tool_call_id, content }`\n * message keyed to the call that produced it.\n *\n * Widening is additive: a `streamTurn` that reads only `role` + `content` still\n * works; one that forwards the whole message to an OpenAI-compatible endpoint\n * now sends correct tool history.\n */\nexport type ToolLoopMessage = {\n role: string\n content: string | null\n tool_calls?: ToolLoopAssistantToolCall[]\n tool_call_id?: string\n}\n\n/** A tool-call id is required to key a `role: 'tool'` result back to its call.\n * When the model omitted one, derive a stable id from the tool name so the\n * assistant `tool_calls` entry and its `tool` result still match. */\nfunction toolCallId(call: ToolLoopCall): string {\n return call.toolCallId ?? `call_${call.toolName}`\n}\n\n/** The assistant turn that emitted `pending`, in OpenAI shape: text content\n * (null when the turn was tool-only) plus its `tool_calls` array. */\nfunction assistantToolCallMessage(turnText: string, pending: ToolLoopCall[]): ToolLoopMessage {\n return {\n role: 'assistant',\n content: turnText.trim() || null,\n tool_calls: pending.map((call) => ({\n id: toolCallId(call),\n type: 'function',\n function: { name: call.toolName, arguments: JSON.stringify(call.args) },\n })),\n }\n}\n\n/** One `role: 'tool'` result message keyed to its call by `tool_call_id`. */\nfunction toolResultMessage(call: ToolLoopCall, content: string): ToolLoopMessage {\n return { role: 'tool', tool_call_id: toolCallId(call), content }\n}\n\n/**\n * One progress report from a running tool, shaped like an MCP `notifications/progress`\n * payload: a free-text `message`, a monotonic `progress` count with an optional\n * `total`, and any structured `data` the caller's UI understands.\n */\nexport interface ToolProgressUpdate {\n message?: string\n progress?: number\n total?: number\n data?: unknown\n}\n\n/** What the loop hands a running tool besides its call. */\nexport interface ToolCallContext {\n /**\n * Report progress while the call runs. The streaming loop yields each update as a\n * `tool_progress` step, in order, before the call's `tool_result`; updates sent after\n * the call settles are dropped.\n */\n progress: (update: ToolProgressUpdate) => void\n}\n\n/** Executes one tool call. Implementations that take only the call stay valid. */\nexport type ToolCallExecutor = (\n call: ToolLoopCall,\n context: ToolCallContext,\n) => Promise<ToolCallOutcome>\n\nfunction defaultRender(label: string, outcome: ToolCallOutcome): string {\n if (outcome.ok) return `- ${label} → ok: ${JSON.stringify(outcome.result)}`\n return `- ${label} → failed (${outcome.code}): ${outcome.message}`\n}\n\n// ── Awaitable variant (drain-only callers, tests) ──────────────────────────\n\nexport type ToolLoopEvent =\n | { type: 'text'; text: string }\n | { type: 'tool_call'; call: ToolLoopCall }\n | { type: 'other'; event: unknown }\n\n/** Why the loop stopped. `completed` = model finished naturally; `stuck-loop` =\n * ≥3 consecutive identical tool calls (same tool + args); `backstop` = hit the\n * runaway-backstop cap (200 by default); `deadline` = wall-clock deadlineMs\n * exceeded; `budget` = maxCostUsd exhausted. Non-`completed` stops are infra /\n * resource outcomes — eval scoring must distinguish them from capability failure. */\nexport type ToolLoopStopReason = 'completed' | 'stuck-loop' | 'backstop' | 'deadline' | 'budget'\n\nexport interface ToolLoopResult {\n finalText: string\n toolResults: Array<{ call: ToolLoopCall; label: string; outcome: ToolCallOutcome }>\n turns: number\n stopReason: ToolLoopStopReason\n /** @deprecated Use `stopReason !== 'completed'` instead. */\n cappedOut: boolean\n}\n\nexport interface RunToolLoopOptions {\n systemPrompt: string\n userMessage: string\n priorMessages?: ToolLoopMessage[]\n streamTurn: (messages: ToolLoopMessage[]) => AsyncIterable<ToolLoopEvent>\n executeToolCall: ToolCallExecutor\n /** Receives each progress update a running tool reports, in order. */\n onToolProgress?: (call: ToolLoopCall, update: ToolProgressUpdate) => void\n isExecutableTool: (toolName: string) => boolean\n /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow.\n * For per-workflow limits, use `maxCostUsd` or `deadlineMs` instead. */\n maxToolTurns?: number\n /** Wall-clock deadline in ms since epoch (Date.now()-based). When exceeded the\n * loop stops with stopReason `deadline`. */\n deadlineMs?: number\n /** Maximum total cost in USD. Requires `costOf` to meter each tool call. */\n maxCostUsd?: number\n /** Return the USD cost of one outcome. Required for `maxCostUsd` to work. */\n costOf?: (call: ToolLoopCall, outcome: ToolCallOutcome) => number\n renderResult?: (label: string, outcome: ToolCallOutcome) => string\n labelFor?: (call: ToolLoopCall) => string\n runId?: string\n scenarioId?: string\n hooks?: RuntimeHooks\n}\n\n/** Run the bounded tool loop and return the final text + every executed tool\n * outcome. Awaitable — callers needing to stream events to a UI use\n * {@link streamToolLoop}. */\nexport async function runToolLoop(opts: RunToolLoopOptions): Promise<ToolLoopResult> {\n const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS\n const render = opts.renderResult ?? defaultRender\n const labelFor = opts.labelFor ?? ((c: ToolLoopCall) => c.toolName)\n const runId = opts.runId ?? `agent-run-${randomSuffix()}`\n const messages: ToolLoopMessage[] = [\n { role: 'system', content: opts.systemPrompt },\n ...(opts.priorMessages ?? []),\n { role: 'user', content: opts.userMessage },\n ]\n const observer = createToolLoopObserver(opts.hooks, runId, opts.scenarioId)\n const toolResults: ToolLoopResult['toolResults'] = []\n let finalText = ''\n let turns = 0\n let accumulatedCostUsd = 0\n // Stuck-loop detection: track the last canonical-JSON call signature and how\n // many consecutive times we've seen it.\n let lastCallHash: string | null = null\n let consecutiveCount = 0\n\n observer.loopBefore(backstop, messages.length)\n\n for (let toolTurn = 0; ; toolTurn++) {\n turns++\n\n // Wall-clock deadline check — before every new turn.\n if (opts.deadlineMs !== undefined && Date.now() >= opts.deadlineMs) {\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'deadline' })\n return { finalText, toolResults, turns, stopReason: 'deadline', cappedOut: true }\n }\n\n let turnText = ''\n const pending: ToolLoopCall[] = []\n const turnEventId = observer.turnBefore(toolTurn, messages.length)\n for await (const ev of opts.streamTurn([...messages])) {\n if (ev.type === 'text') {\n turnText += ev.text\n finalText += ev.text\n } else if (ev.type === 'tool_call' && opts.isExecutableTool(ev.call.toolName)) {\n pending.push(ev.call)\n }\n }\n if (pending.length === 0) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: 0,\n finalTextChars: finalText.length,\n })\n break\n }\n\n // Runaway backstop — the model keeps emitting calls past the safety ceiling.\n if (toolTurn >= backstop) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'backstop',\n })\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'backstop' })\n return { finalText, toolResults, turns, stopReason: 'backstop', cappedOut: true }\n }\n\n // The assistant turn that emitted the calls, carrying its tool_calls array,\n // so a strict model reads its own tool use back in OpenAI shape.\n messages.push(assistantToolCallMessage(turnText, pending))\n const outcomes: ExecutedToolCall[] = []\n for (const [callIndex, call] of pending.entries()) {\n // Stuck-loop detection: hash the first pending call each turn (a model\n // stuck in a loop re-issues the same call repeatedly, not alternating calls).\n const callHash = canonicalCallHash(call)\n if (callHash === lastCallHash) {\n consecutiveCount++\n } else {\n lastCallHash = callHash\n consecutiveCount = 1\n }\n if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'stuck-loop',\n })\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'stuck-loop' })\n return { finalText, toolResults, turns, stopReason: 'stuck-loop', cappedOut: true }\n }\n\n const callEventId = observer.toolCallBefore(toolTurn, turnEventId, callIndex, call)\n let settled = false\n const outcome = await executeGuarded(opts.executeToolCall, call, (update) => {\n if (!settled) opts.onToolProgress?.(call, update)\n })\n settled = true\n\n // Budget check after each tool call.\n if (opts.maxCostUsd !== undefined && opts.costOf !== undefined) {\n accumulatedCostUsd += opts.costOf(call, outcome)\n if (accumulatedCostUsd >= opts.maxCostUsd) {\n const label = labelFor(call)\n toolResults.push({ call, label, outcome })\n messages.push(toolResultMessage(call, render(label, outcome)))\n observer.toolCallAfter(toolTurn, callEventId, call, outcome)\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'budget',\n })\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'budget' })\n return { finalText, toolResults, turns, stopReason: 'budget', cappedOut: true }\n }\n }\n\n const label = labelFor(call)\n const rendered = render(label, outcome)\n toolResults.push({ call, label, outcome })\n outcomes.push({ call, label, outcome, rendered })\n // One role:'tool' message per result, keyed to its call by tool_call_id.\n messages.push(toolResultMessage(call, rendered))\n observer.toolCallAfter(toolTurn, callEventId, call, outcome)\n }\n observer.failureRecovery({\n toolTurn,\n messages,\n turnText,\n outcomes,\n })\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n toolResults: outcomes.map((item) => ({\n toolName: item.call.toolName,\n toolCallId: item.call.toolCallId,\n ok: item.outcome.ok,\n })),\n failedToolCalls: outcomes.filter((item) => !item.outcome.ok).length,\n })\n }\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'completed' })\n return { finalText, toolResults, turns, stopReason: 'completed', cappedOut: false }\n}\n\n// ── Streaming variant (SSE chat runtimes + per-event telemetry) ────────────\n\nexport type StreamToolLoopYield<Raw> =\n | { kind: 'event'; event: Raw }\n | {\n kind: 'tool_progress'\n toolName: string\n toolCallId?: string\n update: ToolProgressUpdate\n }\n | {\n kind: 'tool_result'\n toolName: string\n toolCallId?: string\n label: string\n outcome: ToolCallOutcome\n }\n | { kind: 'capped'; pending: number; stopReason: Exclude<ToolLoopStopReason, 'completed'> }\n\nexport interface StreamToolLoopOptions<Raw> {\n systemPrompt: string\n userMessage: string\n priorMessages?: ToolLoopMessage[]\n streamTurn: (messages: ToolLoopMessage[]) => AsyncIterable<Raw>\n extractText: (event: Raw) => string\n extractToolCall: (event: Raw) => ToolLoopCall | null\n isExecutableTool: (toolName: string) => boolean\n executeToolCall: ToolCallExecutor\n /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow. */\n maxToolTurns?: number\n /** Wall-clock deadline in ms since epoch (Date.now()-based). */\n deadlineMs?: number\n /** Maximum total cost in USD. Requires `costOf` to meter each tool call. */\n maxCostUsd?: number\n /** Return the USD cost of one outcome. Required for `maxCostUsd` to work. */\n costOf?: (call: ToolLoopCall, outcome: ToolCallOutcome) => number\n renderResult?: (label: string, outcome: ToolCallOutcome) => string\n labelFor?: (call: ToolLoopCall) => string\n runId?: string\n scenarioId?: string\n hooks?: RuntimeHooks\n}\n\n/** Streaming bounded tool loop: yields each raw turn event (the caller maps +\n * telemetries + re-emits it), each `tool_progress` a running tool reports, and\n * each executed `tool_result`; emits one `capped` if it stops for any\n * non-completed reason with calls still pending. */\nexport async function* streamToolLoop<Raw>(\n opts: StreamToolLoopOptions<Raw>,\n): AsyncGenerator<StreamToolLoopYield<Raw>, void, unknown> {\n const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS\n const render = opts.renderResult ?? defaultRender\n const labelFor = opts.labelFor ?? ((c: ToolLoopCall) => c.toolName)\n const runId = opts.runId ?? `agent-run-${randomSuffix()}`\n const messages: ToolLoopMessage[] = [\n { role: 'system', content: opts.systemPrompt },\n ...(opts.priorMessages ?? []),\n { role: 'user', content: opts.userMessage },\n ]\n const observer = createToolLoopObserver(opts.hooks, runId, opts.scenarioId)\n let accumulatedCostUsd = 0\n let lastCallHash: string | null = null\n let consecutiveCount = 0\n\n observer.loopBefore(backstop, messages.length)\n\n for (let toolTurn = 0; ; toolTurn++) {\n // Wall-clock deadline check before every new turn.\n if (opts.deadlineMs !== undefined && Date.now() >= opts.deadlineMs) {\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'deadline' })\n yield { kind: 'capped', pending: 0, stopReason: 'deadline' }\n return\n }\n\n let turnText = ''\n const pending: ToolLoopCall[] = []\n const turnEventId = observer.turnBefore(toolTurn, messages.length)\n for await (const event of opts.streamTurn([...messages])) {\n yield { kind: 'event', event }\n turnText += opts.extractText(event)\n const call = opts.extractToolCall(event)\n if (call && opts.isExecutableTool(call.toolName)) pending.push(call)\n }\n if (pending.length === 0) {\n observer.turnAfter(toolTurn, turnEventId, { pendingToolCalls: 0 })\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'completed' })\n return\n }\n\n // Runaway backstop.\n if (toolTurn >= backstop) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'backstop',\n })\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'backstop' })\n yield { kind: 'capped', pending: pending.length, stopReason: 'backstop' }\n return\n }\n\n // The assistant turn that emitted the calls, carrying its tool_calls array,\n // so a strict model reads its own tool use back in OpenAI shape.\n messages.push(assistantToolCallMessage(turnText, pending))\n const outcomes: ExecutedToolCall[] = []\n for (const [callIndex, call] of pending.entries()) {\n // Stuck-loop detection.\n const callHash = canonicalCallHash(call)\n if (callHash === lastCallHash) {\n consecutiveCount++\n } else {\n lastCallHash = callHash\n consecutiveCount = 1\n }\n if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'stuck-loop',\n })\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'stuck-loop' })\n yield { kind: 'capped', pending: pending.length, stopReason: 'stuck-loop' }\n return\n }\n\n const callEventId = observer.toolCallBefore(toolTurn, turnEventId, callIndex, call)\n // Progress arrives while the call is in flight; yield it as it lands, in order,\n // and stop accepting it once the call settles so it never trails the result.\n const progress: ToolProgressUpdate[] = []\n let wake: (() => void) | undefined\n let settled = false\n const running = executeGuarded(opts.executeToolCall, call, (update) => {\n if (settled) return\n progress.push(update)\n wake?.()\n }).then((result) => {\n settled = true\n wake?.()\n return result\n })\n while (!settled || progress.length > 0) {\n const update = progress.shift()\n if (update !== undefined) {\n yield {\n kind: 'tool_progress',\n toolName: call.toolName,\n toolCallId: call.toolCallId,\n update,\n }\n continue\n }\n await new Promise<void>((resolve) => {\n wake = resolve\n })\n wake = undefined\n }\n const outcome = await running\n\n // Budget check after each tool call.\n if (opts.maxCostUsd !== undefined && opts.costOf !== undefined) {\n accumulatedCostUsd += opts.costOf(call, outcome)\n if (accumulatedCostUsd >= opts.maxCostUsd) {\n const label = labelFor(call)\n yield {\n kind: 'tool_result',\n toolName: call.toolName,\n toolCallId: call.toolCallId,\n label,\n outcome,\n }\n messages.push(toolResultMessage(call, render(label, outcome)))\n observer.toolCallAfter(toolTurn, callEventId, call, outcome)\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'budget',\n })\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'budget' })\n yield { kind: 'capped', pending: pending.length, stopReason: 'budget' }\n return\n }\n }\n\n const label = labelFor(call)\n yield {\n kind: 'tool_result',\n toolName: call.toolName,\n toolCallId: call.toolCallId,\n label,\n outcome,\n }\n const rendered = render(label, outcome)\n outcomes.push({ call, label, outcome, rendered })\n // One role:'tool' message per result, keyed to its call by tool_call_id.\n messages.push(toolResultMessage(call, rendered))\n observer.toolCallAfter(toolTurn, callEventId, call, outcome)\n }\n observer.failureRecovery({\n toolTurn,\n messages,\n turnText,\n outcomes,\n })\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n toolResults: outcomes.map((item) => ({\n toolName: item.call.toolName,\n toolCallId: item.call.toolCallId,\n ok: item.outcome.ok,\n })),\n failedToolCalls: outcomes.filter((item) => !item.outcome.ok).length,\n })\n }\n}\n\n/** Run one call; a thrown executor becomes an `executor_error` outcome. */\nasync function executeGuarded(\n execute: ToolCallExecutor,\n call: ToolLoopCall,\n progress: (update: ToolProgressUpdate) => void,\n): Promise<ToolCallOutcome> {\n try {\n return await execute(call, { progress })\n } catch (err) {\n return {\n ok: false,\n code: 'executor_error',\n message: err instanceof Error ? err.message : String(err),\n }\n }\n}\n\ninterface ExecutedToolCall {\n call: ToolLoopCall\n label: string\n outcome: ToolCallOutcome\n rendered: string\n}\n\ninterface NotifyToolFailureRecoveryOptions {\n hooks?: RuntimeHooks\n runId: string\n scenarioId?: string\n stepIndex: number\n messages: ToolLoopMessage[]\n turnText: string\n outcomes: ExecutedToolCall[]\n}\n\ninterface NotifyToolLoopEventOptions {\n hooks?: RuntimeHooks\n runId: string\n scenarioId?: string\n target: 'agent.run' | 'agent.turn' | 'agent.tool_call'\n phase: 'before' | 'after' | 'error' | 'event'\n id?: string\n stepIndex?: number\n parentId?: string\n payload?: Record<string, unknown>\n metadata?: Record<string, unknown>\n}\n\ninterface ToolLoopObserver {\n loopBefore(maxToolTurns: number, messageCount: number): void\n loopAfter(payload: Record<string, unknown>): void\n turnBefore(toolTurn: number, messageCount: number): string\n turnAfter(toolTurn: number, turnEventId: string, payload: Record<string, unknown>): void\n toolCallBefore(\n toolTurn: number,\n turnEventId: string,\n callIndex: number,\n call: ToolLoopCall,\n ): string\n toolCallAfter(\n toolTurn: number,\n callEventId: string,\n call: ToolLoopCall,\n outcome: ToolCallOutcome,\n ): void\n failureRecovery(options: {\n toolTurn: number\n messages: ToolLoopMessage[]\n turnText: string\n outcomes: ExecutedToolCall[]\n }): void\n}\n\nfunction createToolLoopObserver(\n hooks: RuntimeHooks | undefined,\n runId: string,\n scenarioId: string | undefined,\n): ToolLoopObserver {\n const loopEventId = `${runId}:agent.run`\n return {\n loopBefore: (maxToolTurns, messageCount) => {\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.run',\n phase: 'before',\n id: `${loopEventId}:before`,\n payload: { maxToolTurns, messageCount },\n })\n },\n loopAfter: (payload) => {\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.run',\n phase: 'after',\n id: `${loopEventId}:after`,\n payload,\n })\n },\n turnBefore: (toolTurn, messageCount) => {\n const turnEventId = `${loopEventId}:${toolTurn}`\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.turn',\n phase: 'before',\n id: turnEventId,\n stepIndex: toolTurn,\n parentId: loopEventId,\n payload: { messageCount },\n })\n return turnEventId\n },\n turnAfter: (toolTurn, turnEventId, payload) => {\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.turn',\n phase: 'after',\n id: `${turnEventId}:after`,\n stepIndex: toolTurn,\n parentId: turnEventId,\n payload,\n })\n },\n toolCallBefore: (toolTurn, turnEventId, callIndex, call) => {\n const callEventId = `${turnEventId}:tool-call:${callIndex}`\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.tool_call',\n phase: 'before',\n id: callEventId,\n stepIndex: toolTurn,\n parentId: turnEventId,\n payload: toolCallPayload(call),\n })\n return callEventId\n },\n toolCallAfter: (toolTurn, callEventId, call, outcome) => {\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.tool_call',\n phase: 'after',\n id: `${callEventId}:after`,\n stepIndex: toolTurn,\n parentId: callEventId,\n payload: { ...toolCallPayload(call), outcome: outcomePayload(outcome) },\n })\n },\n failureRecovery: (options) => {\n notifyToolFailureRecovery({\n hooks,\n runId,\n scenarioId,\n stepIndex: options.toolTurn,\n messages: options.messages,\n turnText: options.turnText,\n outcomes: options.outcomes,\n })\n },\n }\n}\n\nfunction notifyToolLoopEvent(options: NotifyToolLoopEventOptions): void {\n notifyRuntimeHookEvent(options.hooks, {\n id: options.id ?? `${options.runId}:${options.target}:${options.phase}`,\n runId: options.runId,\n scenarioId: options.scenarioId,\n target: options.target,\n phase: options.phase,\n timestamp: Date.now(),\n stepIndex: options.stepIndex,\n parentId: options.parentId,\n payload: options.payload,\n metadata: { producer: 'tool-loop', ...options.metadata },\n })\n}\n\nfunction notifyToolFailureRecovery(options: NotifyToolFailureRecoveryOptions): void {\n const failed = options.outcomes.filter((item) => !item.outcome.ok)\n if (failed.length === 0) return\n\n const evidence: RuntimeDecisionEvidenceRef[] = []\n for (const item of failed) {\n const id = item.call.toolCallId ?? `${options.stepIndex}:${item.label}`\n evidence.push({\n source: 'tool_call',\n id,\n detail: `${item.call.toolName} ${stringifySafe(item.call.args, 2_000)}`,\n metadata: { toolName: item.call.toolName, label: item.label },\n })\n evidence.push({\n source: 'tool_result',\n id: `${id}:result`,\n detail: item.rendered,\n metadata: failureMetadata(item.outcome),\n })\n }\n\n notifyRuntimeDecisionPoint(options.hooks, {\n id: `${options.runId}:agent.turn:${options.stepIndex}:failure-recovery`,\n runId: options.runId,\n scenarioId: options.scenarioId,\n stepIndex: options.stepIndex,\n kind: 'retry',\n candidateActions: [...FAILURE_RECOVERY_ACTIONS],\n context: renderDecisionContext(options.messages, options.turnText, options.outcomes),\n evidence,\n metadata: {\n target: 'failure-recovery',\n source: 'agent.turn',\n failedToolCount: failed.length,\n toolNames: failed.map((item) => item.call.toolName),\n },\n })\n}\n\nfunction toolCallPayload(call: ToolLoopCall): Record<string, unknown> {\n return {\n toolName: call.toolName,\n toolCallId: call.toolCallId,\n argsPreview: stringifySafe(call.args, 2_000),\n }\n}\n\nfunction outcomePayload(outcome: ToolCallOutcome): Record<string, unknown> {\n if (!outcome.ok) {\n return {\n ok: false,\n code: outcome.code,\n message: trimText(outcome.message, 2_000),\n status: outcome.status,\n }\n }\n return {\n ok: true,\n resultPreview: stringifySafe(outcome.result, 2_000),\n }\n}\n\nfunction failureMetadata(outcome: ToolCallOutcome): Record<string, unknown> | undefined {\n if (outcome.ok) return undefined\n return {\n code: outcome.code,\n message: outcome.message,\n status: outcome.status,\n }\n}\n\nfunction renderDecisionContext(\n messages: ToolLoopMessage[],\n turnText: string,\n outcomes: ExecutedToolCall[],\n): string {\n const recent = messages.slice(-6).map((message) => `[${message.role}]\\n${message.content ?? ''}`)\n const assistant = turnText.trim() ? [`[assistant]\\n${turnText}`] : []\n const toolResults = [`[tool results]\\n${outcomes.map((item) => item.rendered).join('\\n')}`]\n return trimText(\n [...recent, ...assistant, ...toolResults].join('\\n\\n'),\n DEFAULT_DECISION_CONTEXT_CHARS,\n )\n}\n\n/** Canonical identifier for a tool call used by stuck-loop detection.\n * Keys are sorted so `{b:1,a:2}` and `{a:2,b:1}` produce the same hash. */\nfunction canonicalCallHash(call: ToolLoopCall): string {\n const sortedArgs = Object.fromEntries(\n Object.entries(call.args).sort(([a], [b]) => a.localeCompare(b)),\n )\n return `${call.toolName}:${JSON.stringify(sortedArgs)}`\n}\n\nfunction stringifySafe(value: unknown, max: number): string {\n let text: string\n try {\n text = JSON.stringify(value) ?? String(value)\n } catch {\n text = String(value)\n }\n return trimText(text, max)\n}\n\nfunction trimText(text: string, max: number): string {\n if (text.length <= max) return text\n return `${text.slice(0, max)}…`\n}\n\nfunction randomSuffix(len = 8): string {\n return Math.random()\n .toString(36)\n .slice(2, 2 + len)\n}\n"],"mappings":"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"}
|
package/dist/tui/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _t as SupervisorCleanupReceipt, gt as ProvisionedSupervisor, ht as ProvisionSupervisorRequest, mt as ProvisionSupervisorConnection, vt as provisionSupervisor } from "../index-
|
|
1
|
+
import { _t as SupervisorCleanupReceipt, gt as ProvisionedSupervisor, ht as ProvisionSupervisorRequest, mt as ProvisionSupervisorConnection, vt as provisionSupervisor } from "../index-CUh40yUJ.js";
|
|
2
2
|
//#region src/tui/top-app.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* The interactive side of the supervisor-run TUI: a keypress/mouse loop over the frames
|
package/dist/tui/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as provisionSupervisor } from "../provision-supervisor-
|
|
1
|
+
import { t as provisionSupervisor } from "../provision-supervisor-BQLLrUtn.js";
|
|
2
2
|
import { a as renderTopFrameWithLayout, i as renderTopFrame, n as runTopApp, r as loadTopSnapshot, t as renderTopOnce } from "../top-app-DaiR4LXM.js";
|
|
3
3
|
export { loadTopSnapshot, provisionSupervisor, renderTopFrame, renderTopFrameWithLayout, renderTopOnce, runTopApp };
|