@burtson-labs/agent-core 1.6.13

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 (195) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +88 -0
  3. package/dist/index.d.ts +16 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +52 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/mcp/activation.d.ts +60 -0
  8. package/dist/mcp/activation.d.ts.map +1 -0
  9. package/dist/mcp/activation.js +139 -0
  10. package/dist/mcp/activation.js.map +1 -0
  11. package/dist/mcp/clientPool.d.ts +202 -0
  12. package/dist/mcp/clientPool.d.ts.map +1 -0
  13. package/dist/mcp/clientPool.js +469 -0
  14. package/dist/mcp/clientPool.js.map +1 -0
  15. package/dist/mcp/index.d.ts +18 -0
  16. package/dist/mcp/index.d.ts.map +1 -0
  17. package/dist/mcp/index.js +28 -0
  18. package/dist/mcp/index.js.map +1 -0
  19. package/dist/mcp/server.d.ts +43 -0
  20. package/dist/mcp/server.d.ts.map +1 -0
  21. package/dist/mcp/server.js +130 -0
  22. package/dist/mcp/server.js.map +1 -0
  23. package/dist/mcp/toolAdapter.d.ts +57 -0
  24. package/dist/mcp/toolAdapter.d.ts.map +1 -0
  25. package/dist/mcp/toolAdapter.js +223 -0
  26. package/dist/mcp/toolAdapter.js.map +1 -0
  27. package/dist/mcp/types.d.ts +122 -0
  28. package/dist/mcp/types.d.ts.map +1 -0
  29. package/dist/mcp/types.js +15 -0
  30. package/dist/mcp/types.js.map +1 -0
  31. package/dist/providers/deterministic-provider.d.ts +21 -0
  32. package/dist/providers/deterministic-provider.d.ts.map +1 -0
  33. package/dist/providers/deterministic-provider.js +80 -0
  34. package/dist/providers/deterministic-provider.js.map +1 -0
  35. package/dist/providers/provider-client.d.ts +12 -0
  36. package/dist/providers/provider-client.d.ts.map +1 -0
  37. package/dist/providers/provider-client.js +11 -0
  38. package/dist/providers/provider-client.js.map +1 -0
  39. package/dist/runtime/AgentRuntime.d.ts +67 -0
  40. package/dist/runtime/AgentRuntime.d.ts.map +1 -0
  41. package/dist/runtime/AgentRuntime.js +382 -0
  42. package/dist/runtime/AgentRuntime.js.map +1 -0
  43. package/dist/security/secretPatterns.d.ts +76 -0
  44. package/dist/security/secretPatterns.d.ts.map +1 -0
  45. package/dist/security/secretPatterns.js +290 -0
  46. package/dist/security/secretPatterns.js.map +1 -0
  47. package/dist/tools/ask-user-tool.d.ts +19 -0
  48. package/dist/tools/ask-user-tool.d.ts.map +1 -0
  49. package/dist/tools/ask-user-tool.js +148 -0
  50. package/dist/tools/ask-user-tool.js.map +1 -0
  51. package/dist/tools/compactMessages.d.ts +52 -0
  52. package/dist/tools/compactMessages.d.ts.map +1 -0
  53. package/dist/tools/compactMessages.js +158 -0
  54. package/dist/tools/compactMessages.js.map +1 -0
  55. package/dist/tools/core-tools.d.ts +29 -0
  56. package/dist/tools/core-tools.d.ts.map +1 -0
  57. package/dist/tools/core-tools.js +2214 -0
  58. package/dist/tools/core-tools.js.map +1 -0
  59. package/dist/tools/git-tools.d.ts +32 -0
  60. package/dist/tools/git-tools.d.ts.map +1 -0
  61. package/dist/tools/git-tools.js +330 -0
  62. package/dist/tools/git-tools.js.map +1 -0
  63. package/dist/tools/index.d.ts +15 -0
  64. package/dist/tools/index.d.ts.map +1 -0
  65. package/dist/tools/index.js +31 -0
  66. package/dist/tools/index.js.map +1 -0
  67. package/dist/tools/language-adapters.d.ts +48 -0
  68. package/dist/tools/language-adapters.d.ts.map +1 -0
  69. package/dist/tools/language-adapters.js +299 -0
  70. package/dist/tools/language-adapters.js.map +1 -0
  71. package/dist/tools/loop/compactionTrigger.d.ts +47 -0
  72. package/dist/tools/loop/compactionTrigger.d.ts.map +1 -0
  73. package/dist/tools/loop/compactionTrigger.js +32 -0
  74. package/dist/tools/loop/compactionTrigger.js.map +1 -0
  75. package/dist/tools/loop/finalAnswerNudges.d.ts +68 -0
  76. package/dist/tools/loop/finalAnswerNudges.d.ts.map +1 -0
  77. package/dist/tools/loop/finalAnswerNudges.js +87 -0
  78. package/dist/tools/loop/finalAnswerNudges.js.map +1 -0
  79. package/dist/tools/loop/goalAnchor.d.ts +72 -0
  80. package/dist/tools/loop/goalAnchor.d.ts.map +1 -0
  81. package/dist/tools/loop/goalAnchor.js +76 -0
  82. package/dist/tools/loop/goalAnchor.js.map +1 -0
  83. package/dist/tools/loop/llmStream.d.ts +70 -0
  84. package/dist/tools/loop/llmStream.d.ts.map +1 -0
  85. package/dist/tools/loop/llmStream.js +181 -0
  86. package/dist/tools/loop/llmStream.js.map +1 -0
  87. package/dist/tools/loop/parallelExecute.d.ts +57 -0
  88. package/dist/tools/loop/parallelExecute.d.ts.map +1 -0
  89. package/dist/tools/loop/parallelExecute.js +54 -0
  90. package/dist/tools/loop/parallelExecute.js.map +1 -0
  91. package/dist/tools/loop/singleToolExecute.d.ts +71 -0
  92. package/dist/tools/loop/singleToolExecute.d.ts.map +1 -0
  93. package/dist/tools/loop/singleToolExecute.js +139 -0
  94. package/dist/tools/loop/singleToolExecute.js.map +1 -0
  95. package/dist/tools/loop/toolCallNormalize.d.ts +57 -0
  96. package/dist/tools/loop/toolCallNormalize.d.ts.map +1 -0
  97. package/dist/tools/loop/toolCallNormalize.js +99 -0
  98. package/dist/tools/loop/toolCallNormalize.js.map +1 -0
  99. package/dist/tools/loop/turnSetup.d.ts +43 -0
  100. package/dist/tools/loop/turnSetup.d.ts.map +1 -0
  101. package/dist/tools/loop/turnSetup.js +48 -0
  102. package/dist/tools/loop/turnSetup.js.map +1 -0
  103. package/dist/tools/ocr.d.ts +52 -0
  104. package/dist/tools/ocr.d.ts.map +1 -0
  105. package/dist/tools/ocr.js +238 -0
  106. package/dist/tools/ocr.js.map +1 -0
  107. package/dist/tools/post-edit-checks.d.ts +46 -0
  108. package/dist/tools/post-edit-checks.d.ts.map +1 -0
  109. package/dist/tools/post-edit-checks.js +236 -0
  110. package/dist/tools/post-edit-checks.js.map +1 -0
  111. package/dist/tools/skill-loader.d.ts +94 -0
  112. package/dist/tools/skill-loader.d.ts.map +1 -0
  113. package/dist/tools/skill-loader.js +422 -0
  114. package/dist/tools/skill-loader.js.map +1 -0
  115. package/dist/tools/skill-registry.d.ts +44 -0
  116. package/dist/tools/skill-registry.d.ts.map +1 -0
  117. package/dist/tools/skill-registry.js +118 -0
  118. package/dist/tools/skill-registry.js.map +1 -0
  119. package/dist/tools/skill-types.d.ts +38 -0
  120. package/dist/tools/skill-types.d.ts.map +1 -0
  121. package/dist/tools/skill-types.js +10 -0
  122. package/dist/tools/skill-types.js.map +1 -0
  123. package/dist/tools/skills/code-review-skill.d.ts +9 -0
  124. package/dist/tools/skills/code-review-skill.d.ts.map +1 -0
  125. package/dist/tools/skills/code-review-skill.js +66 -0
  126. package/dist/tools/skills/code-review-skill.js.map +1 -0
  127. package/dist/tools/skills/core-skill.d.ts +13 -0
  128. package/dist/tools/skills/core-skill.d.ts.map +1 -0
  129. package/dist/tools/skills/core-skill.js +23 -0
  130. package/dist/tools/skills/core-skill.js.map +1 -0
  131. package/dist/tools/skills/git-skill.d.ts +10 -0
  132. package/dist/tools/skills/git-skill.d.ts.map +1 -0
  133. package/dist/tools/skills/git-skill.js +30 -0
  134. package/dist/tools/skills/git-skill.js.map +1 -0
  135. package/dist/tools/skills/index.d.ts +17 -0
  136. package/dist/tools/skills/index.d.ts.map +1 -0
  137. package/dist/tools/skills/index.js +49 -0
  138. package/dist/tools/skills/index.js.map +1 -0
  139. package/dist/tools/skills/interaction-skill.d.ts +14 -0
  140. package/dist/tools/skills/interaction-skill.d.ts.map +1 -0
  141. package/dist/tools/skills/interaction-skill.js +24 -0
  142. package/dist/tools/skills/interaction-skill.js.map +1 -0
  143. package/dist/tools/skills/mail-search-skill.d.ts +25 -0
  144. package/dist/tools/skills/mail-search-skill.d.ts.map +1 -0
  145. package/dist/tools/skills/mail-search-skill.js +343 -0
  146. package/dist/tools/skills/mail-search-skill.js.map +1 -0
  147. package/dist/tools/skills/plan-skill.d.ts +10 -0
  148. package/dist/tools/skills/plan-skill.d.ts.map +1 -0
  149. package/dist/tools/skills/plan-skill.js +126 -0
  150. package/dist/tools/skills/plan-skill.js.map +1 -0
  151. package/dist/tools/skills/semantic-search-skill.d.ts +22 -0
  152. package/dist/tools/skills/semantic-search-skill.d.ts.map +1 -0
  153. package/dist/tools/skills/semantic-search-skill.js +244 -0
  154. package/dist/tools/skills/semantic-search-skill.js.map +1 -0
  155. package/dist/tools/skills/test-gen-skill.d.ts +9 -0
  156. package/dist/tools/skills/test-gen-skill.d.ts.map +1 -0
  157. package/dist/tools/skills/test-gen-skill.js +123 -0
  158. package/dist/tools/skills/test-gen-skill.js.map +1 -0
  159. package/dist/tools/tool-registry.d.ts +60 -0
  160. package/dist/tools/tool-registry.d.ts.map +1 -0
  161. package/dist/tools/tool-registry.js +200 -0
  162. package/dist/tools/tool-registry.js.map +1 -0
  163. package/dist/tools/tool-types.d.ts +281 -0
  164. package/dist/tools/tool-types.d.ts.map +1 -0
  165. package/dist/tools/tool-types.js +10 -0
  166. package/dist/tools/tool-types.js.map +1 -0
  167. package/dist/tools/tool-use-loop.d.ts +231 -0
  168. package/dist/tools/tool-use-loop.d.ts.map +1 -0
  169. package/dist/tools/tool-use-loop.js +2057 -0
  170. package/dist/tools/tool-use-loop.js.map +1 -0
  171. package/dist/tools/tool-use-parser.d.ts +78 -0
  172. package/dist/tools/tool-use-parser.d.ts.map +1 -0
  173. package/dist/tools/tool-use-parser.js +427 -0
  174. package/dist/tools/tool-use-parser.js.map +1 -0
  175. package/dist/tools/toolAvailabilityDetector.d.ts +48 -0
  176. package/dist/tools/toolAvailabilityDetector.d.ts.map +1 -0
  177. package/dist/tools/toolAvailabilityDetector.js +156 -0
  178. package/dist/tools/toolAvailabilityDetector.js.map +1 -0
  179. package/dist/tools/unified-patch.d.ts +87 -0
  180. package/dist/tools/unified-patch.d.ts.map +1 -0
  181. package/dist/tools/unified-patch.js +217 -0
  182. package/dist/tools/unified-patch.js.map +1 -0
  183. package/dist/types/agent.d.ts +69 -0
  184. package/dist/types/agent.d.ts.map +1 -0
  185. package/dist/types/agent.js +54 -0
  186. package/dist/types/agent.js.map +1 -0
  187. package/dist/types/tasks.d.ts +22 -0
  188. package/dist/types/tasks.d.ts.map +1 -0
  189. package/dist/types/tasks.js +3 -0
  190. package/dist/types/tasks.js.map +1 -0
  191. package/dist/utils/event-emitter.d.ts +13 -0
  192. package/dist/utils/event-emitter.d.ts.map +1 -0
  193. package/dist/utils/event-emitter.js +54 -0
  194. package/dist/utils/event-emitter.js.map +1 -0
  195. package/package.json +33 -0
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Per-iteration goal-anchor injector for ToolUseLoop.runWithMessages.
3
+ *
4
+ * Re-injects the original user goal into the conversation when the
5
+ * loop is at risk of drifting. Two observed drift modes the anchor
6
+ * defends against:
7
+ *
8
+ * 1. Recency bias — long tool-result chains push the goal down the
9
+ * attention window; model answers about whatever was salient in
10
+ * the most recent tool reads.
11
+ * 2. Multi-turn pivot — after a stretch of failed tool calls,
12
+ * compaction cleans them up and the model re-answers an EARLIER
13
+ * user prompt that's cleaner to address.
14
+ *
15
+ * Eligibility: `iterations >= 2 AND messageTokens > 4000`. Re-fire
16
+ * allowed after `GOAL_ANCHOR_REFIRE_GAP` (4) iterations on sticky turns.
17
+ *
18
+ * Aggressive compaction this iteration overrides BOTH the refire-gap
19
+ * AND the eligibility floor — it's the highest-risk drift trigger we
20
+ * have. Compaction stripped most of the file content the model was
21
+ * reasoning over; without an immediate re-anchor the model falls back
22
+ * to imitating tool-result formats from training (the 2026-05-06
23
+ * fabricated `read_file` envelope) or pivoting to a tangent that
24
+ * sounded plausible from the surviving tool names.
25
+ *
26
+ * Post-aggressive-compaction anchors also include:
27
+ * - a "context just compacted" preamble explaining what the
28
+ * `[earlier run, N lines elided]` markers actually mean
29
+ * - the live tool list (MCP-namespaced first, capped at 40) so a
30
+ * surviving "tool not registered" error doesn't convince the model
31
+ * that real tools are unavailable
32
+ *
33
+ * The whole module returns the new `lastGoalAnchorIteration` so the
34
+ * orchestrator can update its mutable counter; no other state escapes.
35
+ */
36
+ import type { ToolLoopMessage } from '../tool-types';
37
+ import type { ToolRegistry } from '../tool-registry';
38
+ export type AnchorEmit = (type: string, payload?: unknown) => void;
39
+ export declare const GOAL_ANCHOR_REFIRE_GAP = 4;
40
+ export interface ApplyGoalAnchorArgs {
41
+ /** The user's goal for THIS turn (the most-recent substantive prompt). */
42
+ originalGoal: string;
43
+ /** Number of earlier user prompts in the seed — used by the
44
+ * "ignore prior prompts" suffix that defends against the multi-turn
45
+ * pivot failure mode. */
46
+ priorUserPromptCount: number;
47
+ /** When true, no anchor injection (the loop is wrapping up). */
48
+ hitLimit: boolean;
49
+ /** Current iteration counter. */
50
+ iteration: number;
51
+ /** Iteration at which the anchor last fired (-1 if never). */
52
+ lastGoalAnchorIteration: number;
53
+ /** When true, compaction this iteration dropped >=25% of tokens OR
54
+ * >=10k absolute. Overrides eligibility floor and refire gap. */
55
+ aggressiveCompactionThisIteration: boolean;
56
+ /** Mutable message log — anchor is pushed in place when fired. */
57
+ messages: ToolLoopMessage[];
58
+ /** Tool registry — read for the tool-list block when compaction is
59
+ * aggressive. */
60
+ registry: ToolRegistry;
61
+ /** Event sink for `tool_loop:goal_anchor`. */
62
+ emit: AnchorEmit;
63
+ }
64
+ export interface ApplyGoalAnchorResult {
65
+ /** New value for `lastGoalAnchorIteration` (caller assigns it back).
66
+ * Unchanged when the anchor did not fire. */
67
+ lastGoalAnchorIteration: number;
68
+ /** True when the anchor message was appended. */
69
+ anchored: boolean;
70
+ }
71
+ export declare function applyGoalAnchorIfNeeded(args: ApplyGoalAnchorArgs): ApplyGoalAnchorResult;
72
+ //# sourceMappingURL=goalAnchor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"goalAnchor.d.ts","sourceRoot":"","sources":["../../../src/tools/loop/goalAnchor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAEnE,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB;;6BAEyB;IACzB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gEAAgE;IAChE,QAAQ,EAAE,OAAO,CAAC;IAClB,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,uBAAuB,EAAE,MAAM,CAAC;IAChC;qEACiE;IACjE,iCAAiC,EAAE,OAAO,CAAC;IAC3C,kEAAkE;IAClE,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B;qBACiB;IACjB,QAAQ,EAAE,YAAY,CAAC;IACvB,8CAA8C;IAC9C,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC;iDAC6C;IAC7C,uBAAuB,EAAE,MAAM,CAAC;IAChC,iDAAiD;IACjD,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,mBAAmB,GAAG,qBAAqB,CAyFxF"}
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GOAL_ANCHOR_REFIRE_GAP = void 0;
4
+ exports.applyGoalAnchorIfNeeded = applyGoalAnchorIfNeeded;
5
+ exports.GOAL_ANCHOR_REFIRE_GAP = 4;
6
+ function applyGoalAnchorIfNeeded(args) {
7
+ const { originalGoal, priorUserPromptCount, hitLimit, iteration, lastGoalAnchorIteration, aggressiveCompactionThisIteration, messages, registry, emit } = args;
8
+ if (!originalGoal || hitLimit) {
9
+ return { lastGoalAnchorIteration, anchored: false };
10
+ }
11
+ const messageTokens = messages.reduce((acc, m) => acc + (m.content?.length ?? 0), 0);
12
+ const eligible = iteration >= 2 && messageTokens > 4000;
13
+ const canRefire = lastGoalAnchorIteration >= 0
14
+ && (iteration - lastGoalAnchorIteration) >= exports.GOAL_ANCHOR_REFIRE_GAP;
15
+ const shouldAnchor = aggressiveCompactionThisIteration
16
+ || (eligible && (lastGoalAnchorIteration < 0 || canRefire));
17
+ if (!shouldAnchor) {
18
+ return { lastGoalAnchorIteration, anchored: false };
19
+ }
20
+ emit('tool_loop:goal_anchor', {
21
+ iteration,
22
+ goalPreview: originalGoal.slice(0, 120),
23
+ priorUserPromptCount,
24
+ refire: canRefire,
25
+ postAggressiveCompaction: aggressiveCompactionThisIteration
26
+ });
27
+ // Multi-turn-aware anchor. When prior user prompts exist in history,
28
+ // explicitly tell the model to ignore them — that's the failure mode
29
+ // boolean recency-bias guards don't catch. Structured header +
30
+ // bullet so even small models can lock onto the format.
31
+ const ignoreEarlier = priorUserPromptCount > 0
32
+ ? `\n - There ${priorUserPromptCount === 1 ? 'is 1 earlier user prompt' : `are ${priorUserPromptCount} earlier user prompts`} in this conversation. Do NOT answer ${priorUserPromptCount === 1 ? 'it' : 'them'}. They were settled in prior turns.`
33
+ : '';
34
+ // Re-inject the current tool list whenever compaction has been
35
+ // aggressive — defends against the failure mode where an earlier
36
+ // error message ("tool not registered" or "expected object received
37
+ // string") survives compaction while the success path doesn't, and
38
+ // the model concludes the tool doesn't exist. The model would
39
+ // normally see the tools in the native-tools schema sent on every
40
+ // call, but small/mid models trust prose history over schema, so we
41
+ // make the availability claim textual and authoritative.
42
+ let toolListBlock = '';
43
+ if (aggressiveCompactionThisIteration) {
44
+ const allNames = registry.getAll().map((t) => t.name);
45
+ if (allNames.length > 0) {
46
+ // Cap to keep the prompt budget bounded — surface MCP-namespaced
47
+ // tools (the ones most likely to be hallucinated as absent)
48
+ // first, then any remaining names, then truncate.
49
+ const mcpNames = allNames.filter((n) => n.includes('.') || n.startsWith('mcp__'));
50
+ const otherNames = allNames.filter((n) => !mcpNames.includes(n));
51
+ const ordered = [...mcpNames, ...otherNames];
52
+ const TOOL_LIST_CAP = 40;
53
+ const shown = ordered.slice(0, TOOL_LIST_CAP);
54
+ const more = ordered.length > TOOL_LIST_CAP ? ` (+${ordered.length - TOOL_LIST_CAP} more)` : '';
55
+ toolListBlock =
56
+ `## TOOLS CURRENTLY AVAILABLE THIS TURN${more}\n\n` +
57
+ shown.map((n) => ` - ${n}`).join('\n') +
58
+ '\n\nIf you think a tool is missing, re-check this list before saying so. ' +
59
+ 'The names above were sent to you in the native-tools schema on THIS call.\n\n';
60
+ }
61
+ }
62
+ const compactionPreamble = aggressiveCompactionThisIteration
63
+ ? '## CONTEXT JUST COMPACTED — read this first.\n\n' +
64
+ 'Most of the tool-result content from this turn was just collapsed to one-line placeholders to fit the context window. ' +
65
+ 'Those `[earlier run, N lines elided]` markers represent real reads whose content is no longer in front of you. ' +
66
+ 'Do NOT fabricate `<tool_result>` blocks pretending to read files; do NOT pivot to a topic that looks salient based on which tool names survived in the placeholders. ' +
67
+ 'Answer from what you ALREADY learned in this turn, owning honestly anything you cannot recall in detail.\n\n' +
68
+ toolListBlock
69
+ : '';
70
+ messages.push({
71
+ role: 'user',
72
+ content: `${compactionPreamble}## CURRENT GOAL — answer THIS, nothing else:\n\n "${originalGoal.trim()}"\n\nRules:\n - Use what you have gathered to answer the goal above.\n - Do not pivot to a related topic that happens to be salient in recent tool results.${ignoreEarlier}\n - If the available tools cannot finish the goal, own that honestly in your final answer — do NOT redirect to an easier question.`
73
+ });
74
+ return { lastGoalAnchorIteration: iteration, anchored: true };
75
+ }
76
+ //# sourceMappingURL=goalAnchor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"goalAnchor.js","sourceRoot":"","sources":["../../../src/tools/loop/goalAnchor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * LLM streaming with intra-response loop detection and same-channel
3
+ * transient-error retry. Extracted from ToolUseLoop.runWithMessages so
4
+ * the orchestrator only holds high-level control flow, not the chunk-
5
+ * level fingerprint accounting and the retry ladder.
6
+ *
7
+ * Behavior pinned by `packages/agent-core/test/llmStreamContract.test.ts`:
8
+ *
9
+ * - Chunks emit through `tool_loop:llm_chunk` in arrival order; the
10
+ * aggregated text is returned in full when no detector trips.
11
+ * - HARD_MAX cap (24,000 chars) aborts the stream, marks it, and
12
+ * appends a sentinel so the downstream prose-loop detector can route
13
+ * to the nudge path. Real trace it caught: a single response of
14
+ * 24,663 chars of "Wait, I'll try X. Actually..." prose.
15
+ * - Line-level fast path: 5 identical short lines (<= 120 chars,
16
+ * non-empty, trimmed) in a row aborts the stream. Catches
17
+ * "I can't install things." × 24 before the 800-char fingerprint
18
+ * window can fire (24 short lines ≈ 550 chars, below CHECK_EVERY).
19
+ * - Window-level fingerprint: every 800 chars, hash the last 400
20
+ * (whitespace-normalized, numeric tokens collapsed to '#') and check
21
+ * for 3 identical fingerprints in a row. Catches re-numbered list
22
+ * repetition the line-level path misses.
23
+ * - User abort: `signal.aborted` mid-stream emits `stream_abort` with
24
+ * reason `cancelled` and breaks. No retry.
25
+ * - Retryable error WITH zero text streamed AND (no native tools OR
26
+ * not yet streamed): exponential backoff retry (1.2s × 2^n, two
27
+ * attempts). Emits `tool_loop:llm_retry` per attempt.
28
+ * - Non-retryable error OR partial text already streamed: re-throw
29
+ * (tagged with `UPSTREAM_MODEL` code on retryable to preserve the
30
+ * existing error-summarization contract for outer-layer handlers).
31
+ *
32
+ * Gate widening note (preserved from the in-class implementation):
33
+ * the original retry gate was `!tools || tools.length === 0` — only
34
+ * text-tools turns got the same-channel ladder. That meant native-tools
35
+ * turns hit a transient 500 once and jumped to the disruptive native→
36
+ * text envelope switch (a second hiccup hard-failed). Widening to
37
+ * `!tools || tools.length === 0 || safeToReplay` keeps the original
38
+ * guarantee (no duplicated tool_call deltas after partial output) but
39
+ * lets the native path retry transient infra blips before the envelope
40
+ * switch escalates.
41
+ */
42
+ import type { ChatFn, ToolLoopMessage, ChatCallOptions, NativeToolSchema } from '../tool-types';
43
+ export type StreamEmit = (type: string, payload?: unknown) => void;
44
+ export interface StreamAndAggregateArgs {
45
+ chat: ChatFn;
46
+ messages: ToolLoopMessage[];
47
+ emit: StreamEmit;
48
+ iteration: number;
49
+ tools?: NativeToolSchema[];
50
+ signal?: AbortSignal;
51
+ callOptions?: ChatCallOptions;
52
+ }
53
+ /**
54
+ * Stream from the chat function and return the fully aggregated response
55
+ * string. Aborts early if we detect the model is trapped in a self-
56
+ * contradicting prose loop — repeating phrases like "Wait, I see X
57
+ * isn't listed. Let me check X. Actually, I'll try to read X." without
58
+ * ever emitting a tool call. This was observed Apr 2026 on
59
+ * bandit-core-1: a single stream produced 24k chars of such prose.
60
+ * Without this guard the turn terminates naturally (no tool calls →
61
+ * final response), but the user sees the entire wall of repetition.
62
+ *
63
+ * Detection is cheap: after every ~800 chars we compute a hash of the
64
+ * last ~400 chars and check if the same hash appeared recently. Three
65
+ * repeats in a row means we're looping — abort and return what we
66
+ * have. The upstream loop will then still call the prose-loop detector
67
+ * and apply its nudge on the next iteration.
68
+ */
69
+ export declare function streamAndAggregate(args: StreamAndAggregateArgs): Promise<string>;
70
+ //# sourceMappingURL=llmStream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llmStream.d.ts","sourceRoot":"","sources":["../../../src/tools/loop/llmStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGhG,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAEnE,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC3B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmKtF"}
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.streamAndAggregate = streamAndAggregate;
4
+ const tool_use_loop_1 = require("../tool-use-loop");
5
+ /**
6
+ * Stream from the chat function and return the fully aggregated response
7
+ * string. Aborts early if we detect the model is trapped in a self-
8
+ * contradicting prose loop — repeating phrases like "Wait, I see X
9
+ * isn't listed. Let me check X. Actually, I'll try to read X." without
10
+ * ever emitting a tool call. This was observed Apr 2026 on
11
+ * bandit-core-1: a single stream produced 24k chars of such prose.
12
+ * Without this guard the turn terminates naturally (no tool calls →
13
+ * final response), but the user sees the entire wall of repetition.
14
+ *
15
+ * Detection is cheap: after every ~800 chars we compute a hash of the
16
+ * last ~400 chars and check if the same hash appeared recently. Three
17
+ * repeats in a row means we're looping — abort and return what we
18
+ * have. The upstream loop will then still call the prose-loop detector
19
+ * and apply its nudge on the next iteration.
20
+ */
21
+ async function streamAndAggregate(args) {
22
+ const { chat, messages, emit, iteration, tools, signal, callOptions } = args;
23
+ let text = '';
24
+ // Intra-response loop guard. Tuned by observation, not theory:
25
+ // - CHECK_EVERY=800: a single "Actually, I'll try to read X." cycle
26
+ // in the real trace was ~120-180 chars, so we want multiple
27
+ // samples per cycle but not one per chunk (that would be slow).
28
+ // - WINDOW=400: the tail chunk used to build the fingerprint.
29
+ // - THRESHOLD=3: three identical fingerprints in a row is
30
+ // overwhelmingly a loop; two could be a natural repetition
31
+ // (e.g. a code block shown twice).
32
+ // - HARD_MAX=24000: a soft upper bound so a runaway generation
33
+ // doesn't burn unbounded tokens even if the fingerprint shifts
34
+ // slightly each cycle. The real trace was 24663 chars.
35
+ const CHECK_EVERY = 800;
36
+ const WINDOW = 400;
37
+ const THRESHOLD = 3;
38
+ const HARD_MAX = 24000;
39
+ const recentFingerprints = [];
40
+ let nextCheckAt = CHECK_EVERY;
41
+ let aborted = false;
42
+ // Line-level repetition guard. Catches "I can't install things." × 24
43
+ // long before the 800-char fingerprint check fires (24 short lines is
44
+ // ~550 chars, below CHECK_EVERY). Tracks the last completed line
45
+ // (text up to the most recent \n) and counts consecutive identical
46
+ // non-empty short lines. Five in a row is a clear loop; trips
47
+ // earlier than the full-fingerprint detector ever can.
48
+ const REPEAT_LINE_THRESHOLD = 5;
49
+ const REPEAT_LINE_MAXLEN = 120; // only short lines — long paragraphs aren't loops
50
+ let lastLine = '';
51
+ let repeatCount = 1;
52
+ let lastEmittedLineLength = 0;
53
+ const MAX_TRANSIENT_RETRIES = 2;
54
+ const BASE_RETRY_MS = 1200;
55
+ let transientRetries = 0;
56
+ while (true) {
57
+ try {
58
+ for await (const chunk of chat(messages, tools, callOptions)) {
59
+ if (signal?.aborted) {
60
+ aborted = true;
61
+ emit('tool_loop:stream_abort', { iteration, reason: 'cancelled', length: text.length });
62
+ break;
63
+ }
64
+ text += chunk;
65
+ emit('tool_loop:llm_chunk', { iteration, chunk });
66
+ if (text.length >= HARD_MAX) {
67
+ aborted = true;
68
+ emit('tool_loop:stream_abort', { iteration, reason: 'hard_max', length: text.length });
69
+ break;
70
+ }
71
+ // Line-repetition fast path. Walk newly-completed lines (between
72
+ // lastEmittedLineLength and the most recent \n) and bump a
73
+ // consecutive-identical counter. Trips faster than the
74
+ // 800-char fingerprint window for short-phrase loops.
75
+ const lastNewline = text.lastIndexOf('\n');
76
+ if (lastNewline > lastEmittedLineLength) {
77
+ const newSegment = text.slice(lastEmittedLineLength, lastNewline);
78
+ for (const rawLine of newSegment.split('\n')) {
79
+ const line = rawLine.trim();
80
+ if (!line || line.length > REPEAT_LINE_MAXLEN) {
81
+ lastLine = line;
82
+ repeatCount = 1;
83
+ continue;
84
+ }
85
+ if (line === lastLine) {
86
+ repeatCount += 1;
87
+ if (repeatCount >= REPEAT_LINE_THRESHOLD) {
88
+ aborted = true;
89
+ emit('tool_loop:stream_abort', {
90
+ iteration,
91
+ reason: 'line_repetition_loop',
92
+ length: text.length,
93
+ fingerprintPreview: line.slice(0, 120),
94
+ repeatCount
95
+ });
96
+ break;
97
+ }
98
+ }
99
+ else {
100
+ lastLine = line;
101
+ repeatCount = 1;
102
+ }
103
+ }
104
+ lastEmittedLineLength = lastNewline + 1;
105
+ if (aborted)
106
+ break;
107
+ }
108
+ if (text.length >= nextCheckAt) {
109
+ nextCheckAt = text.length + CHECK_EVERY;
110
+ const tail = text.slice(Math.max(0, text.length - WINDOW));
111
+ // Normalize whitespace + drop numeric noise so "3." vs "4." don't
112
+ // defeat the comparison when a model re-numbers each attempt.
113
+ const fingerprint = tail
114
+ .toLowerCase()
115
+ .replace(/\s+/g, ' ')
116
+ .replace(/\d+/g, '#')
117
+ .trim();
118
+ recentFingerprints.push(fingerprint);
119
+ if (recentFingerprints.length > THRESHOLD)
120
+ recentFingerprints.shift();
121
+ if (recentFingerprints.length === THRESHOLD &&
122
+ recentFingerprints.every(f => f === fingerprint)) {
123
+ aborted = true;
124
+ emit('tool_loop:stream_abort', {
125
+ iteration,
126
+ reason: 'intra_response_loop',
127
+ length: text.length,
128
+ fingerprintPreview: fingerprint.slice(0, 120)
129
+ });
130
+ break;
131
+ }
132
+ }
133
+ }
134
+ break;
135
+ }
136
+ catch (error) {
137
+ const retryable = (0, tool_use_loop_1.isRetryableLlmError)(error);
138
+ const safeToReplay = text.length === 0;
139
+ // Original gate was `!tools || tools.length === 0` — i.e. only
140
+ // text-tools turns got the same-channel backoff ladder. Native-tools
141
+ // turns hit a transient 500 once and jumped straight to the
142
+ // disruptive native→text envelope switch (and a second hiccup
143
+ // hard-failed). The gate exists to prevent re-emitting tool-call
144
+ // deltas after partial output was already streamed; when
145
+ // `safeToReplay` is true NO chunks have arrived yet, so there are
146
+ // no deltas to duplicate. Widening the gate to allow retry when
147
+ // safeToReplay holds catches transient infra blips (gateway load
148
+ // shedding, ollama restart, brief network glitches) on the native
149
+ // path without risking duplicated tool calls.
150
+ const sameTransportRetryAllowed = !tools || tools.length === 0 || safeToReplay;
151
+ if (retryable && safeToReplay && sameTransportRetryAllowed && !signal?.aborted && transientRetries < MAX_TRANSIENT_RETRIES) {
152
+ transientRetries++;
153
+ const delayMs = BASE_RETRY_MS * Math.pow(2, transientRetries - 1);
154
+ emit('tool_loop:llm_retry', {
155
+ iteration,
156
+ attempt: transientRetries + 1,
157
+ maxAttempts: MAX_TRANSIENT_RETRIES + 1,
158
+ delayMs,
159
+ reason: (0, tool_use_loop_1.summarizeLlmError)(error)
160
+ });
161
+ await (0, tool_use_loop_1.sleep)(delayMs);
162
+ continue;
163
+ }
164
+ if (retryable)
165
+ (0, tool_use_loop_1.tagRetryableLlmError)(error);
166
+ throw error;
167
+ }
168
+ }
169
+ // If we aborted a runaway stream, trim trailing garbage and append
170
+ // a sentinel so the downstream final-response detector can recognize
171
+ // this and route to the prose-loop nudge (or a clean termination).
172
+ if (aborted) {
173
+ // Drop the last (likely-truncated) repetition cycle from the
174
+ // output — cleaner to end at a previous paragraph boundary than
175
+ // mid-sentence.
176
+ const trimmed = text.replace(/\s+$/, '');
177
+ text = trimmed + '\n\n[stream aborted: self-contradicting prose loop detected]';
178
+ }
179
+ return text;
180
+ }
181
+ //# sourceMappingURL=llmStream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llmStream.js","sourceRoot":"","sources":["../../../src/tools/loop/llmStream.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Output-budget-aware batch dispatch for ToolUseLoop.runWithMessages.
3
+ *
4
+ * Takes a normalized batch (already trimmed by maxParallelTools +
5
+ * dedup'd by `normalizeToolCallBatch`) and runs it. Two execution
6
+ * modes:
7
+ *
8
+ * Parallel (default): `Promise.all(toolCalls.map(dispatchOne))`.
9
+ *
10
+ * Serial: when the estimated combined output of the batch would
11
+ * exceed `outputBudgetTokens * outputBudgetRatio`, the batch runs
12
+ * one tool at a time. Each call short-circuits on `signal.aborted`.
13
+ *
14
+ * Why the serial mode exists: smaller models (4B–12B) generate
15
+ * malformed JSON in the tail of a multi-file emission once their
16
+ * effective output budget is exhausted. on a
17
+ * portfolio build — even a strong model produced a malformed
18
+ * `todo_write` after writing four files of ~7 KB each in one
19
+ * assistant turn. Serialising lets the model react to each result
20
+ * before committing further output, and gives the user one approval
21
+ * at a time instead of a queued pile.
22
+ *
23
+ * Single-call batches skip the threshold check entirely — there's no
24
+ * parallel/serial distinction with one call, and the gate's purpose
25
+ * is preventing a *batch* from overrunning the assistant turn.
26
+ *
27
+ * Token estimate is intentionally coarse (heavy payload fields × ¼).
28
+ * Reads and small calls never trip the gate; only writes/edits whose
29
+ * `content`/`replace`/`find`/`text` fields dominate the output budget.
30
+ * Accuracy isn't the goal — order-of-magnitude is enough to gate.
31
+ */
32
+ import type { ParsedToolCall } from '../tool-use-parser';
33
+ import type { ToolDispatchResult } from './singleToolExecute';
34
+ export type BatchEmit = (type: string, payload?: unknown) => void;
35
+ export interface ExecuteParallelBatchArgs {
36
+ toolCalls: ParsedToolCall[];
37
+ dispatchOne: (tc: ParsedToolCall) => Promise<ToolDispatchResult>;
38
+ outputBudgetTokens: number;
39
+ outputBudgetRatio: number;
40
+ emit: BatchEmit;
41
+ iteration: number;
42
+ signal?: AbortSignal;
43
+ }
44
+ /**
45
+ * Coarse token estimate for a single tool call's contribution to the
46
+ * assistant turn's output. Heavy fields (file content, edit
47
+ * replacements, apply_edit find/replace blocks) dominate; everything
48
+ * else is negligible. Uses chars/4 as a rough byte→token approximation
49
+ * — fast and good enough to gate batches; we don't need accuracy, just
50
+ * an order-of-magnitude check.
51
+ */
52
+ export declare function estimateToolCallOutputTokens(tc: {
53
+ name: string;
54
+ params: Record<string, string>;
55
+ }): number;
56
+ export declare function executeParallelBatch(args: ExecuteParallelBatchArgs): Promise<ToolDispatchResult[]>;
57
+ //# sourceMappingURL=parallelExecute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parallelExecute.d.ts","sourceRoot":"","sources":["../../../src/tools/loop/parallelExecute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAElE,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,WAAW,EAAE,CAAC,EAAE,EAAE,cAAc,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACjE,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,MAAM,CAMzG;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,wBAAwB,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAmCxG"}
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.estimateToolCallOutputTokens = estimateToolCallOutputTokens;
4
+ exports.executeParallelBatch = executeParallelBatch;
5
+ /**
6
+ * Coarse token estimate for a single tool call's contribution to the
7
+ * assistant turn's output. Heavy fields (file content, edit
8
+ * replacements, apply_edit find/replace blocks) dominate; everything
9
+ * else is negligible. Uses chars/4 as a rough byte→token approximation
10
+ * — fast and good enough to gate batches; we don't need accuracy, just
11
+ * an order-of-magnitude check.
12
+ */
13
+ function estimateToolCallOutputTokens(tc) {
14
+ const params = tc.params ?? {};
15
+ const heavy = [params.content, params.replace, params.find, params.text]
16
+ .filter((v) => typeof v === 'string' && v.length > 0)
17
+ .reduce((sum, s) => sum + s.length, 0);
18
+ return Math.ceil(heavy / 4);
19
+ }
20
+ async function executeParallelBatch(args) {
21
+ const { toolCalls, dispatchOne, outputBudgetTokens, outputBudgetRatio, emit, iteration, signal } = args;
22
+ // Output-budget gate. Only meaningful for multi-call batches —
23
+ // single-call iterations never have a parallel/serial choice.
24
+ let serializeBatch = false;
25
+ if (Number.isFinite(outputBudgetTokens) && toolCalls.length > 1) {
26
+ let estimatedBatchOutputTokens = 0;
27
+ for (const tc of toolCalls) {
28
+ estimatedBatchOutputTokens += estimateToolCallOutputTokens(tc);
29
+ }
30
+ const threshold = outputBudgetTokens * outputBudgetRatio;
31
+ if (estimatedBatchOutputTokens > threshold) {
32
+ serializeBatch = true;
33
+ emit('tool_loop:batch_serialized', {
34
+ iteration,
35
+ toolCount: toolCalls.length,
36
+ estimatedTokens: estimatedBatchOutputTokens,
37
+ budgetTokens: outputBudgetTokens,
38
+ threshold: Math.floor(threshold),
39
+ reason: 'output-budget-exceeded'
40
+ });
41
+ }
42
+ }
43
+ if (serializeBatch) {
44
+ const results = [];
45
+ for (const tc of toolCalls) {
46
+ if (signal?.aborted)
47
+ break;
48
+ results.push(await dispatchOne(tc));
49
+ }
50
+ return results;
51
+ }
52
+ return Promise.all(toolCalls.map(dispatchOne));
53
+ }
54
+ //# sourceMappingURL=parallelExecute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parallelExecute.js","sourceRoot":"","sources":["../../../src/tools/loop/parallelExecute.ts"],"names":[],"mappings":";;AAsDA,oEAMC;AAED,oDAmCC;AAnDD;;;;;;;GAOG;AACH,SAAgB,4BAA4B,CAAC,EAAoD;IAC/F,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC;IAC/B,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC;SACrE,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;SACjE,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACzC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;AAC9B,CAAC;AAEM,KAAK,UAAU,oBAAoB,CAAC,IAA8B;IACvE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAExG,+DAA+D;IAC/D,8DAA8D;IAC9D,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,IAAI,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChE,IAAI,0BAA0B,GAAG,CAAC,CAAC;QACnC,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;YAC3B,0BAA0B,IAAI,4BAA4B,CAAC,EAAE,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,SAAS,GAAG,kBAAkB,GAAG,iBAAiB,CAAC;QACzD,IAAI,0BAA0B,GAAG,SAAS,EAAE,CAAC;YAC3C,cAAc,GAAG,IAAI,CAAC;YACtB,IAAI,CAAC,4BAA4B,EAAE;gBACjC,SAAS;gBACT,SAAS,EAAE,SAAS,CAAC,MAAM;gBAC3B,eAAe,EAAE,0BAA0B;gBAC3C,YAAY,EAAE,kBAAkB;gBAChC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;gBAChC,MAAM,EAAE,wBAAwB;aACjC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,cAAc,EAAE,CAAC;QACnB,MAAM,OAAO,GAAyB,EAAE,CAAC;QACzC,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;YAC3B,IAAI,MAAM,EAAE,OAAO;gBAAE,MAAM;YAC3B,OAAO,CAAC,IAAI,CAAC,MAAM,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC;AACjD,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Per-tool execution closure for ToolUseLoop.runWithMessages.
3
+ *
4
+ * The dispatcher takes one ParsedToolCall and runs it end-to-end:
5
+ *
6
+ * - Build a coarse signature for the repeat-call breaker (the model's
7
+ * "I'll just retry the same broken write 4 times in a row" failure
8
+ * mode). The window is `repeatLimit` calls; once all entries are
9
+ * identical, the dispatcher short-circuits with a stop-this-now error
10
+ * instead of running the tool.
11
+ * - Look up the tool in the registry. Unknown tool → error result.
12
+ * - Run the host's `beforeToolExecute` gate. Blocked → error result.
13
+ * - Execute the tool. On success: bump the success-only edit counter
14
+ * via `onEditToolSucceeded()` for file-editing tools, and record the
15
+ * touched file (normalized to basename) in `filesReadThisTurn` /
16
+ * `filesWrittenThisTurn`. On exception: catch + emit `tool_error`.
17
+ * - Emit `tool_execute` before invocation and `tool_result` (with a
18
+ * redacted output snippet) after.
19
+ *
20
+ * The factory captures all the per-turn deps in a closure and returns
21
+ * a single async function. Mutable turn-state — `recentCallKeys`,
22
+ * `filesReadThisTurn`, `filesWrittenThisTurn` — is passed by reference
23
+ * so the dispatcher mutates the same objects the orchestrator (and the
24
+ * loop's detector pipeline) read.
25
+ *
26
+ * The success-only edit counter (`editToolsInvoked` in the orchestrator)
27
+ * has multiple downstream readers (false-completion detector, wrap-up
28
+ * template chooser), so it's routed through `onEditToolSucceeded` rather
29
+ * than incremented directly here.
30
+ */
31
+ import type { ParsedToolCall } from '../tool-use-parser';
32
+ import type { ToolExecutionContext } from '../tool-types';
33
+ import type { ToolRegistry } from '../tool-registry';
34
+ export type DispatchEmit = (type: string, payload?: unknown) => void;
35
+ export type BeforeToolExecuteFn = (call: {
36
+ name: string;
37
+ params: Record<string, string>;
38
+ }) => Promise<{
39
+ allow: boolean;
40
+ reason?: string;
41
+ }> | {
42
+ allow: boolean;
43
+ reason?: string;
44
+ };
45
+ export interface ToolDispatchDeps {
46
+ registry: ToolRegistry;
47
+ ctx: ToolExecutionContext;
48
+ beforeToolExecute: BeforeToolExecuteFn;
49
+ emit: DispatchEmit;
50
+ /** Mutable turn-state — rolling window of recent call signatures. */
51
+ recentCallKeys: string[];
52
+ /** Cap on the rolling window; identical keys filling it trip the breaker. */
53
+ repeatLimit: number;
54
+ /** Mutable set of basenames read this turn (used by false-completion detector). */
55
+ filesReadThisTurn: Set<string>;
56
+ /** Mutable set of basenames written this turn. */
57
+ filesWrittenThisTurn: Set<string>;
58
+ /** Predicate identifying file-editing tools (write/apply_edit/replace_range). */
59
+ isFileEditTool: (name: string) => boolean;
60
+ /** Called once per successful file-editing tool result. Hosts use this
61
+ * to bump the `editToolsInvoked` counter that gates false-completion
62
+ * nudges and wrap-up template selection. */
63
+ onEditToolSucceeded: () => void;
64
+ }
65
+ export type ToolDispatchResult = {
66
+ name: string;
67
+ output: string;
68
+ isError?: boolean;
69
+ };
70
+ export declare function createToolDispatcher(deps: ToolDispatchDeps): (tc: ParsedToolCall) => Promise<ToolDispatchResult>;
71
+ //# sourceMappingURL=singleToolExecute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"singleToolExecute.d.ts","sourceRoot":"","sources":["../../../src/tools/loop/singleToolExecute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,oBAAoB,EAAc,MAAM,eAAe,CAAC;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGrD,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AACrE,MAAM,MAAM,mBAAmB,GAAG,CAChC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,KACnD,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,YAAY,CAAC;IACvB,GAAG,EAAE,oBAAoB,CAAC;IAC1B,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,IAAI,EAAE,YAAY,CAAC;IACnB,qEAAqE;IACrE,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,6EAA6E;IAC7E,WAAW,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,iBAAiB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B,kDAAkD;IAClD,oBAAoB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAClC,iFAAiF;IACjF,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC1C;;gDAE4C;IAC5C,mBAAmB,EAAE,MAAM,IAAI,CAAC;CACjC;AAED,MAAM,MAAM,kBAAkB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAErF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,gBAAgB,GAAG,CAAC,EAAE,EAAE,cAAc,KAAK,OAAO,CAAC,kBAAkB,CAAC,CA4IhH"}