@tangle-network/agent-runtime 0.108.1 → 0.109.1

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 (56) hide show
  1. package/README.md +7 -6
  2. package/dist/{activation-DRpnplEm.js → activation-CM-L6TtL.js} +2 -2
  3. package/dist/{activation-DRpnplEm.js.map → activation-CM-L6TtL.js.map} +1 -1
  4. package/dist/agent.d.ts +2 -2
  5. package/dist/agent.js +2 -2
  6. package/dist/analyst-loop.d.ts +1 -1
  7. package/dist/{environment-provider-D0NXc4Qz.d.ts → environment-provider-CMqSAp-O.d.ts} +3 -2
  8. package/dist/environment-provider.d.ts +1 -1
  9. package/dist/{improvement-cycle-CRnDDdX0.js → improvement-cycle-9O4iVFOA.js} +3 -3
  10. package/dist/{improvement-cycle-CRnDDdX0.js.map → improvement-cycle-9O4iVFOA.js.map} +1 -1
  11. package/dist/{index-DQp3BPeC.d.ts → index-Bur6uUF4.d.ts} +6 -152
  12. package/dist/{index-cdkwHruJ.d.ts → index-Cn5TtHUG.d.ts} +3 -3
  13. package/dist/{index-BZbJWqoZ.d.ts → index-bXiMVrUv.d.ts} +8 -7
  14. package/dist/index.d.ts +8 -6
  15. package/dist/index.js +11 -635
  16. package/dist/index.js.map +1 -1
  17. package/dist/intelligence.d.ts +2 -2
  18. package/dist/intelligence.js +2 -2
  19. package/dist/{loops.d.ts → kernel.d.ts} +3 -3
  20. package/dist/{loops.js → kernel.js} +4 -4
  21. package/dist/{knowledge-rdrpPIXs.js → knowledge-1a38bcUT.js} +3 -3
  22. package/dist/{knowledge-rdrpPIXs.js.map → knowledge-1a38bcUT.js.map} +1 -1
  23. package/dist/knowledge.d.ts +1 -1
  24. package/dist/knowledge.js +1 -1
  25. package/dist/{loop-runner-bin-9-JlGqN7.d.ts → loop-runner-bin-CSzrWlgE.d.ts} +3 -3
  26. package/dist/{loop-runner-bin-B1V-XS5A.js → loop-runner-bin-V4EN9aNT.js} +3 -3
  27. package/dist/{loop-runner-bin-B1V-XS5A.js.map → loop-runner-bin-V4EN9aNT.js.map} +1 -1
  28. package/dist/loop-runner-bin.d.ts +1 -1
  29. package/dist/loop-runner-bin.js +1 -1
  30. package/dist/mcp/bin.js +1 -1
  31. package/dist/mcp/index.d.ts +2 -2
  32. package/dist/mcp/index.js +4 -4
  33. package/dist/{openai-tools-BjQq2TTL.js → openai-tools-DT9FH2_t.js} +2 -2
  34. package/dist/{openai-tools-BjQq2TTL.js.map → openai-tools-DT9FH2_t.js.map} +1 -1
  35. package/dist/primeintellect/index.d.ts +1 -1
  36. package/dist/profiles.d.ts +1 -1
  37. package/dist/profiles.js.map +1 -1
  38. package/dist/{runtime-CDekUROm.js → runtime-BVMyqgct.js} +8 -7
  39. package/dist/{runtime-CDekUROm.js.map → runtime-BVMyqgct.js.map} +1 -1
  40. package/dist/runtime-hooks-C7iJOWm3.js +99 -0
  41. package/dist/runtime-hooks-C7iJOWm3.js.map +1 -0
  42. package/dist/runtime-hooks-sbRpjStq.d.ts +88 -0
  43. package/dist/{structural-rollout-BC81Otmc.js → structural-rollout-ASQLr4-v.js} +2 -2
  44. package/dist/{structural-rollout-BC81Otmc.js.map → structural-rollout-ASQLr4-v.js.map} +1 -1
  45. package/dist/{supervise-JfKPwIlO.js → supervise-DyPmmhJ6.js} +6 -5
  46. package/dist/supervise-DyPmmhJ6.js.map +1 -0
  47. package/dist/{supervisor-B2LzaWRb.js → supervisor-sTJC9psT.js} +3 -98
  48. package/dist/supervisor-sTJC9psT.js.map +1 -0
  49. package/dist/testing.js +8 -8
  50. package/dist/tool-loop.d.ts +149 -0
  51. package/dist/tool-loop.js +629 -0
  52. package/dist/tool-loop.js.map +1 -0
  53. package/dist/{types-BevOjfTY.d.ts → types-DnNGJ5Gz.d.ts} +3 -88
  54. package/package.json +21 -10
  55. package/dist/supervise-JfKPwIlO.js.map +0 -1
  56. package/dist/supervisor-B2LzaWRb.js.map +0 -1
