@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.
Files changed (82) hide show
  1. package/README.md +6 -0
  2. package/dist/{activation-BpMnOlw5.d.ts → activation-BFDmjaKP.d.ts} +2 -2
  3. package/dist/{activation-Ci20OAsG.js → activation-CvdE2HIU.js} +3 -3
  4. package/dist/{activation-Ci20OAsG.js.map → activation-CvdE2HIU.js.map} +1 -1
  5. package/dist/agent.d.ts +2 -1
  6. package/dist/agent.js +2 -2
  7. package/dist/{bundle-BK3XBY6q.js → bundle-RgHpAFDY.js} +2 -2
  8. package/dist/{bundle-BK3XBY6q.js.map → bundle-RgHpAFDY.js.map} +1 -1
  9. package/dist/candidate-execution/index.js +5 -5
  10. package/dist/{coordination-driver-CyJF37L8.js → coordination-driver-DGhN8GO9.js} +3 -3
  11. package/dist/{coordination-driver-CyJF37L8.js.map → coordination-driver-DGhN8GO9.js.map} +1 -1
  12. package/dist/{delegate-CTezpiM2.js → delegate-B5pmSbQa.js} +2 -2
  13. package/dist/{delegate-CTezpiM2.js.map → delegate-B5pmSbQa.js.map} +1 -1
  14. package/dist/durable.d.ts +1 -1
  15. package/dist/durable.js +4 -4
  16. package/dist/{graph-DGDrO_ZW.js → graph-G0j1ysqe.js} +4 -4
  17. package/dist/{graph-DGDrO_ZW.js.map → graph-G0j1ysqe.js.map} +1 -1
  18. package/dist/{improvement-cycle-DReg-Qi4.js → improvement-cycle-D7yR6GC2.js} +6 -6
  19. package/dist/{improvement-cycle-DReg-Qi4.js.map → improvement-cycle-D7yR6GC2.js.map} +1 -1
  20. package/dist/{index-B89bMKhD.d.ts → index-CUh40yUJ.d.ts} +13 -90
  21. package/dist/index.d.ts +4 -4
  22. package/dist/index.js +9 -9
  23. package/dist/integrations-DopfCC2a.d.ts +190 -0
  24. package/dist/integrations-dFi-ALk7.js +132 -0
  25. package/dist/integrations-dFi-ALk7.js.map +1 -0
  26. package/dist/intelligence.d.ts +2 -2
  27. package/dist/intelligence.js +7 -7
  28. package/dist/kernel.d.ts +2 -2
  29. package/dist/kernel.js +9 -9
  30. package/dist/{loop-runner-bin-eoycPwY3.js → loop-runner-bin-CFVX4oi0.js} +3 -3
  31. package/dist/{loop-runner-bin-eoycPwY3.js.map → loop-runner-bin-CFVX4oi0.js.map} +1 -1
  32. package/dist/{loop-runner-bin-BM1in-RT.d.ts → loop-runner-bin-DQKLUt0l.d.ts} +2 -2
  33. package/dist/loop-runner-bin.d.ts +1 -1
  34. package/dist/loop-runner-bin.js +1 -1
  35. package/dist/mcp/bin.js +3 -3
  36. package/dist/mcp/index.d.ts +74 -33
  37. package/dist/mcp/index.js +129 -6
  38. package/dist/mcp/index.js.map +1 -1
  39. package/dist/{openai-tools-xPO6dzf4.d.ts → openai-tools-u3gWBi3x.d.ts} +2 -2
  40. package/dist/placement.d.ts +132 -0
  41. package/dist/placement.js +319 -0
  42. package/dist/placement.js.map +1 -0
  43. package/dist/platform.d.ts +1 -188
  44. package/dist/platform.js +1 -129
  45. package/dist/platform.js.map +1 -1
  46. package/dist/{prepare-8A1CAWG8.js → prepare-CD9kR0gS.js} +3 -3
  47. package/dist/{prepare-8A1CAWG8.js.map → prepare-CD9kR0gS.js.map} +1 -1
  48. package/dist/{profile-BhQShsM1.js → profile-CHXYmpGd.js} +2 -2
  49. package/dist/{profile-BhQShsM1.js.map → profile-CHXYmpGd.js.map} +1 -1
  50. package/dist/profile-materialization-Cca615eu.d.ts +90 -0
  51. package/dist/profiles.d.ts +1 -1
  52. package/dist/{protected-model-port-ug52OmvB.js → protected-model-port-B0pHd65J.js} +4 -4
  53. package/dist/{protected-model-port-ug52OmvB.js.map → protected-model-port-B0pHd65J.js.map} +1 -1
  54. package/dist/{provision-supervisor-B6D3XRbL.js → provision-supervisor-BQLLrUtn.js} +4 -4
  55. package/dist/{provision-supervisor-B6D3XRbL.js.map → provision-supervisor-BQLLrUtn.js.map} +1 -1
  56. package/dist/{runtime-NlIVxRXu.js → runtime-B9BOFyEp.js} +10 -9
  57. package/dist/runtime-B9BOFyEp.js.map +1 -0
  58. package/dist/{search-executor-DFs-79tK.js → search-executor-B5ZVFsDV.js} +3 -3
  59. package/dist/{search-executor-DFs-79tK.js.map → search-executor-B5ZVFsDV.js.map} +1 -1
  60. package/dist/{server-B8ubWXpW.js → server-TwpH9aik.js} +4 -4
  61. package/dist/{server-B8ubWXpW.js.map → server-TwpH9aik.js.map} +1 -1
  62. package/dist/{supervise-BE9ta8bl.js → supervise-CU6AuxWY.js} +119 -29
  63. package/dist/supervise-CU6AuxWY.js.map +1 -0
  64. package/dist/{supervisor-DmSuKksw.js → supervisor-CbFyf48U.js} +3 -3
  65. package/dist/{supervisor-DmSuKksw.js.map → supervisor-CbFyf48U.js.map} +1 -1
  66. package/dist/testing.d.ts +1 -1
  67. package/dist/testing.js +14 -14
  68. package/dist/tool-loop.d.ts +35 -5
  69. package/dist/tool-loop.js +48 -21
  70. package/dist/tool-loop.js.map +1 -1
  71. package/dist/tui/index.d.ts +1 -1
  72. package/dist/tui/index.js +1 -1
  73. package/dist/workspace-store/index.d.ts +206 -0
  74. package/dist/workspace-store/index.js +855 -0
  75. package/dist/workspace-store/index.js.map +1 -0
  76. package/dist/{workspace-streams-BEvSX97P.js → workspace-streams-DkijayqX.js} +16 -3
  77. package/dist/workspace-streams-DkijayqX.js.map +1 -0
  78. package/package.json +14 -4
  79. package/skills/profile-authoring/SKILL.md +114 -171
  80. package/dist/runtime-NlIVxRXu.js.map +0 -1
  81. package/dist/supervise-BE9ta8bl.js.map +0 -1
  82. package/dist/workspace-streams-BEvSX97P.js.map +0 -1
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Av as ToolLoopChat, Bt as RunGraphTestOptions, Dc as superviseWithTestBrain, Tc as SuperviseTestOptions, Wt as runGraphWithTestBrain, kv as ToolLoopCallContext, mn as driverAgent, pn as DriverAgentOptions, ul as SupervisorAgentTestDeps, yl as supervisorAgentWithTestBrain } from "./index-B89bMKhD.js";
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 { F as immutableCandidateValue, j as canonicalCandidateDigest$1 } from "./workspace-streams-BEvSX97P.js";
2
- import { f as verifyAgentImprovementProposal } from "./improvement-cycle-DReg-Qi4.js";
3
- import { i as applyExactAgentProfileDiff, p as parseExactAgentProfile } from "./profile-BhQShsM1.js";
4
- import { r as driverAgent } from "./coordination-driver-CyJF37L8.js";
5
- import { a as superviseWithTestBrain, m as supervisorAgentWithTestBrain } from "./supervise-BE9ta8bl.js";
6
- import { i as runGraphWithTestBrain } from "./graph-DGDrO_ZW.js";
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:f77c9f7e1c95f3cad39a717c47da992493ad34c256cf4b2aa5d6a8782059fa41",
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.314.1"
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:f9029c8c0dd834df8e56abe92bd409934dee014a884044370397573a5227b37c",
4694
- "runId": "agent-runtime-0.314.1-proposal-fixture",
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.314.1-proposal-fixture"
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:e15090d9639d84beb1e61ab2da43265824aa5b5f0eae7b80ffc63541868b7835",
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.314.1"
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:cf91e9fcf31c43d6e030f86287c82fa33371df4832fa8efadb0f9bc7e999eeaa",
6468
+ "recordDigest": "sha256:3b5bd31ff4221182c11d47ab2ddcfc34ed49c05b3790eead4226cc89a5a0972a",
6469
6469
  "runId": "profile-improvement-1",
6470
6470
  "schema": "agent-profile-improvement-experiment"
6471
6471
  }
@@ -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: (call: ToolLoopCall) => Promise<ToolCallOutcome>;
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: (call: ToolLoopCall) => Promise<ToolCallOutcome>;
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) and each executed `tool_result`; emits one
145
- * `capped` if it stops for any non-completed reason with calls still pending. */
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 outcome;
155
- try {
156
- outcome = await opts.executeToolCall(call);
157
- } catch (err) {
158
- outcome = {
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) and each executed `tool_result`; emits one
240
- * `capped` if it stops for any non-completed reason with calls still pending. */
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
- let outcome;
338
- try {
339
- outcome = await opts.executeToolCall(call);
340
- } catch (err) {
341
- outcome = {
342
- ok: false,
343
- code: "executor_error",
344
- message: err instanceof Error ? err.message : String(err)
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 {
@@ -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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"}
@@ -1,4 +1,4 @@
1
- import { _t as SupervisorCleanupReceipt, gt as ProvisionedSupervisor, ht as ProvisionSupervisorRequest, mt as ProvisionSupervisorConnection, vt as provisionSupervisor } from "../index-B89bMKhD.js";
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-B6D3XRbL.js";
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 };