agentfootprint 7.17.0 → 7.19.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 (202) hide show
  1. package/AGENTS.md +1 -1
  2. package/CLAUDE.md +2 -2
  3. package/ai-instructions/claude-code/SKILL.md +1 -1
  4. package/dist/adapters/hosting/agentcore.js +23 -9
  5. package/dist/adapters/hosting/agentcore.js.map +1 -1
  6. package/dist/core/Agent.js +57 -2
  7. package/dist/core/Agent.js.map +1 -1
  8. package/dist/core/RunnerBase.js +16 -1
  9. package/dist/core/RunnerBase.js.map +1 -1
  10. package/dist/core/agent/AgentBuilder.js +117 -1
  11. package/dist/core/agent/AgentBuilder.js.map +1 -1
  12. package/dist/core/agent/middleware/errors.js +54 -0
  13. package/dist/core/agent/middleware/errors.js.map +1 -0
  14. package/dist/core/agent/middleware/index.js +23 -0
  15. package/dist/core/agent/middleware/index.js.map +1 -0
  16. package/dist/core/agent/middleware/ledger.js +61 -0
  17. package/dist/core/agent/middleware/ledger.js.map +1 -0
  18. package/dist/core/agent/middleware/outcomes.js +66 -0
  19. package/dist/core/agent/middleware/outcomes.js.map +1 -0
  20. package/dist/core/agent/middleware/runChain.js +144 -0
  21. package/dist/core/agent/middleware/runChain.js.map +1 -0
  22. package/dist/core/agent/middleware/types.js +41 -0
  23. package/dist/core/agent/middleware/types.js.map +1 -0
  24. package/dist/core/agent/stages/prepareFinal.js +10 -0
  25. package/dist/core/agent/stages/prepareFinal.js.map +1 -1
  26. package/dist/core/agent/stages/route.js +62 -1
  27. package/dist/core/agent/stages/route.js.map +1 -1
  28. package/dist/core/agent/stages/seed.js +130 -85
  29. package/dist/core/agent/stages/seed.js.map +1 -1
  30. package/dist/core/agent/stages/toolCalls.js +208 -8
  31. package/dist/core/agent/stages/toolCalls.js.map +1 -1
  32. package/dist/core/durabilityBarrier.js +68 -0
  33. package/dist/core/durabilityBarrier.js.map +1 -0
  34. package/dist/core/pause.js +19 -1
  35. package/dist/core/pause.js.map +1 -1
  36. package/dist/esm/adapters/hosting/agentcore.d.ts +4 -4
  37. package/dist/esm/adapters/hosting/agentcore.js +24 -10
  38. package/dist/esm/adapters/hosting/agentcore.js.map +1 -1
  39. package/dist/esm/core/Agent.d.ts +9 -1
  40. package/dist/esm/core/Agent.js +58 -3
  41. package/dist/esm/core/Agent.js.map +1 -1
  42. package/dist/esm/core/RunnerBase.js +16 -1
  43. package/dist/esm/core/RunnerBase.js.map +1 -1
  44. package/dist/esm/core/agent/AgentBuilder.d.ts +97 -0
  45. package/dist/esm/core/agent/AgentBuilder.js +117 -1
  46. package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
  47. package/dist/esm/core/agent/buildAgentChart.d.ts +2 -2
  48. package/dist/esm/core/agent/middleware/errors.d.ts +51 -0
  49. package/dist/esm/core/agent/middleware/errors.js +50 -0
  50. package/dist/esm/core/agent/middleware/errors.js.map +1 -0
  51. package/dist/esm/core/agent/middleware/index.d.ts +16 -0
  52. package/dist/esm/core/agent/middleware/index.js +16 -0
  53. package/dist/esm/core/agent/middleware/index.js.map +1 -0
  54. package/dist/esm/core/agent/middleware/ledger.d.ts +40 -0
  55. package/dist/esm/core/agent/middleware/ledger.js +57 -0
  56. package/dist/esm/core/agent/middleware/ledger.js.map +1 -0
  57. package/dist/esm/core/agent/middleware/outcomes.d.ts +45 -0
  58. package/dist/esm/core/agent/middleware/outcomes.js +60 -0
  59. package/dist/esm/core/agent/middleware/outcomes.js.map +1 -0
  60. package/dist/esm/core/agent/middleware/runChain.d.ts +104 -0
  61. package/dist/esm/core/agent/middleware/runChain.js +139 -0
  62. package/dist/esm/core/agent/middleware/runChain.js.map +1 -0
  63. package/dist/esm/core/agent/middleware/types.d.ts +191 -0
  64. package/dist/esm/core/agent/middleware/types.js +40 -0
  65. package/dist/esm/core/agent/middleware/types.js.map +1 -0
  66. package/dist/esm/core/agent/stages/prepareFinal.d.ts +10 -0
  67. package/dist/esm/core/agent/stages/prepareFinal.js +10 -0
  68. package/dist/esm/core/agent/stages/prepareFinal.js.map +1 -1
  69. package/dist/esm/core/agent/stages/route.d.ts +29 -0
  70. package/dist/esm/core/agent/stages/route.js +60 -0
  71. package/dist/esm/core/agent/stages/route.js.map +1 -1
  72. package/dist/esm/core/agent/stages/seed.d.ts +16 -1
  73. package/dist/esm/core/agent/stages/seed.js +130 -85
  74. package/dist/esm/core/agent/stages/seed.js.map +1 -1
  75. package/dist/esm/core/agent/stages/toolCalls.d.ts +44 -0
  76. package/dist/esm/core/agent/stages/toolCalls.js +208 -8
  77. package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
  78. package/dist/esm/core/agent/types.d.ts +18 -0
  79. package/dist/esm/core/durabilityBarrier.d.ts +61 -0
  80. package/dist/esm/core/durabilityBarrier.js +63 -0
  81. package/dist/esm/core/durabilityBarrier.js.map +1 -0
  82. package/dist/esm/core/pause.d.ts +42 -0
  83. package/dist/esm/core/pause.js +17 -0
  84. package/dist/esm/core/pause.js.map +1 -1
  85. package/dist/esm/events/dispatcher.d.ts +1 -1
  86. package/dist/esm/events/dispatcher.js.map +1 -1
  87. package/dist/esm/events/payloads.d.ts +26 -0
  88. package/dist/esm/events/registry.d.ts +5 -1
  89. package/dist/esm/events/registry.js +4 -0
  90. package/dist/esm/events/registry.js.map +1 -1
  91. package/dist/esm/hosting/durability.d.ts +92 -0
  92. package/dist/esm/hosting/durability.js +174 -0
  93. package/dist/esm/hosting/durability.js.map +1 -0
  94. package/dist/esm/hosting/envelope.d.ts +75 -14
  95. package/dist/esm/hosting/envelope.js +141 -16
  96. package/dist/esm/hosting/envelope.js.map +1 -1
  97. package/dist/esm/hosting/errors.d.ts +60 -11
  98. package/dist/esm/hosting/errors.js +92 -18
  99. package/dist/esm/hosting/errors.js.map +1 -1
  100. package/dist/esm/hosting/httpHost.d.ts +17 -1
  101. package/dist/esm/hosting/httpHost.js +42 -6
  102. package/dist/esm/hosting/httpHost.js.map +1 -1
  103. package/dist/esm/hosting/index.d.ts +7 -4
  104. package/dist/esm/hosting/index.js +6 -3
  105. package/dist/esm/hosting/index.js.map +1 -1
  106. package/dist/esm/hosting/nodeHost.d.ts +4 -2
  107. package/dist/esm/hosting/nodeHost.js +14 -3
  108. package/dist/esm/hosting/nodeHost.js.map +1 -1
  109. package/dist/esm/hosting/standingAgent.d.ts +22 -7
  110. package/dist/esm/hosting/standingAgent.js +144 -32
  111. package/dist/esm/hosting/standingAgent.js.map +1 -1
  112. package/dist/esm/hosting/types.d.ts +193 -19
  113. package/dist/esm/index.d.ts +2 -1
  114. package/dist/esm/index.js +13 -1
  115. package/dist/esm/index.js.map +1 -1
  116. package/dist/esm/lib/mcp/mcpServe.js +39 -6
  117. package/dist/esm/lib/mcp/mcpServe.js.map +1 -1
  118. package/dist/esm/lib/mcp/types.d.ts +16 -0
  119. package/dist/events/dispatcher.js.map +1 -1
  120. package/dist/events/registry.js +4 -0
  121. package/dist/events/registry.js.map +1 -1
  122. package/dist/hosting/durability.js +178 -0
  123. package/dist/hosting/durability.js.map +1 -0
  124. package/dist/hosting/envelope.js +146 -18
  125. package/dist/hosting/envelope.js.map +1 -1
  126. package/dist/hosting/errors.js +95 -19
  127. package/dist/hosting/errors.js.map +1 -1
  128. package/dist/hosting/httpHost.js +42 -6
  129. package/dist/hosting/httpHost.js.map +1 -1
  130. package/dist/hosting/index.js +10 -2
  131. package/dist/hosting/index.js.map +1 -1
  132. package/dist/hosting/nodeHost.js +14 -3
  133. package/dist/hosting/nodeHost.js.map +1 -1
  134. package/dist/hosting/standingAgent.js +142 -30
  135. package/dist/hosting/standingAgent.js.map +1 -1
  136. package/dist/index.js +31 -14
  137. package/dist/index.js.map +1 -1
  138. package/dist/lib/mcp/mcpServe.js +39 -6
  139. package/dist/lib/mcp/mcpServe.js.map +1 -1
  140. package/dist/types/adapters/hosting/agentcore.d.ts +4 -4
  141. package/dist/types/adapters/hosting/agentcore.d.ts.map +1 -1
  142. package/dist/types/core/Agent.d.ts +9 -1
  143. package/dist/types/core/Agent.d.ts.map +1 -1
  144. package/dist/types/core/RunnerBase.d.ts.map +1 -1
  145. package/dist/types/core/agent/AgentBuilder.d.ts +97 -0
  146. package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
  147. package/dist/types/core/agent/buildAgentChart.d.ts +2 -2
  148. package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -1
  149. package/dist/types/core/agent/middleware/errors.d.ts +52 -0
  150. package/dist/types/core/agent/middleware/errors.d.ts.map +1 -0
  151. package/dist/types/core/agent/middleware/index.d.ts +17 -0
  152. package/dist/types/core/agent/middleware/index.d.ts.map +1 -0
  153. package/dist/types/core/agent/middleware/ledger.d.ts +41 -0
  154. package/dist/types/core/agent/middleware/ledger.d.ts.map +1 -0
  155. package/dist/types/core/agent/middleware/outcomes.d.ts +46 -0
  156. package/dist/types/core/agent/middleware/outcomes.d.ts.map +1 -0
  157. package/dist/types/core/agent/middleware/runChain.d.ts +105 -0
  158. package/dist/types/core/agent/middleware/runChain.d.ts.map +1 -0
  159. package/dist/types/core/agent/middleware/types.d.ts +192 -0
  160. package/dist/types/core/agent/middleware/types.d.ts.map +1 -0
  161. package/dist/types/core/agent/stages/prepareFinal.d.ts +10 -0
  162. package/dist/types/core/agent/stages/prepareFinal.d.ts.map +1 -1
  163. package/dist/types/core/agent/stages/route.d.ts +29 -0
  164. package/dist/types/core/agent/stages/route.d.ts.map +1 -1
  165. package/dist/types/core/agent/stages/seed.d.ts +16 -1
  166. package/dist/types/core/agent/stages/seed.d.ts.map +1 -1
  167. package/dist/types/core/agent/stages/toolCalls.d.ts +44 -0
  168. package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
  169. package/dist/types/core/agent/types.d.ts +18 -0
  170. package/dist/types/core/agent/types.d.ts.map +1 -1
  171. package/dist/types/core/durabilityBarrier.d.ts +62 -0
  172. package/dist/types/core/durabilityBarrier.d.ts.map +1 -0
  173. package/dist/types/core/pause.d.ts +42 -0
  174. package/dist/types/core/pause.d.ts.map +1 -1
  175. package/dist/types/events/dispatcher.d.ts +1 -1
  176. package/dist/types/events/dispatcher.d.ts.map +1 -1
  177. package/dist/types/events/payloads.d.ts +26 -0
  178. package/dist/types/events/payloads.d.ts.map +1 -1
  179. package/dist/types/events/registry.d.ts +5 -1
  180. package/dist/types/events/registry.d.ts.map +1 -1
  181. package/dist/types/hosting/durability.d.ts +93 -0
  182. package/dist/types/hosting/durability.d.ts.map +1 -0
  183. package/dist/types/hosting/envelope.d.ts +75 -14
  184. package/dist/types/hosting/envelope.d.ts.map +1 -1
  185. package/dist/types/hosting/errors.d.ts +60 -11
  186. package/dist/types/hosting/errors.d.ts.map +1 -1
  187. package/dist/types/hosting/httpHost.d.ts +17 -1
  188. package/dist/types/hosting/httpHost.d.ts.map +1 -1
  189. package/dist/types/hosting/index.d.ts +7 -4
  190. package/dist/types/hosting/index.d.ts.map +1 -1
  191. package/dist/types/hosting/nodeHost.d.ts +4 -2
  192. package/dist/types/hosting/nodeHost.d.ts.map +1 -1
  193. package/dist/types/hosting/standingAgent.d.ts +22 -7
  194. package/dist/types/hosting/standingAgent.d.ts.map +1 -1
  195. package/dist/types/hosting/types.d.ts +193 -19
  196. package/dist/types/hosting/types.d.ts.map +1 -1
  197. package/dist/types/index.d.ts +2 -1
  198. package/dist/types/index.d.ts.map +1 -1
  199. package/dist/types/lib/mcp/mcpServe.d.ts.map +1 -1
  200. package/dist/types/lib/mcp/types.d.ts +16 -0
  201. package/dist/types/lib/mcp/types.d.ts.map +1 -1
  202. package/package.json +1 -1