@@ -0,0 +1,629 @@
1
+ import { i as notifyRuntimeHookEvent, r as notifyRuntimeDecisionPoint } from "./runtime-hooks-C7iJOWm3.js";
2
+ //#region src/tool-loop.ts
3
+ /** Runaway-backstop: stops an infinite tool loop where cost is unmetered. Set
4
+ * far above any legitimate workflow — this is a watchdog, not a policy cap.
5
+ * Legitimate per-call budgets come from `maxCostUsd` + `costOf`. */
6
+ const RUNAWAY_BACKSTOP_TURNS = 200;
7
+ const DEFAULT_DECISION_CONTEXT_CHARS = 12e3;
8
+ const FAILURE_RECOVERY_ACTIONS = [
9
+ "retry",
10
+ "verify",
11
+ "continue",
12
+ "stop"
13
+ ];
14
+ /** Consecutive identical calls (same tool + canonical-JSON args) that trigger
15
+ * stuck-loop detection. The window resets on any different call. */
16
+ const STUCK_LOOP_THRESHOLD = 3;
17
+ /** A tool-call id is required to key a `role: 'tool'` result back to its call.
18
+ * When the model omitted one, derive a stable id from the tool name so the
19
+ * assistant `tool_calls` entry and its `tool` result still match. */
20
+ function toolCallId(call) {
21
+ return call.toolCallId ?? `call_${call.toolName}`;
22
+ }
23
+ /** The assistant turn that emitted `pending`, in OpenAI shape: text content
24
+ * (null when the turn was tool-only) plus its `tool_calls` array. */
25
+ function assistantToolCallMessage(turnText, pending) {
26
+ return {
27
+ role: "assistant",
28
+ content: turnText.trim() || null,
29
+ tool_calls: pending.map((call) => ({
30
+ id: toolCallId(call),
31
+ type: "function",
32
+ function: {
33
+ name: call.toolName,
34
+ arguments: JSON.stringify(call.args)
35
+ }
36
+ }))
37
+ };
38
+ }
39
+ /** One `role: 'tool'` result message keyed to its call by `tool_call_id`. */
40
+ function toolResultMessage(call, content) {
41
+ return {
42
+ role: "tool",
43
+ tool_call_id: toolCallId(call),
44
+ content
45
+ };
46
+ }
47
+ function defaultRender(label, outcome) {
48
+ if (outcome.ok) return `- ${label} → ok: ${JSON.stringify(outcome.result)}`;
49
+ return `- ${label} → failed (${outcome.code}): ${outcome.message}`;
50
+ }
51
+ /** Run the bounded tool loop and return the final text + every executed tool
52
+ * outcome. Awaitable — callers needing to stream events to a UI use
53
+ * {@link streamToolLoop}. */
54
+ async function runToolLoop(opts) {
55
+ const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS;
56
+ const render = opts.renderResult ?? defaultRender;
57
+ const labelFor = opts.labelFor ?? ((c) => c.toolName);
58
+ const runId = opts.runId ?? `agent-run-${randomSuffix()}`;
59
+ const messages = [
60
+ {
61
+ role: "system",
62
+ content: opts.systemPrompt
63
+ },
64
+ ...opts.priorMessages ?? [],
65
+ {
66
+ role: "user",
67
+ content: opts.userMessage
68
+ }
69
+ ];
70
+ const observer = createToolLoopObserver(opts.hooks, runId, opts.scenarioId);
71
+ const toolResults = [];
72
+ let finalText = "";
73
+ let turns = 0;
74
+ let accumulatedCostUsd = 0;
75
+ let lastCallHash = null;
76
+ let consecutiveCount = 0;
77
+ observer.loopBefore(backstop, messages.length);
78
+ for (let toolTurn = 0;; toolTurn++) {
79
+ turns++;
80
+ if (opts.deadlineMs !== void 0 && Date.now() >= opts.deadlineMs) {
81
+ observer.loopAfter({
82
+ turns,
83
+ toolResults: toolResults.length,
84
+ stopReason: "deadline"
85
+ });
86
+ return {
87
+ finalText,
88
+ toolResults,
89
+ turns,
90
+ stopReason: "deadline",
91
+ cappedOut: true
92
+ };
93
+ }
94
+ let turnText = "";
95
+ const pending = [];
96
+ const turnEventId = observer.turnBefore(toolTurn, messages.length);
97
+ for await (const ev of opts.streamTurn([...messages])) if (ev.type === "text") {
98
+ turnText += ev.text;
99
+ finalText += ev.text;
100
+ } else if (ev.type === "tool_call" && opts.isExecutableTool(ev.call.toolName)) pending.push(ev.call);
101
+ if (pending.length === 0) {
102
+ observer.turnAfter(toolTurn, turnEventId, {
103
+ pendingToolCalls: 0,
104
+ finalTextChars: finalText.length
105
+ });
106
+ break;
107
+ }
108
+ if (toolTurn >= backstop) {
109
+ observer.turnAfter(toolTurn, turnEventId, {
110
+ pendingToolCalls: pending.length,
111
+ stopReason: "backstop"
112
+ });
113
+ observer.loopAfter({
114
+ turns,
115
+ toolResults: toolResults.length,
116
+ stopReason: "backstop"
117
+ });
118
+ return {
119
+ finalText,
120
+ toolResults,
121
+ turns,
122
+ stopReason: "backstop",
123
+ cappedOut: true
124
+ };
125
+ }
126
+ messages.push(assistantToolCallMessage(turnText, pending));
127
+ const outcomes = [];
128
+ for (const [callIndex, call] of pending.entries()) {
129
+ const callHash = canonicalCallHash(call);
130
+ if (callHash === lastCallHash) consecutiveCount++;
131
+ else {
132
+ lastCallHash = callHash;
133
+ consecutiveCount = 1;
134
+ }
135
+ if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {
136
+ observer.turnAfter(toolTurn, turnEventId, {
137
+ pendingToolCalls: pending.length,
138
+ stopReason: "stuck-loop"
139
+ });
140
+ observer.loopAfter({
141
+ turns,
142
+ toolResults: toolResults.length,
143
+ stopReason: "stuck-loop"
144
+ });
145
+ return {
146
+ finalText,
147
+ toolResults,
148
+ turns,
149
+ stopReason: "stuck-loop",
150
+ cappedOut: true
151
+ };
152
+ }
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
+ }
164
+ if (opts.maxCostUsd !== void 0 && opts.costOf !== void 0) {
165
+ accumulatedCostUsd += opts.costOf(call, outcome);
166
+ if (accumulatedCostUsd >= opts.maxCostUsd) {
167
+ const label = labelFor(call);
168
+ toolResults.push({
169
+ call,
170
+ label,
171
+ outcome
172
+ });
173
+ messages.push(toolResultMessage(call, render(label, outcome)));
174
+ observer.toolCallAfter(toolTurn, callEventId, call, outcome);
175
+ observer.turnAfter(toolTurn, turnEventId, {
176
+ pendingToolCalls: pending.length,
177
+ stopReason: "budget"
178
+ });
179
+ observer.loopAfter({
180
+ turns,
181
+ toolResults: toolResults.length,
182
+ stopReason: "budget"
183
+ });
184
+ return {
185
+ finalText,
186
+ toolResults,
187
+ turns,
188
+ stopReason: "budget",
189
+ cappedOut: true
190
+ };
191
+ }
192
+ }
193
+ const label = labelFor(call);
194
+ const rendered = render(label, outcome);
195
+ toolResults.push({
196
+ call,
197
+ label,
198
+ outcome
199
+ });
200
+ outcomes.push({
201
+ call,
202
+ label,
203
+ outcome,
204
+ rendered
205
+ });
206
+ messages.push(toolResultMessage(call, rendered));
207
+ observer.toolCallAfter(toolTurn, callEventId, call, outcome);
208
+ }
209
+ observer.failureRecovery({
210
+ toolTurn,
211
+ messages,
212
+ turnText,
213
+ outcomes
214
+ });
215
+ observer.turnAfter(toolTurn, turnEventId, {
216
+ pendingToolCalls: pending.length,
217
+ toolResults: outcomes.map((item) => ({
218
+ toolName: item.call.toolName,
219
+ toolCallId: item.call.toolCallId,
220
+ ok: item.outcome.ok
221
+ })),
222
+ failedToolCalls: outcomes.filter((item) => !item.outcome.ok).length
223
+ });
224
+ }
225
+ observer.loopAfter({
226
+ turns,
227
+ toolResults: toolResults.length,
228
+ stopReason: "completed"
229
+ });
230
+ return {
231
+ finalText,
232
+ toolResults,
233
+ turns,
234
+ stopReason: "completed",
235
+ cappedOut: false
236
+ };
237
+ }
238
+ /** 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. */
241
+ async function* streamToolLoop(opts) {
242
+ const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS;
243
+ const render = opts.renderResult ?? defaultRender;
244
+ const labelFor = opts.labelFor ?? ((c) => c.toolName);
245
+ const runId = opts.runId ?? `agent-run-${randomSuffix()}`;
246
+ const messages = [
247
+ {
248
+ role: "system",
249
+ content: opts.systemPrompt
250
+ },
251
+ ...opts.priorMessages ?? [],
252
+ {
253
+ role: "user",
254
+ content: opts.userMessage
255
+ }
256
+ ];
257
+ const observer = createToolLoopObserver(opts.hooks, runId, opts.scenarioId);
258
+ let accumulatedCostUsd = 0;
259
+ let lastCallHash = null;
260
+ let consecutiveCount = 0;
261
+ observer.loopBefore(backstop, messages.length);
262
+ for (let toolTurn = 0;; toolTurn++) {
263
+ if (opts.deadlineMs !== void 0 && Date.now() >= opts.deadlineMs) {
264
+ observer.loopAfter({
265
+ turns: toolTurn + 1,
266
+ stopReason: "deadline"
267
+ });
268
+ yield {
269
+ kind: "capped",
270
+ pending: 0,
271
+ stopReason: "deadline"
272
+ };
273
+ return;
274
+ }
275
+ let turnText = "";
276
+ const pending = [];
277
+ const turnEventId = observer.turnBefore(toolTurn, messages.length);
278
+ for await (const event of opts.streamTurn([...messages])) {
279
+ yield {
280
+ kind: "event",
281
+ event
282
+ };
283
+ turnText += opts.extractText(event);
284
+ const call = opts.extractToolCall(event);
285
+ if (call && opts.isExecutableTool(call.toolName)) pending.push(call);
286
+ }
287
+ if (pending.length === 0) {
288
+ observer.turnAfter(toolTurn, turnEventId, { pendingToolCalls: 0 });
289
+ observer.loopAfter({
290
+ turns: toolTurn + 1,
291
+ stopReason: "completed"
292
+ });
293
+ return;
294
+ }
295
+ if (toolTurn >= backstop) {
296
+ observer.turnAfter(toolTurn, turnEventId, {
297
+ pendingToolCalls: pending.length,
298
+ stopReason: "backstop"
299
+ });
300
+ observer.loopAfter({
301
+ turns: toolTurn + 1,
302
+ stopReason: "backstop"
303
+ });
304
+ yield {
305
+ kind: "capped",
306
+ pending: pending.length,
307
+ stopReason: "backstop"
308
+ };
309
+ return;
310
+ }
311
+ messages.push(assistantToolCallMessage(turnText, pending));
312
+ const outcomes = [];
313
+ for (const [callIndex, call] of pending.entries()) {
314
+ const callHash = canonicalCallHash(call);
315
+ if (callHash === lastCallHash) consecutiveCount++;
316
+ else {
317
+ lastCallHash = callHash;
318
+ consecutiveCount = 1;
319
+ }
320
+ if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {
321
+ observer.turnAfter(toolTurn, turnEventId, {
322
+ pendingToolCalls: pending.length,
323
+ stopReason: "stuck-loop"
324
+ });
325
+ observer.loopAfter({
326
+ turns: toolTurn + 1,
327
+ stopReason: "stuck-loop"
328
+ });
329
+ yield {
330
+ kind: "capped",
331
+ pending: pending.length,
332
+ stopReason: "stuck-loop"
333
+ };
334
+ return;
335
+ }
336
+ 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
+ };
346
+ }
347
+ if (opts.maxCostUsd !== void 0 && opts.costOf !== void 0) {
348
+ accumulatedCostUsd += opts.costOf(call, outcome);
349
+ if (accumulatedCostUsd >= opts.maxCostUsd) {
350
+ const label = labelFor(call);
351
+ yield {
352
+ kind: "tool_result",
353
+ toolName: call.toolName,
354
+ toolCallId: call.toolCallId,
355
+ label,
356
+ outcome
357
+ };
358
+ messages.push(toolResultMessage(call, render(label, outcome)));
359
+ observer.toolCallAfter(toolTurn, callEventId, call, outcome);
360
+ observer.turnAfter(toolTurn, turnEventId, {
361
+ pendingToolCalls: pending.length,
362
+ stopReason: "budget"
363
+ });
364
+ observer.loopAfter({
365
+ turns: toolTurn + 1,
366
+ stopReason: "budget"
367
+ });
368
+ yield {
369
+ kind: "capped",
370
+ pending: pending.length,
371
+ stopReason: "budget"
372
+ };
373
+ return;
374
+ }
375
+ }
376
+ const label = labelFor(call);
377
+ yield {
378
+ kind: "tool_result",
379
+ toolName: call.toolName,
380
+ toolCallId: call.toolCallId,
381
+ label,
382
+ outcome
383
+ };
384
+ const rendered = render(label, outcome);
385
+ outcomes.push({
386
+ call,
387
+ label,
388
+ outcome,
389
+ rendered
390
+ });
391
+ messages.push(toolResultMessage(call, rendered));
392
+ observer.toolCallAfter(toolTurn, callEventId, call, outcome);
393
+ }
394
+ observer.failureRecovery({
395
+ toolTurn,
396
+ messages,
397
+ turnText,
398
+ outcomes
399
+ });
400
+ observer.turnAfter(toolTurn, turnEventId, {
401
+ pendingToolCalls: pending.length,
402
+ toolResults: outcomes.map((item) => ({
403
+ toolName: item.call.toolName,
404
+ toolCallId: item.call.toolCallId,
405
+ ok: item.outcome.ok
406
+ })),
407
+ failedToolCalls: outcomes.filter((item) => !item.outcome.ok).length
408
+ });
409
+ }
410
+ }
411
+ function createToolLoopObserver(hooks, runId, scenarioId) {
412
+ const loopEventId = `${runId}:agent.run`;
413
+ return {
414
+ loopBefore: (maxToolTurns, messageCount) => {
415
+ notifyToolLoopEvent({
416
+ hooks,
417
+ runId,
418
+ scenarioId,
419
+ target: "agent.run",
420
+ phase: "before",
421
+ id: `${loopEventId}:before`,
422
+ payload: {
423
+ maxToolTurns,
424
+ messageCount
425
+ }
426
+ });
427
+ },
428
+ loopAfter: (payload) => {
429
+ notifyToolLoopEvent({
430
+ hooks,
431
+ runId,
432
+ scenarioId,
433
+ target: "agent.run",
434
+ phase: "after",
435
+ id: `${loopEventId}:after`,
436
+ payload
437
+ });
438
+ },
439
+ turnBefore: (toolTurn, messageCount) => {
440
+ const turnEventId = `${loopEventId}:${toolTurn}`;
441
+ notifyToolLoopEvent({
442
+ hooks,
443
+ runId,
444
+ scenarioId,
445
+ target: "agent.turn",
446
+ phase: "before",
447
+ id: turnEventId,
448
+ stepIndex: toolTurn,
449
+ parentId: loopEventId,
450
+ payload: { messageCount }
451
+ });
452
+ return turnEventId;
453
+ },
454
+ turnAfter: (toolTurn, turnEventId, payload) => {
455
+ notifyToolLoopEvent({
456
+ hooks,
457
+ runId,
458
+ scenarioId,
459
+ target: "agent.turn",
460
+ phase: "after",
461
+ id: `${turnEventId}:after`,
462
+ stepIndex: toolTurn,
463
+ parentId: turnEventId,
464
+ payload
465
+ });
466
+ },
467
+ toolCallBefore: (toolTurn, turnEventId, callIndex, call) => {
468
+ const callEventId = `${turnEventId}:tool-call:${callIndex}`;
469
+ notifyToolLoopEvent({
470
+ hooks,
471
+ runId,
472
+ scenarioId,
473
+ target: "agent.tool_call",
474
+ phase: "before",
475
+ id: callEventId,
476
+ stepIndex: toolTurn,
477
+ parentId: turnEventId,
478
+ payload: toolCallPayload(call)
479
+ });
480
+ return callEventId;
481
+ },
482
+ toolCallAfter: (toolTurn, callEventId, call, outcome) => {
483
+ notifyToolLoopEvent({
484
+ hooks,
485
+ runId,
486
+ scenarioId,
487
+ target: "agent.tool_call",
488
+ phase: "after",
489
+ id: `${callEventId}:after`,
490
+ stepIndex: toolTurn,
491
+ parentId: callEventId,
492
+ payload: {
493
+ ...toolCallPayload(call),
494
+ outcome: outcomePayload(outcome)
495
+ }
496
+ });
497
+ },
498
+ failureRecovery: (options) => {
499
+ notifyToolFailureRecovery({
500
+ hooks,
501
+ runId,
502
+ scenarioId,
503
+ stepIndex: options.toolTurn,
504
+ messages: options.messages,
505
+ turnText: options.turnText,
506
+ outcomes: options.outcomes
507
+ });
508
+ }
509
+ };
510
+ }
511
+ function notifyToolLoopEvent(options) {
512
+ notifyRuntimeHookEvent(options.hooks, {
513
+ id: options.id ?? `${options.runId}:${options.target}:${options.phase}`,
514
+ runId: options.runId,
515
+ scenarioId: options.scenarioId,
516
+ target: options.target,
517
+ phase: options.phase,
518
+ timestamp: Date.now(),
519
+ stepIndex: options.stepIndex,
520
+ parentId: options.parentId,
521
+ payload: options.payload,
522
+ metadata: {
523
+ producer: "tool-loop",
524
+ ...options.metadata
525
+ }
526
+ });
527
+ }
528
+ function notifyToolFailureRecovery(options) {
529
+ const failed = options.outcomes.filter((item) => !item.outcome.ok);
530
+ if (failed.length === 0) return;
531
+ const evidence = [];
532
+ for (const item of failed) {
533
+ const id = item.call.toolCallId ?? `${options.stepIndex}:${item.label}`;
534
+ evidence.push({
535
+ source: "tool_call",
536
+ id,
537
+ detail: `${item.call.toolName} ${stringifySafe(item.call.args, 2e3)}`,
538
+ metadata: {
539
+ toolName: item.call.toolName,
540
+ label: item.label
541
+ }
542
+ });
543
+ evidence.push({
544
+ source: "tool_result",
545
+ id: `${id}:result`,
546
+ detail: item.rendered,
547
+ metadata: failureMetadata(item.outcome)
548
+ });
549
+ }
550
+ notifyRuntimeDecisionPoint(options.hooks, {
551
+ id: `${options.runId}:agent.turn:${options.stepIndex}:failure-recovery`,
552
+ runId: options.runId,
553
+ scenarioId: options.scenarioId,
554
+ stepIndex: options.stepIndex,
555
+ kind: "retry",
556
+ candidateActions: [...FAILURE_RECOVERY_ACTIONS],
557
+ context: renderDecisionContext(options.messages, options.turnText, options.outcomes),
558
+ evidence,
559
+ metadata: {
560
+ target: "failure-recovery",
561
+ source: "agent.turn",
562
+ failedToolCount: failed.length,
563
+ toolNames: failed.map((item) => item.call.toolName)
564
+ }
565
+ });
566
+ }
567
+ function toolCallPayload(call) {
568
+ return {
569
+ toolName: call.toolName,
570
+ toolCallId: call.toolCallId,
571
+ argsPreview: stringifySafe(call.args, 2e3)
572
+ };
573
+ }
574
+ function outcomePayload(outcome) {
575
+ if (!outcome.ok) return {
576
+ ok: false,
577
+ code: outcome.code,
578
+ message: trimText(outcome.message, 2e3),
579
+ status: outcome.status
580
+ };
581
+ return {
582
+ ok: true,
583
+ resultPreview: stringifySafe(outcome.result, 2e3)
584
+ };
585
+ }
586
+ function failureMetadata(outcome) {
587
+ if (outcome.ok) return void 0;
588
+ return {
589
+ code: outcome.code,
590
+ message: outcome.message,
591
+ status: outcome.status
592
+ };
593
+ }
594
+ function renderDecisionContext(messages, turnText, outcomes) {
595
+ const recent = messages.slice(-6).map((message) => `[${message.role}]\n${message.content ?? ""}`);
596
+ const assistant = turnText.trim() ? [`[assistant]\n${turnText}`] : [];
597
+ const toolResults = [`[tool results]\n${outcomes.map((item) => item.rendered).join("\n")}`];
598
+ return trimText([
599
+ ...recent,
600
+ ...assistant,
601
+ ...toolResults
602
+ ].join("\n\n"), DEFAULT_DECISION_CONTEXT_CHARS);
603
+ }
604
+ /** Canonical identifier for a tool call used by stuck-loop detection.
605
+ * Keys are sorted so `{b:1,a:2}` and `{a:2,b:1}` produce the same hash. */
606
+ function canonicalCallHash(call) {
607
+ const sortedArgs = Object.fromEntries(Object.entries(call.args).sort(([a], [b]) => a.localeCompare(b)));
608
+ return `${call.toolName}:${JSON.stringify(sortedArgs)}`;
609
+ }
610
+ function stringifySafe(value, max) {
611
+ let text;
612
+ try {
613
+ text = JSON.stringify(value) ?? String(value);
614
+ } catch {
615
+ text = String(value);
616
+ }
617
+ return trimText(text, max);
618
+ }
619
+ function trimText(text, max) {
620
+ if (text.length <= max) return text;
621
+ return `${text.slice(0, max)}…`;
622
+ }
623
+ function randomSuffix(len = 8) {
624
+ return Math.random().toString(36).slice(2, 2 + len);
625
+ }
626
+ //#endregion
627
+ export { runToolLoop, streamToolLoop };
628
+
629
+ //# sourceMappingURL=tool-loop.js.map