@@ -20,100 +20,145 @@
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.buildSeedStage = void 0;
22
22
  const typedEmit_js_1 = require("../../../recorders/core/typedEmit.js");
23
+ const runChain_js_1 = require("../middleware/runChain.js");
24
+ const ledger_js_1 = require("../middleware/ledger.js");
23
25
  /**
24
26
  * Build the seed stage function for an Agent instance. Captures both
25
27
  * the chart-build-time constants and the per-run mutable accessors
26
28
  * via the deps object.
27
29
  */
28
30
  function buildSeedStage(deps) {
29
- return (scope) => {
30
- const args = scope.$getArgs();
31
- scope.userMessage = args.message;
32
- // If `resumeOnError(...)` set the side channel, restore the
33
- // checkpointed conversation history. The next iteration sees
34
- // the prior messages and continues from the failure point.
35
- // Always clear the field after reading so subsequent runs
36
- // (without resumeOnError) start fresh.
37
- const resumeHistory = deps.consumePendingResumeHistory();
38
- if (resumeHistory && resumeHistory.length > 0) {
39
- scope.history = [...resumeHistory];
40
- }
41
- else {
42
- scope.history = [{ role: 'user', content: args.message }];
43
- }
44
- // Default identity uses the runId so multi-run isolation works
45
- // without consumer changes; explicit identity (multi-tenant)
46
- // overrides via `agent.run({ identity })`.
47
- scope.runIdentity = args.identity ?? {
48
- conversationId: deps.getCurrentRunId() ?? 'default',
31
+ const chain = deps.messageMiddleware ?? [];
32
+ // No chain → the same synchronous function this stage has always been.
33
+ // Not an optimisation: an agent without middleware must produce the same
34
+ // stage shape, the same committed keys and the same request bytes as before.
35
+ if (chain.length === 0) {
36
+ return (scope) => {
37
+ seedFrom(scope, scope.$getArgs().message, deps);
49
38
  };
50
- scope.newMessages = [];
51
- scope.turnNumber = 1;
52
- // Permissive default — explicit cap will land when PricingTable
53
- // gets a context-window field. Memory pickByBudget treats anything
54
- // ≥ minimumTokens as "fits", so this just enables the budget path.
55
- scope.contextTokensRemaining = 32_000;
56
- scope.iteration = 1;
57
- scope.maxIterations = deps.maxIterations;
58
- scope.finalContent = '';
59
- scope.totalInputTokens = 0;
60
- scope.totalOutputTokens = 0;
61
- scope.turnStartMs = Date.now();
62
- scope.systemPromptInjections = [];
63
- scope.messagesInjections = [];
64
- scope.toolsInjections = [];
65
- scope.llmLatestContent = '';
66
- scope.llmLatestToolCalls = [];
67
- // v2.14 — initialize thinking blocks. Empty array means "no thinking
68
- // this iteration"; the NormalizeThinking sub-subflow overwrites
69
- // this AFTER each CallLLM when a ThinkingHandler is configured.
70
- scope.thinkingBlocks = [];
71
- scope.pausedToolCallId = '';
72
- scope.pausedToolName = '';
73
- scope.pausedToolStartMs = 0;
74
- scope.cumTokensInput = 0;
75
- scope.cumTokensOutput = 0;
76
- scope.cumEstimatedUsd = 0;
77
- scope.costBudgetHit = false;
78
- scope.activeInjections = [];
79
- scope.activatedInjectionIds = [];
80
- scope.dynamicToolSchemas = deps.toolSchemas;
81
- // Cache layer state (v2.6) — initialized to inert defaults.
82
- // CacheDecision subflow populates `cacheMarkers` per iteration;
83
- // UpdateSkillHistory + CacheGate consume `cachingDisabled`,
84
- // `recentHitRate`, `skillHistory`. Empty defaults mean the
85
- // CacheGate falls through to 'apply-markers' on iter 1 (no
86
- // history yet → no churn detected; recentHitRate undefined →
87
- // hit-rate floor doesn't fire).
88
- scope.cacheMarkers = [];
89
- scope.cachingDisabled = deps.cachingDisabled;
90
- scope.recentHitRate = undefined;
91
- scope.skillHistory = [];
92
- // Skill-graph cursor — reset per turn so each new user message re-enters the
93
- // graph through the entry router (cold start). The Injection Engine advances
94
- // it each iteration; undefined for agents without a skillGraph().
95
- scope.currentSkillId = undefined;
96
- // `.configure()` — resolved ONCE here (seed runs exactly once per run)
97
- // and written to scope, which means the run's commit log records the
98
- // model and instructions the run actually used. A run that changed its
99
- // own model without committing that fact would produce a trace that
100
- // reads as if the built-in default answered.
101
- //
102
- // Only what the resolver actually returned is written: an agent with no
103
- // `.configure()`, or one whose resolver returned `{}`, commits nothing
104
- // extra and behaves exactly as before.
105
- if (deps.resolveRunConfig) {
106
- const resolved = deps.resolveRunConfig(args);
107
- if (resolved?.model !== undefined)
108
- scope.resolvedModel = resolved.model;
109
- if (resolved?.instructions !== undefined)
110
- scope.resolvedInstructions = resolved.instructions;
111
- }
112
- (0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.agent.turn_start', {
113
- turnIndex: 0,
114
- userPrompt: args.message,
39
+ }
40
+ return async (scope) => {
41
+ const args = scope.$getArgs();
42
+ const verdict = await (0, runChain_js_1.runMessageChain)(chain, {
43
+ phase: 'input',
44
+ content: args.message,
45
+ history: [],
46
+ // The input boundary runs before iteration 1 exists.
47
+ iteration: 0,
48
+ ...(args.identity && { identity: args.identity }),
115
49
  });
50
+ (0, ledger_js_1.recordDecisions)(scope, verdict.decisions);
51
+ if (verdict.kind === 'deny') {
52
+ // Seed the run anyway, with the content as it stood when it was
53
+ // refused, then stop. Committing it costs nothing (a refusal is a fact
54
+ // about a run, and hiding what was refused would make the record
55
+ // useless), and a fully-seeded state means `resumeOnError` and every
56
+ // recorder see the shape they expect rather than a half-built one.
57
+ seedFrom(scope, verdict.content, deps);
58
+ scope.messageDeniedReason = verdict.reason;
59
+ scope.messageDeniedPhase = 'input';
60
+ scope.messageDeniedBy = verdict.middleware;
61
+ // Stops the chart here: no injections, no slots, no LLM call. The
62
+ // boundary turns these flags into a MessageDeniedError.
63
+ scope.$break(`message denied at input: ${verdict.reason}`);
64
+ return;
65
+ }
66
+ seedFrom(scope, verdict.content, deps);
116
67
  };
117
68
  }
118
69
  exports.buildSeedStage = buildSeedStage;
70
+ /**
71
+ * Initialise every mutable field of `AgentState` from `message` + the run
72
+ * args. Split out so the message the run proceeds with can come either
73
+ * straight from the caller or from the `'input'` middleware chain — one
74
+ * initialiser, so the two paths cannot drift.
75
+ */
76
+ function seedFrom(scope, message, deps) {
77
+ const args = scope.$getArgs();
78
+ scope.userMessage = message;
79
+ // If `resumeOnError(...)` set the side channel, restore the
80
+ // checkpointed conversation history. The next iteration sees
81
+ // the prior messages and continues from the failure point.
82
+ // Always clear the field after reading so subsequent runs
83
+ // (without resumeOnError) start fresh.
84
+ const resumeHistory = deps.consumePendingResumeHistory();
85
+ if (resumeHistory && resumeHistory.length > 0) {
86
+ scope.history = [...resumeHistory];
87
+ }
88
+ else {
89
+ scope.history = [{ role: 'user', content: message }];
90
+ }
91
+ // Default identity uses the runId so multi-run isolation works
92
+ // without consumer changes; explicit identity (multi-tenant)
93
+ // overrides via `agent.run({ identity })`.
94
+ scope.runIdentity = args.identity ?? {
95
+ conversationId: deps.getCurrentRunId() ?? 'default',
96
+ };
97
+ scope.newMessages = [];
98
+ scope.turnNumber = 1;
99
+ // Permissive default — explicit cap will land when PricingTable
100
+ // gets a context-window field. Memory pickByBudget treats anything
101
+ // ≥ minimumTokens as "fits", so this just enables the budget path.
102
+ scope.contextTokensRemaining = 32_000;
103
+ scope.iteration = 1;
104
+ scope.maxIterations = deps.maxIterations;
105
+ scope.finalContent = '';
106
+ scope.totalInputTokens = 0;
107
+ scope.totalOutputTokens = 0;
108
+ scope.turnStartMs = Date.now();
109
+ scope.systemPromptInjections = [];
110
+ scope.messagesInjections = [];
111
+ scope.toolsInjections = [];
112
+ scope.llmLatestContent = '';
113
+ scope.llmLatestToolCalls = [];
114
+ // v2.14 — initialize thinking blocks. Empty array means "no thinking
115
+ // this iteration"; the NormalizeThinking sub-subflow overwrites
116
+ // this AFTER each CallLLM when a ThinkingHandler is configured.
117
+ scope.thinkingBlocks = [];
118
+ scope.pausedToolCallId = '';
119
+ scope.pausedToolName = '';
120
+ scope.pausedToolStartMs = 0;
121
+ scope.cumTokensInput = 0;
122
+ scope.cumTokensOutput = 0;
123
+ scope.cumEstimatedUsd = 0;
124
+ scope.costBudgetHit = false;
125
+ scope.activeInjections = [];
126
+ scope.activatedInjectionIds = [];
127
+ scope.dynamicToolSchemas = deps.toolSchemas;
128
+ // Cache layer state (v2.6) — initialized to inert defaults.
129
+ // CacheDecision subflow populates `cacheMarkers` per iteration;
130
+ // UpdateSkillHistory + CacheGate consume `cachingDisabled`,
131
+ // `recentHitRate`, `skillHistory`. Empty defaults mean the
132
+ // CacheGate falls through to 'apply-markers' on iter 1 (no
133
+ // history yet → no churn detected; recentHitRate undefined →
134
+ // hit-rate floor doesn't fire).
135
+ scope.cacheMarkers = [];
136
+ scope.cachingDisabled = deps.cachingDisabled;
137
+ scope.recentHitRate = undefined;
138
+ scope.skillHistory = [];
139
+ // Skill-graph cursor — reset per turn so each new user message re-enters the
140
+ // graph through the entry router (cold start). The Injection Engine advances
141
+ // it each iteration; undefined for agents without a skillGraph().
142
+ scope.currentSkillId = undefined;
143
+ // `.configure()` — resolved ONCE here (seed runs exactly once per run)
144
+ // and written to scope, which means the run's commit log records the
145
+ // model and instructions the run actually used. A run that changed its
146
+ // own model without committing that fact would produce a trace that
147
+ // reads as if the built-in default answered.
148
+ //
149
+ // Only what the resolver actually returned is written: an agent with no
150
+ // `.configure()`, or one whose resolver returned `{}`, commits nothing
151
+ // extra and behaves exactly as before.
152
+ if (deps.resolveRunConfig) {
153
+ const resolved = deps.resolveRunConfig(args);
154
+ if (resolved?.model !== undefined)
155
+ scope.resolvedModel = resolved.model;
156
+ if (resolved?.instructions !== undefined)
157
+ scope.resolvedInstructions = resolved.instructions;
158
+ }
159
+ (0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.agent.turn_start', {
160
+ turnIndex: 0,
161
+ userPrompt: message,
162
+ });
163
+ }
119
164
  //# sourceMappingURL=seed.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"seed.js","sourceRoot":"","sources":["../../../../src/core/agent/stages/seed.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"seed.js","sourceRoot":"","sources":["../../../../src/core/agent/stages/seed.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAIH,uEAAiE;AAGjE,2DAA4D;AAC5D,uDAA0D;AAoD1D;;;;GAIG;AACH,SAAgB,cAAc,CAC5B,IAAmB;IAEnB,MAAM,KAAK,GAAG,IAAI,CAAC,iBAAiB,IAAI,EAAE,CAAC;IAC3C,uEAAuE;IACvE,yEAAyE;IACzE,6EAA6E;IAC7E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,KAAK,EAAE,EAAE;YACf,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAc,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC9D,CAAC,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,EAAE,KAAK,EAAE,EAAE;QACrB,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAc,CAAC;QAC1C,MAAM,OAAO,GAAG,MAAM,IAAA,6BAAe,EAAC,KAAK,EAAE;YAC3C,KAAK,EAAE,OAAO;YACd,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,OAAO,EAAE,EAAE;YACX,qDAAqD;YACrD,SAAS,EAAE,CAAC;YACZ,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;SAClD,CAAC,CAAC;QACH,IAAA,2BAAe,EAAC,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC5B,gEAAgE;YAChE,uEAAuE;YACvE,iEAAiE;YACjE,qEAAqE;YACrE,mEAAmE;YACnE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACvC,KAAK,CAAC,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;YAC3C,KAAK,CAAC,kBAAkB,GAAG,OAAO,CAAC;YACnC,KAAK,CAAC,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC;YAC3C,kEAAkE;YAClE,wDAAwD;YACxD,KAAK,CAAC,MAAM,CAAC,4BAA4B,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;YAC3D,OAAO;QACT,CAAC;QACD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACzC,CAAC,CAAC;AACJ,CAAC;AAxCD,wCAwCC;AAED;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,KAA6B,EAAE,OAAe,EAAE,IAAmB;IACnF,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAc,CAAC;IAC1C,KAAK,CAAC,WAAW,GAAG,OAAO,CAAC;IAE5B,4DAA4D;IAC5D,6DAA6D;IAC7D,2DAA2D;IAC3D,0DAA0D;IAC1D,uCAAuC;IACvC,MAAM,aAAa,GAAG,IAAI,CAAC,2BAA2B,EAAE,CAAC;IACzD,IAAI,aAAa,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9C,KAAK,CAAC,OAAO,GAAG,CAAC,GAAG,aAAa,CAAC,CAAC;IACrC,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,OAAO,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,+DAA+D;IAC/D,6DAA6D;IAC7D,2CAA2C;IAC3C,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,QAAQ,IAAI;QACnC,cAAc,EAAE,IAAI,CAAC,eAAe,EAAE,IAAI,SAAS;KACpD,CAAC;IACF,KAAK,CAAC,WAAW,GAAG,EAAE,CAAC;IACvB,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC;IACrB,gEAAgE;IAChE,mEAAmE;IACnE,mEAAmE;IACnE,KAAK,CAAC,sBAAsB,GAAG,MAAM,CAAC;IACtC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;IACpB,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC;IACzC,KAAK,CAAC,YAAY,GAAG,EAAE,CAAC;IACxB,KAAK,CAAC,gBAAgB,GAAG,CAAC,CAAC;IAC3B,KAAK,CAAC,iBAAiB,GAAG,CAAC,CAAC;IAC5B,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC/B,KAAK,CAAC,sBAAsB,GAAG,EAAE,CAAC;IAClC,KAAK,CAAC,kBAAkB,GAAG,EAAE,CAAC;IAC9B,KAAK,CAAC,eAAe,GAAG,EAAE,CAAC;IAC3B,KAAK,CAAC,gBAAgB,GAAG,EAAE,CAAC;IAC5B,KAAK,CAAC,kBAAkB,GAAG,EAAE,CAAC;IAC9B,qEAAqE;IACrE,gEAAgE;IAChE,gEAAgE;IAChE,KAAK,CAAC,cAAc,GAAG,EAAE,CAAC;IAC1B,KAAK,CAAC,gBAAgB,GAAG,EAAE,CAAC;IAC5B,KAAK,CAAC,cAAc,GAAG,EAAE,CAAC;IAC1B,KAAK,CAAC,iBAAiB,GAAG,CAAC,CAAC;IAC5B,KAAK,CAAC,cAAc,GAAG,CAAC,CAAC;IACzB,KAAK,CAAC,eAAe,GAAG,CAAC,CAAC;IAC1B,KAAK,CAAC,eAAe,GAAG,CAAC,CAAC;IAC1B,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC;IAC5B,KAAK,CAAC,gBAAgB,GAAG,EAAE,CAAC;IAC5B,KAAK,CAAC,qBAAqB,GAAG,EAAE,CAAC;IACjC,KAAK,CAAC,kBAAkB,GAAG,IAAI,CAAC,WAAW,CAAC;IAC5C,4DAA4D;IAC5D,gEAAgE;IAChE,4DAA4D;IAC5D,2DAA2D;IAC3D,2DAA2D;IAC3D,6DAA6D;IAC7D,gCAAgC;IAChC,KAAK,CAAC,YAAY,GAAG,EAAE,CAAC;IACxB,KAAK,CAAC,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC;IAC7C,KAAK,CAAC,aAAa,GAAG,SAAS,CAAC;IAChC,KAAK,CAAC,YAAY,GAAG,EAAE,CAAC;IACxB,6EAA6E;IAC7E,6EAA6E;IAC7E,kEAAkE;IAClE,KAAK,CAAC,cAAc,GAAG,SAAS,CAAC;IAEjC,uEAAuE;IACvE,qEAAqE;IACrE,uEAAuE;IACvE,oEAAoE;IACpE,6CAA6C;IAC7C,EAAE;IACF,wEAAwE;IACxE,uEAAuE;IACvE,uCAAuC;IACvC,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,QAAQ,EAAE,KAAK,KAAK,SAAS;YAAE,KAAK,CAAC,aAAa,GAAG,QAAQ,CAAC,KAAK,CAAC;QACxE,IAAI,QAAQ,EAAE,YAAY,KAAK,SAAS;YAAE,KAAK,CAAC,oBAAoB,GAAG,QAAQ,CAAC,YAAY,CAAC;IAC/F,CAAC;IAED,IAAA,wBAAS,EAAC,KAAK,EAAE,iCAAiC,EAAE;QAClD,SAAS,EAAE,CAAC;QACZ,UAAU,EAAE,OAAO;KACpB,CAAC,CAAC;AACL,CAAC"}
@@ -21,6 +21,19 @@
21
21
  * `tool.execute`. Deny → tool not executed; result is a synthetic
22
22
  * denial string. Allow / gate_open → execution proceeds.
23
23
  *
24
+ * Gate order for one call, and why it is this order:
25
+ *
26
+ * permission → MIDDLEWARE CHAIN → arg validation → check-in →
27
+ * credentials → execute
28
+ *
29
+ * The chain sits after the permission gate so an existing checker still
30
+ * decides first (a denial there means no middleware runs), and before arg
31
+ * validation so validation judges the args that will actually be sent —
32
+ * a middleware that transformed args into something the tool's schema
33
+ * rejects must be caught, not forwarded. A middleware answering `ask`
34
+ * pauses on the SAME wire `checkIn` uses; the human's answer is a
35
+ * decision, not a result, so the chain resumes and the REAL tool runs.
36
+ *
24
37
  * `read_skill` is the auto-attached activation tool — when the LLM
25
38
  * calls it with a valid Skill id, the next InjectionEngine pass
26
39
  * activates that Skill (lifetime: turn).
@@ -32,6 +45,8 @@ const extractSequence_js_1 = require("../../../security/extractSequence.js");
32
45
  const types_js_1 = require("../../../identity/types.js");
33
46
  const pause_js_1 = require("../../pause.js");
34
47
  const checkin_js_1 = require("../../checkin.js");
48
+ const runChain_js_1 = require("../middleware/runChain.js");
49
+ const ledger_js_1 = require("../middleware/ledger.js");
35
50
  const toolArgsValidation_js_1 = require("../toolArgsValidation.js");
36
51
  const validators_js_1 = require("../validators.js");
37
52
  /**
@@ -136,6 +151,13 @@ function buildToolCallsHandler(deps) {
136
151
  };
137
152
  return {
138
153
  execute: async (scope) => {
154
+ // Durable-write barrier — the LAST iteration's state must have landed in
155
+ // the session store before THIS iteration's tools are allowed to run.
156
+ // `undefined` (no composer, or nothing outstanding) means no await, no
157
+ // microtask, no behaviour change whatsoever.
158
+ const durable = deps.awaitDurable?.();
159
+ if (durable)
160
+ await durable;
139
161
  // Materialize ONCE — `scope.llmLatestToolCalls` is a live TypedScope
140
162
  // deep-Proxy view; spreading yields the raw (plain, structured-clone-
141
163
  // safe) elements. This array is embedded into the assistant history
@@ -205,6 +227,12 @@ function buildToolCallsHandler(deps) {
205
227
  // identity, and abort signal — enough surface to build sequence-
206
228
  // aware policies (forbidden chains, idempotency limits, cost
207
229
  // guards) without maintaining parallel state.
230
+ // Args as they will actually be used. The middleware chain below may
231
+ // replace this; everything downstream (validation, the check-in
232
+ // evidence a human approves, credentials, execute, the read_skill
233
+ // gate) reads `callArgs`, never `tc.args`, so there is exactly one
234
+ // answer to "what did this call really run with".
235
+ let callArgs = tc.args;
208
236
  let denied = false;
209
237
  let haltContext;
210
238
  if (permissionChecker) {
@@ -271,6 +299,52 @@ function buildToolCallsHandler(deps) {
271
299
  result = `[permission denied: checker error: ${msg}]`;
272
300
  }
273
301
  }
302
+ // ── The middleware chain ─────────────────────────────────────────
303
+ // Walked only for a call the permission gate let through, so an
304
+ // existing checker keeps deciding first and a denial there costs
305
+ // nothing. A denial from the chain lands as the tool result, exactly
306
+ // like every other refusal in this loop — the model reads it and
307
+ // adapts. An `ask` commits partial state and pauses, on the same wire
308
+ // the check-in gate uses.
309
+ if (!denied && deps.toolMiddleware && deps.toolMiddleware.length > 0) {
310
+ const chain = await (0, runChain_js_1.runToolChain)(deps.toolMiddleware, {
311
+ toolName: tc.name,
312
+ toolCallId: tc.id,
313
+ iteration,
314
+ args: callArgs,
315
+ history: newHistory,
316
+ ...(runIdentity && { identity: runIdentity }),
317
+ ...(env.signal && { signal: env.signal }),
318
+ });
319
+ (0, ledger_js_1.recordDecisions)(scope, chain.decisions);
320
+ callArgs = chain.args;
321
+ if (chain.kind === 'deny') {
322
+ denied = true;
323
+ result = chain.reason;
324
+ }
325
+ else if (chain.kind === 'ask') {
326
+ // Commit partial state so resume() finds history intact (the
327
+ // pauseHere / check-in path does the same). The TRANSFORMED args
328
+ // ride the checkpoint: a person approves what the chain produced,
329
+ // not what the model originally proposed.
330
+ scope.history = newHistory;
331
+ scope.pausedToolCallId = tc.id;
332
+ scope.pausedToolName = tc.name;
333
+ scope.pausedToolStartMs = startMs;
334
+ scope.pausedAsk = true;
335
+ scope.pausedAskArgs = chain.args;
336
+ scope.pausedAskIndex = chain.index;
337
+ scope.pausedAskMiddleware = chain.middleware;
338
+ // A defined return value triggers the footprintjs pause; this
339
+ // object becomes the checkpoint's pauseData, and detectPause
340
+ // surfaces `pauseData.ask` as `outcome.ask`.
341
+ return {
342
+ toolCallId: tc.id,
343
+ toolName: tc.name,
344
+ ask: { ...chain.payload, middleware: chain.middleware },
345
+ };
346
+ }
347
+ }
274
348
  // Tool-args validation (#9) — AFTER the permission gate (policy must
275
349
  // see every attempted call, valid or not) and BEFORE credential
276
350
  // resolution (never acquire credentials for a call that won't run).
@@ -281,7 +355,7 @@ function buildToolCallsHandler(deps) {
281
355
  // tools (their inputSchema is the contract the LLM was shown).
282
356
  let argsRejected = false;
283
357
  if (!denied && tool && toolArgValidation !== 'off') {
284
- const verdict = (0, toolArgsValidation_js_1.validateToolArgs)(tc.args, tool.schema.inputSchema);
358
+ const verdict = (0, toolArgsValidation_js_1.validateToolArgs)(callArgs, tool.schema.inputSchema);
285
359
  if (!verdict.ok) {
286
360
  (0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.validation.args_invalid', {
287
361
  toolName: tc.name,
@@ -319,7 +393,7 @@ function buildToolCallsHandler(deps) {
319
393
  const historyForEvidence = systemPrompt
320
394
  ? [{ role: 'system', content: systemPrompt }, ...newHistory]
321
395
  : newHistory;
322
- if (!(0, checkin_js_1.shouldCheckIn)(tool.checkIn, tc.args, {
396
+ if (!(0, checkin_js_1.shouldCheckIn)(tool.checkIn, callArgs, {
323
397
  iteration,
324
398
  toolCallId: tc.id,
325
399
  history: historyForEvidence,
@@ -331,7 +405,7 @@ function buildToolCallsHandler(deps) {
331
405
  const intent = scope.llmLatestContent ? String(scope.llmLatestContent) : undefined;
332
406
  const evidence = await deps.checkIn.assembler({
333
407
  tool: { name: tc.name, description: tool.schema.description },
334
- args: tc.args,
408
+ args: callArgs,
335
409
  ...(intent !== undefined && { intent }),
336
410
  iteration,
337
411
  history: historyForEvidence,
@@ -340,7 +414,7 @@ function buildToolCallsHandler(deps) {
340
414
  });
341
415
  const request = {
342
416
  tool: tc.name,
343
- args: tc.args,
417
+ args: callArgs,
344
418
  ...(intent !== undefined && { intent }),
345
419
  evidence,
346
420
  };
@@ -358,7 +432,7 @@ function buildToolCallsHandler(deps) {
358
432
  scope.pausedToolName = tc.name;
359
433
  scope.pausedToolStartMs = startMs;
360
434
  scope.pausedCheckIn = true;
361
- scope.pausedCheckInArgs = tc.args;
435
+ scope.pausedCheckInArgs = callArgs;
362
436
  // Returning a defined value triggers the footprintjs pause; the
363
437
  // returned object becomes the checkpoint's pauseData. detectPause
364
438
  // surfaces `pauseData.checkIn` as `outcome.checkIn`.
@@ -423,7 +497,7 @@ function buildToolCallsHandler(deps) {
423
497
  try {
424
498
  if (!tool)
425
499
  throw new Error(`Unknown tool: ${tc.name}`);
426
- result = await tool.execute(tc.args, {
500
+ result = await tool.execute(callArgs, {
427
501
  toolCallId: tc.id,
428
502
  iteration,
429
503
  ...(env.signal && { signal: env.signal }),
@@ -462,7 +536,7 @@ function buildToolCallsHandler(deps) {
462
536
  // so plain read_skill agents are byte-for-byte unaffected.
463
537
  let skillRejected = false;
464
538
  if (deps.allowedSkillIds && tc.name === 'read_skill' && !error && !denied) {
465
- const reqId = tc.args.id;
539
+ const reqId = callArgs.id;
466
540
  if (typeof reqId === 'string' && reqId.length > 0) {
467
541
  const currentSkillId = scope.currentSkillId;
468
542
  const allowed = deps.allowedSkillIds(currentSkillId);
@@ -508,7 +582,7 @@ function buildToolCallsHandler(deps) {
508
582
  // NEXT pass activates that Skill (lifetime: turn — stays
509
583
  // active until the turn ends).
510
584
  if (tc.name === 'read_skill' && !error && !denied && !skillRejected) {
511
- const requestedId = tc.args.id;
585
+ const requestedId = callArgs.id;
512
586
  if (typeof requestedId === 'string' && requestedId.length > 0) {
513
587
  const current = scope.activatedInjectionIds;
514
588
  if (!current.includes(requestedId)) {
@@ -562,6 +636,12 @@ function buildToolCallsHandler(deps) {
562
636
  return undefined; // explicit: no pause, flow continues to loopTo
563
637
  },
564
638
  resume: async (scope, input) => {
639
+ // Same barrier as `execute`, for the same reason: an approved check-in
640
+ // runs the REAL tool here, and that side effect must not go out ahead of
641
+ // the state the last write is still carrying.
642
+ const durable = deps.awaitDurable?.();
643
+ if (durable)
644
+ await durable;
565
645
  // Consumer-supplied resume input becomes the paused tool's result.
566
646
  // The subflow's pre-pause scope is restored automatically by
567
647
  // footprintjs 4.17.0 via `checkpoint.subflowStates`, so
@@ -570,6 +650,126 @@ function buildToolCallsHandler(deps) {
570
650
  const toolCallId = scope.pausedToolCallId;
571
651
  const toolName = scope.pausedToolName;
572
652
  const startMs = scope.pausedToolStartMs;
653
+ // ── Middleware-ask decision path ─────────────────────────────────
654
+ // Discriminated by `scope.pausedAsk`, restored from the checkpoint.
655
+ //
656
+ // The answer is a DECISION, not a result. That is the whole reason the
657
+ // outcome union has no `result` arm: a middleware asks a person whether
658
+ // this call may proceed, and on approval the REAL tool runs — the person
659
+ // never writes the tool's answer, and neither does the middleware.
660
+ //
661
+ // A malformed resume DECLINES, for the same reason the check-in path
662
+ // does: a governed call must never execute because a message was
663
+ // mis-shaped.
664
+ if (scope.pausedAsk === true) {
665
+ const iteration = scope.iteration;
666
+ const args = (scope.pausedAskArgs ?? {});
667
+ const askIndex = (scope.pausedAskIndex ?? 0);
668
+ const askedBy = (scope.pausedAskMiddleware ?? 'middleware');
669
+ const decision = (0, checkin_js_1.isCheckInDecision)(input)
670
+ ? input
671
+ : (0, checkin_js_1.checkInDeclined)({ by: 'unknown', note: 'resume input was not a CheckInDecision' });
672
+ let result;
673
+ let error;
674
+ if (!decision.approved) {
675
+ result = decision.note ? `declined by human: ${decision.note}` : 'declined by human';
676
+ (0, ledger_js_1.recordDecisions)(scope, [
677
+ {
678
+ middleware: askedBy,
679
+ at: 'tool',
680
+ toolName,
681
+ toolCallId,
682
+ iteration,
683
+ outcome: 'deny',
684
+ changed: false,
685
+ why: `declined by ${decision.by}${decision.note ? `: ${decision.note}` : ''}`,
686
+ },
687
+ ]);
688
+ }
689
+ else {
690
+ (0, ledger_js_1.recordDecisions)(scope, [
691
+ {
692
+ middleware: askedBy,
693
+ at: 'tool',
694
+ toolName,
695
+ toolCallId,
696
+ iteration,
697
+ outcome: 'allow',
698
+ changed: false,
699
+ why: `approved by ${decision.by}${decision.note ? `: ${decision.note}` : ''}`,
700
+ },
701
+ ]);
702
+ // Continue the chain from the link AFTER the one that asked. Its
703
+ // decision is already on the checkpoint; re-running it would ask the
704
+ // same question twice and file a duplicate row.
705
+ //
706
+ // `askPolicy: 'refuse'` because footprintjs's `PausableHandler.resume`
707
+ // returns void — a resumed dispatch has no second checkpoint to give.
708
+ // A link further down the chain that also wants a person gets a named,
709
+ // model-visible refusal and the tool does NOT run. That is the same
710
+ // rule already applied to a tool that tries to pause during an
711
+ // approved check-in resume: at most one human question per resume.
712
+ const rest = await (0, runChain_js_1.runToolChain)(deps.toolMiddleware ?? [], {
713
+ toolName,
714
+ toolCallId,
715
+ iteration,
716
+ args,
717
+ history: [...scope.history],
718
+ startIndex: askIndex + 1,
719
+ askPolicy: 'refuse',
720
+ });
721
+ (0, ledger_js_1.recordDecisions)(scope, rest.decisions);
722
+ const tool = lookupTool(toolName);
723
+ if (rest.kind === 'deny') {
724
+ result = rest.reason;
725
+ }
726
+ else if (tool?.checkIn !== undefined) {
727
+ // Same one-question rule, from the other direction: this tool also
728
+ // demands consent, and there is no checkpoint left to ask with.
729
+ // Refusing loudly beats executing a tool whose consent gate we
730
+ // silently skipped.
731
+ error = true;
732
+ result =
733
+ `tool '${toolName}' also declares checkIn, and a resumed dispatch cannot pause ` +
734
+ `again to ask a second time. The call was not executed — approve it through one ` +
735
+ `gate, not both.`;
736
+ }
737
+ else {
738
+ const env = scope.$getEnv();
739
+ const dispatched = await resolveCredentialAndExecute(scope, tool, toolName, rest.args, toolCallId, iteration, env);
740
+ result = dispatched.result;
741
+ error = dispatched.error;
742
+ }
743
+ }
744
+ const askResultStr = typeof result === 'string' ? result : (0, validators_js_1.safeStringify)(result);
745
+ const askHistory = [
746
+ ...scope.history,
747
+ { role: 'tool', content: askResultStr, toolCallId, toolName },
748
+ ];
749
+ scope.history = askHistory;
750
+ scope.lastToolResult = { toolName, result: askResultStr };
751
+ (0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.stream.tool_end', {
752
+ toolCallId,
753
+ result,
754
+ durationMs: Date.now() - startMs,
755
+ ...(error === true && { error: true }),
756
+ });
757
+ (0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.agent.iteration_end', {
758
+ turnIndex: 0,
759
+ iterIndex: iteration,
760
+ toolCallCount: 1,
761
+ history: askHistory,
762
+ });
763
+ scope.iteration = iteration + 1;
764
+ scope.pausedToolCallId = '';
765
+ scope.pausedToolName = '';
766
+ scope.pausedToolStartMs = 0;
767
+ scope.pausedAsk = false;
768
+ scope.pausedAskArgs = undefined;
769
+ scope.pausedAskIndex = undefined;
770
+ scope.pausedAskMiddleware = undefined;
771
+ return;
772
+ }
573
773
  // ── Check-in decision path ───────────────────────────────────────
574
774
  // A check-in pause is discriminated by `scope.pausedCheckIn` (restored
575
775
  // from the checkpoint). The resume input is a `CheckInDecision`. On