agentfootprint 9.119.0 → 9.121.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 (172) hide show
  1. package/AGENTS.md +3 -2
  2. package/CAPABILITIES.md +2 -0
  3. package/README.md +1 -1
  4. package/ai-instructions/claude-code/SKILL.md +3 -2
  5. package/dist/core/Agent.js +132 -8
  6. package/dist/core/Agent.js.map +1 -1
  7. package/dist/core/agent/AgentBuilder.js +25 -3
  8. package/dist/core/agent/AgentBuilder.js.map +1 -1
  9. package/dist/core/agent/assessment/assess.js +411 -0
  10. package/dist/core/agent/assessment/assess.js.map +1 -0
  11. package/dist/core/agent/assessment/reasons.js +94 -0
  12. package/dist/core/agent/assessment/reasons.js.map +1 -0
  13. package/dist/core/agent/assessment/types.js +22 -0
  14. package/dist/core/agent/assessment/types.js.map +1 -0
  15. package/dist/core/agent/buildAgentChart.js +5 -3
  16. package/dist/core/agent/buildAgentChart.js.map +1 -1
  17. package/dist/core/agent/buildDynamicAgentChart.js +5 -3
  18. package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
  19. package/dist/core/agent/coverage/answer.js +68 -2
  20. package/dist/core/agent/coverage/answer.js.map +1 -1
  21. package/dist/core/agent/coverage/emptiness.js +221 -0
  22. package/dist/core/agent/coverage/emptiness.js.map +1 -0
  23. package/dist/core/agent/coverage/index.js +3 -1
  24. package/dist/core/agent/coverage/index.js.map +1 -1
  25. package/dist/core/agent/coverage/ledger.js +7 -20
  26. package/dist/core/agent/coverage/ledger.js.map +1 -1
  27. package/dist/core/agent/coverage/recognize.js +28 -7
  28. package/dist/core/agent/coverage/recognize.js.map +1 -1
  29. package/dist/core/agent/stages/answerCoverage.js +57 -0
  30. package/dist/core/agent/stages/answerCoverage.js.map +1 -0
  31. package/dist/core/agent/stages/prepareFinal.js +45 -15
  32. package/dist/core/agent/stages/prepareFinal.js.map +1 -1
  33. package/dist/esm/core/Agent.d.ts +76 -4
  34. package/dist/esm/core/Agent.js +109 -8
  35. package/dist/esm/core/Agent.js.map +1 -1
  36. package/dist/esm/core/agent/AgentBuilder.d.ts +24 -2
  37. package/dist/esm/core/agent/AgentBuilder.js +25 -3
  38. package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
  39. package/dist/esm/core/agent/assessment/assess.d.ts +83 -0
  40. package/dist/esm/core/agent/assessment/assess.js +407 -0
  41. package/dist/esm/core/agent/assessment/assess.js.map +1 -0
  42. package/dist/esm/core/agent/assessment/reasons.d.ts +38 -0
  43. package/dist/esm/core/agent/assessment/reasons.js +90 -0
  44. package/dist/esm/core/agent/assessment/reasons.js.map +1 -0
  45. package/dist/esm/core/agent/assessment/types.d.ts +166 -0
  46. package/dist/esm/core/agent/assessment/types.js +21 -0
  47. package/dist/esm/core/agent/assessment/types.js.map +1 -0
  48. package/dist/esm/core/agent/buildAgentChart.d.ts +12 -1
  49. package/dist/esm/core/agent/buildAgentChart.js +6 -4
  50. package/dist/esm/core/agent/buildAgentChart.js.map +1 -1
  51. package/dist/esm/core/agent/buildDynamicAgentChart.js +6 -4
  52. package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
  53. package/dist/esm/core/agent/coverage/answer.d.ts +35 -1
  54. package/dist/esm/core/agent/coverage/answer.js +65 -1
  55. package/dist/esm/core/agent/coverage/answer.js.map +1 -1
  56. package/dist/esm/core/agent/coverage/emptiness.d.ts +173 -0
  57. package/dist/esm/core/agent/coverage/emptiness.js +214 -0
  58. package/dist/esm/core/agent/coverage/emptiness.js.map +1 -0
  59. package/dist/esm/core/agent/coverage/index.d.ts +1 -1
  60. package/dist/esm/core/agent/coverage/index.js +1 -1
  61. package/dist/esm/core/agent/coverage/index.js.map +1 -1
  62. package/dist/esm/core/agent/coverage/ledger.d.ts +2 -8
  63. package/dist/esm/core/agent/coverage/ledger.js +5 -18
  64. package/dist/esm/core/agent/coverage/ledger.js.map +1 -1
  65. package/dist/esm/core/agent/coverage/recognize.d.ts +17 -7
  66. package/dist/esm/core/agent/coverage/recognize.js +26 -6
  67. package/dist/esm/core/agent/coverage/recognize.js.map +1 -1
  68. package/dist/esm/core/agent/stages/answerCoverage.d.ts +43 -0
  69. package/dist/esm/core/agent/stages/answerCoverage.js +53 -0
  70. package/dist/esm/core/agent/stages/answerCoverage.js.map +1 -0
  71. package/dist/esm/core/agent/stages/prepareFinal.d.ts +24 -5
  72. package/dist/esm/core/agent/stages/prepareFinal.js +44 -15
  73. package/dist/esm/core/agent/stages/prepareFinal.js.map +1 -1
  74. package/dist/esm/core/agent/types.d.ts +16 -0
  75. package/dist/esm/events/payloads.d.ts +18 -0
  76. package/dist/esm/lib/answer-account/account.js +1 -7
  77. package/dist/esm/lib/answer-account/account.js.map +1 -1
  78. package/dist/esm/lib/answer-account/declarations.js +4 -2
  79. package/dist/esm/lib/answer-account/declarations.js.map +1 -1
  80. package/dist/esm/lib/answer-account/facts/calls.d.ts +7 -1
  81. package/dist/esm/lib/answer-account/facts/calls.js +22 -3
  82. package/dist/esm/lib/answer-account/facts/calls.js.map +1 -1
  83. package/dist/esm/lib/answer-account/facts/common.d.ts +5 -18
  84. package/dist/esm/lib/answer-account/facts/common.js +8 -48
  85. package/dist/esm/lib/answer-account/facts/common.js.map +1 -1
  86. package/dist/esm/lib/answer-account/facts/found.d.ts +6 -4
  87. package/dist/esm/lib/answer-account/facts/found.js +55 -5
  88. package/dist/esm/lib/answer-account/facts/found.js.map +1 -1
  89. package/dist/esm/lib/answer-account/facts/howSure.d.ts +12 -3
  90. package/dist/esm/lib/answer-account/facts/howSure.js +176 -11
  91. package/dist/esm/lib/answer-account/facts/howSure.js.map +1 -1
  92. package/dist/esm/lib/answer-account/facts/inView.js +5 -2
  93. package/dist/esm/lib/answer-account/facts/inView.js.map +1 -1
  94. package/dist/esm/lib/answer-account/shown.js +3 -0
  95. package/dist/esm/lib/answer-account/shown.js.map +1 -1
  96. package/dist/esm/lib/answer-account/templates.d.ts +26 -1
  97. package/dist/esm/lib/answer-account/templates.js +33 -2
  98. package/dist/esm/lib/answer-account/templates.js.map +1 -1
  99. package/dist/esm/lib/answer-account/types.d.ts +31 -3
  100. package/dist/esm/observe.d.ts +2 -0
  101. package/dist/esm/observe.js +7 -0
  102. package/dist/esm/observe.js.map +1 -1
  103. package/dist/lib/answer-account/account.js +1 -7
  104. package/dist/lib/answer-account/account.js.map +1 -1
  105. package/dist/lib/answer-account/declarations.js +4 -2
  106. package/dist/lib/answer-account/declarations.js.map +1 -1
  107. package/dist/lib/answer-account/facts/calls.js +21 -2
  108. package/dist/lib/answer-account/facts/calls.js.map +1 -1
  109. package/dist/lib/answer-account/facts/common.js +7 -52
  110. package/dist/lib/answer-account/facts/common.js.map +1 -1
  111. package/dist/lib/answer-account/facts/found.js +55 -5
  112. package/dist/lib/answer-account/facts/found.js.map +1 -1
  113. package/dist/lib/answer-account/facts/howSure.js +175 -10
  114. package/dist/lib/answer-account/facts/howSure.js.map +1 -1
  115. package/dist/lib/answer-account/facts/inView.js +4 -1
  116. package/dist/lib/answer-account/facts/inView.js.map +1 -1
  117. package/dist/lib/answer-account/shown.js +3 -0
  118. package/dist/lib/answer-account/shown.js.map +1 -1
  119. package/dist/lib/answer-account/templates.js +33 -2
  120. package/dist/lib/answer-account/templates.js.map +1 -1
  121. package/dist/observe.js +10 -2
  122. package/dist/observe.js.map +1 -1
  123. package/dist/types/core/Agent.d.ts +76 -4
  124. package/dist/types/core/Agent.d.ts.map +1 -1
  125. package/dist/types/core/agent/AgentBuilder.d.ts +24 -2
  126. package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
  127. package/dist/types/core/agent/assessment/assess.d.ts +84 -0
  128. package/dist/types/core/agent/assessment/assess.d.ts.map +1 -0
  129. package/dist/types/core/agent/assessment/reasons.d.ts +39 -0
  130. package/dist/types/core/agent/assessment/reasons.d.ts.map +1 -0
  131. package/dist/types/core/agent/assessment/types.d.ts +167 -0
  132. package/dist/types/core/agent/assessment/types.d.ts.map +1 -0
  133. package/dist/types/core/agent/buildAgentChart.d.ts +12 -1
  134. package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -1
  135. package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
  136. package/dist/types/core/agent/coverage/answer.d.ts +35 -1
  137. package/dist/types/core/agent/coverage/answer.d.ts.map +1 -1
  138. package/dist/types/core/agent/coverage/emptiness.d.ts +174 -0
  139. package/dist/types/core/agent/coverage/emptiness.d.ts.map +1 -0
  140. package/dist/types/core/agent/coverage/index.d.ts +1 -1
  141. package/dist/types/core/agent/coverage/index.d.ts.map +1 -1
  142. package/dist/types/core/agent/coverage/ledger.d.ts +2 -8
  143. package/dist/types/core/agent/coverage/ledger.d.ts.map +1 -1
  144. package/dist/types/core/agent/coverage/recognize.d.ts +17 -7
  145. package/dist/types/core/agent/coverage/recognize.d.ts.map +1 -1
  146. package/dist/types/core/agent/stages/answerCoverage.d.ts +44 -0
  147. package/dist/types/core/agent/stages/answerCoverage.d.ts.map +1 -0
  148. package/dist/types/core/agent/stages/prepareFinal.d.ts +24 -5
  149. package/dist/types/core/agent/stages/prepareFinal.d.ts.map +1 -1
  150. package/dist/types/core/agent/types.d.ts +16 -0
  151. package/dist/types/core/agent/types.d.ts.map +1 -1
  152. package/dist/types/events/payloads.d.ts +18 -0
  153. package/dist/types/events/payloads.d.ts.map +1 -1
  154. package/dist/types/lib/answer-account/account.d.ts.map +1 -1
  155. package/dist/types/lib/answer-account/declarations.d.ts.map +1 -1
  156. package/dist/types/lib/answer-account/facts/calls.d.ts +7 -1
  157. package/dist/types/lib/answer-account/facts/calls.d.ts.map +1 -1
  158. package/dist/types/lib/answer-account/facts/common.d.ts +5 -18
  159. package/dist/types/lib/answer-account/facts/common.d.ts.map +1 -1
  160. package/dist/types/lib/answer-account/facts/found.d.ts +6 -4
  161. package/dist/types/lib/answer-account/facts/found.d.ts.map +1 -1
  162. package/dist/types/lib/answer-account/facts/howSure.d.ts +12 -3
  163. package/dist/types/lib/answer-account/facts/howSure.d.ts.map +1 -1
  164. package/dist/types/lib/answer-account/facts/inView.d.ts.map +1 -1
  165. package/dist/types/lib/answer-account/shown.d.ts.map +1 -1
  166. package/dist/types/lib/answer-account/templates.d.ts +26 -1
  167. package/dist/types/lib/answer-account/templates.d.ts.map +1 -1
  168. package/dist/types/lib/answer-account/types.d.ts +31 -3
  169. package/dist/types/lib/answer-account/types.d.ts.map +1 -1
  170. package/dist/types/observe.d.ts +2 -0
  171. package/dist/types/observe.d.ts.map +1 -1
  172. package/package.json +2 -1
package/AGENTS.md CHANGED
@@ -259,8 +259,9 @@ delivered status `'absent'` (route it with `onToolStatus: 'absent'`) and grounds
259
259
  COVERAGE in the evidence gate — so an id the model invented does not become grounded by one
260
260
  lookup that found nothing. It is never an error: nothing retries it, nothing refuses it, no
261
261
  `error: true`. Add `.limitsTravelWithTheAnswer()` on the agent and the framework APPENDS the
262
- run's declared coverage to the final answer string (not to streamed tokens; it breaks
263
- `runTyped()`'s JSON), so the model cannot drop what it never wrote.
262
+ run's declared coverage to the final answer string (not to streamed tokens), so the model
263
+ cannot drop what it never wrote. With `.outputSchema()` nothing is appended — the JSON stays
264
+ parseable by `runTyped()` and the same limits come back as data: `agent.answerCoverage()`.
264
265
 
265
266
  ## Observing a run
266
267
 
package/CAPABILITIES.md CHANGED
@@ -64,10 +64,12 @@ not in this table, search `src/index.ts` for the nearest noun before writing cod
64
64
  | a model head-mathing a total from tool-result numbers while a compute tool sits unused on the wire — a LATE line each iteration naming the staged refs and the `wants` tool that spends them (recency working FOR the instruction), plus the revise correction naming the same route | `nudge: true` (default off) + `stagedRefsNudgeLine` + `findStagedRefs` + `grounding_nudged` | `src/core/agent/stagedRefs.ts` | 9.75.0 |
65
65
  | a tool answering "I looked and found nothing", routably | `absent` + `looked_for` + `checked`/`not_checked`/`cannot_cover` + `tryInstead` (delivered status `'absent'`, routable by `onToolStatus`) | `src/core/agent/coverage/absent.ts` | 9.43.0 |
66
66
  | stating what a clean answer does NOT rule out | `coverage` + the declaration `{ checked, not_checked?, cannot_cover? }` | `src/core/agent/coverage/ledger.ts` | 9.43.0 |
67
+ | a TYPED answer (`.outputSchema()`) carrying the limits its tools declared WITHOUT breaking its JSON — the same limits `.limitsTravelWithTheAnswer()` appends to a prose answer, as data beside it (checked / not checked / cannot cover, duplicates said once, every entry kept); `runTyped()` parses the model's answer untouched | `agent.answerCoverage()` + `turn_end.answerCoverage` + `AgentState.answerCoverage` (with `.outputSchema()` + `.limitsTravelWithTheAnswer()`; absent when no tool declared a limit) | `src/core/agent/stages/answerCoverage.ts` | 9.121.0 |
67
68
  | a person-readable short form of a coverage item, and whether an unchecked item is about EXISTENCE (does the thing asked about exist at all) or SCOPE (outside what the tool reaches) — on the record for a report, never in the model's request, never evidence. `short`: at most 80 chars, one line, never longer than `what`. `kind`: 'existence' or 'scope', on notChecked and cannotCover only. Refused at `absent()` and `coverage()`, read-never-repaired on a foreign envelope; carried by `tools.absent`, `tools.coverage_declared` and `coverageDeclared`; stripped from every served envelope, with `tool_end.modelResult` stamped where it differs | `CoverageItem` + `absent` + `coverage` | `src/core/agent/coverage/` | 9.116.0 |
68
69
  | a skill's plain name for a person ("array estate report" for `array-inventory`) on the recording, without changing what the model reads or what a graph draws — at most 60 chars, one line of plain visible text, not the id; carried as `skill.graph_declared` `nodes[].title` on a skill-graph agent, absent when not declared | `defineSkill` + `title` | `src/lib/injection-engine/factories/defineSkill.ts` | 9.116.0 |
69
70
  | how many of an answer's values the evidence check actually LOOKED UP — `candidates` also counts values the person's message or the app's instructions already held, which are never looked up; found plus not found, before the 12-value slice, on every `evidence_checked` | `lookedUp` + `EvidenceVerdict` | `src/core/agent/evidence/` | 9.116.0 |
70
71
  | explaining ONE answer to a person in plain words — what was asked, how it was routed, what each tool checked and did not check, what it found, how sure, and anything wrong — every line from the record by a fixed versioned template (never a model), each with its template id and who vouches for it (person / library / tool / model / app), "not recorded" instead of a guess, own-run events only, three checks with the ones that could not run said aloud | `accountForAnswer` + `AnswerAccount` | `src/lib/answer-account/` | 9.116.0 |
72
+ | how far an answer STANDS — known · consistent with the record · not sure (with the reasons) · ask · not assessed — folded from the run's COMMITTED record (never from the model's confidence, never from events, never "known" from silence: only a passed enforce answer check supports it); each reason names the row it rests on, `checked` counts what ran, this turn only; a pause of any kind reads `ask` from the recording alone, a run with no answer has no standing; one emptiness reader shared with the answer account (`coverage/emptiness.ts`: a `coverage()` envelope read through, a described result counted, an undeclared empty rowset said aloud) | `assessAnswer` + `AnswerAssessment` (on the observe door), `agent.assessment()` | `src/core/agent/assessment/` | 9.120.0 |
71
73
  | serving that explanation to a browser without the recording: one opt-in wire op returns the account plus allow-listed "show me" leaves, owner-only on the `artifact-get` path, lane-free, cached and single-flight, a stated size ceiling, and a swept record answering "not available" even when cached | `standingAgent({ answerAccounts })` + `{ op: 'answer-account', ref }` + `answerAccountPointerKey` | `src/hosting/answerAccounts.ts` | 9.116.0 |
72
74
  | bounding what one session can ask of the artifact door at once: `artifact-head`, `artifact-get` and `answer-account` counted together per session, after the ownership check, refused 429 by name while every other session is served | `standingAgent({ artifactOpsPerSession })` + `ArtifactOpsBusyError` | `src/hosting/standingAgent.ts` | 9.116.0 |
73
75
  | minting one of those shapes from a tool that is NOT JavaScript — the canonical note sentences and reserved marker keys as DATA, so a Python/Go/Rust sidecar reads a file instead of regex-scraping `dist/esm` (which a consumer really did) | `canonical-notes.json` at the package root + the `./canonical-notes.json` exports entry, GENERATED from the built barrel by `scripts/gen-canonical-notes.mjs` | `scripts/gen-canonical-notes.mjs` | 9.70.0 |
package/README.md CHANGED
@@ -117,7 +117,7 @@ Whatever a tool returns is what the model reads next. When that value has limits
117
117
  | `coverage(value, { … })` | any other value that has limits | your value, with what was and was not checked | `tools.coverage_declared` |
118
118
  | `absent({ … })` | nothing matched | what was looked for, where, and that a retry returns the same | `tools.absent` |
119
119
 
120
- Never wrap one in another. Add `.limitsTravelWithTheAnswer()` and the declared coverage is appended to the final answer — provenance and grain never are.
120
+ Never wrap one in another. Add `.limitsTravelWithTheAnswer()` and the declared coverage is appended to the final answer — or, for a typed `.outputSchema()` answer, returned beside it as data by `agent.answerCoverage()`. Provenance and grain never are.
121
121
 
122
122
  ```typescript
123
123
  import { absent, coverage, describedResult } from 'agentfootprint';
@@ -259,8 +259,9 @@ delivered status `'absent'` (route it with `onToolStatus: 'absent'`) and grounds
259
259
  COVERAGE in the evidence gate — so an id the model invented does not become grounded by one
260
260
  lookup that found nothing. It is never an error: nothing retries it, nothing refuses it, no
261
261
  `error: true`. Add `.limitsTravelWithTheAnswer()` on the agent and the framework APPENDS the
262
- run's declared coverage to the final answer string (not to streamed tokens; it breaks
263
- `runTyped()`'s JSON), so the model cannot drop what it never wrote.
262
+ run's declared coverage to the final answer string (not to streamed tokens), so the model
263
+ cannot drop what it never wrote. With `.outputSchema()` nothing is appended — the JSON stays
264
+ parseable by `runTyped()` and the same limits come back as data: `agent.answerCoverage()`.
264
265
 
265
266
  ## Observing a run
266
267
 
@@ -14,6 +14,29 @@
14
14
  * agentfootprint.stream.tool_start / tool_end
15
15
  * agentfootprint.context.* (via ContextRecorder)
16
16
  */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
17
40
  Object.defineProperty(exports, "__esModule", { value: true });
18
41
  exports.Agent = exports.AgentBuilder = void 0;
19
42
  const footprintjs_1 = require("footprintjs");
@@ -135,6 +158,7 @@ const RunnerBase_js_1 = require("./RunnerBase.js");
135
158
  const validators_js_1 = require("./agent/validators.js");
136
159
  const route_js_1 = require("./agent/stages/route.js");
137
160
  const answerValidation_js_1 = require("./agent/stages/answerValidation.js");
161
+ const answerCoverage_js_1 = require("./agent/stages/answerCoverage.js");
138
162
  const index_js_1 = require("../answer-validation/index.js");
139
163
  const seed_js_1 = require("./agent/stages/seed.js");
140
164
  const errors_js_3 = require("./agent/middleware/errors.js");
@@ -266,10 +290,12 @@ class Agent extends RunnerBase_js_1.RunnerBase {
266
290
  */
267
291
  evidenceGate;
268
292
  /**
269
- * `.limitsTravelWithTheAnswer()` (this release) — whether the run's declared
270
- * coverage is folded into the final answer. False for every agent that did
271
- * not ask for it; the RECORDING half of the coverage primitives does not
272
- * consult it.
293
+ * `.limitsTravelWithTheAnswer()` — whether the run's declared coverage
294
+ * travels with the final answer: appended as a block to a prose answer, or,
295
+ * when an output schema makes the answer TYPED, committed beside it as data
296
+ * (`answerCoverage()`), because JSON followed by prose is not JSON. False for
297
+ * every agent that did not ask for it; the RECORDING half of the coverage
298
+ * primitives does not consult it.
273
299
  */
274
300
  limitsTravelWithTheAnswerValue = false;
275
301
  skillGraphCascade;
@@ -1310,6 +1336,10 @@ class Agent extends RunnerBase_js_1.RunnerBase {
1310
1336
  *
1311
1337
  * Throws if the agent has no outputSchema set or if the run
1312
1338
  * pauses (use `run()` directly when pauses are expected).
1339
+ *
1340
+ * With `.limitsTravelWithTheAnswer()`, nothing is appended to a typed
1341
+ * answer: the limits its tools declared come back as data, from
1342
+ * `agent.answerCoverage()`.
1313
1343
  */
1314
1344
  async runTyped(input, options) {
1315
1345
  if (!this.outputSchemaParser) {
@@ -3165,6 +3195,88 @@ class Agent extends RunnerBase_js_1.RunnerBase {
3165
3195
  ?.answerValidation;
3166
3196
  return report === undefined ? undefined : structuredClone(report);
3167
3197
  }
3198
+ /**
3199
+ * The limits of the last run's TYPED answer, as data — what its tools
3200
+ * declared they `checked`, did not check (`notChecked`) and can never see
3201
+ * (`cannotCover`), folded across the run: duplicates said once, every entry
3202
+ * kept.
3203
+ *
3204
+ * Set when the agent has an output schema AND `.limitsTravelWithTheAnswer()`:
3205
+ * a typed answer is JSON, and the limits block that option appends to a
3206
+ * prose answer would stop it being JSON, so for a typed answer the limits
3207
+ * travel beside it instead — here, on `turn_end.answerCoverage`, and in
3208
+ * `getLastSnapshot().sharedState.answerCoverage`, one value three ways. The
3209
+ * answer string, and so `runTyped()`, is exactly the model's.
3210
+ *
3211
+ * `undefined` when the run's tools declared no limits, on a prose answer
3212
+ * (the block is in the answer string there; `sharedState.coverageDeclared`
3213
+ * holds the raw rows either way), and before the first run. Detached from
3214
+ * the execution record, so a caller may keep or mutate it.
3215
+ *
3216
+ * @example
3217
+ * ```ts
3218
+ * const agent = Agent.create({ provider, model })
3219
+ * .tool(replicationHealth) // returns coverage(verdict, { … })
3220
+ * .outputSchema(Verdict)
3221
+ * .limitsTravelWithTheAnswer()
3222
+ * .build();
3223
+ *
3224
+ * const verdict = await agent.runTyped({ message: 'is replication healthy?' });
3225
+ * const limits = agent.answerCoverage();
3226
+ * for (const item of limits?.cannotCover ?? []) {
3227
+ * console.log(`never covered: ${item.what} (${item.why})`);
3228
+ * }
3229
+ * ```
3230
+ */
3231
+ answerCoverage() {
3232
+ const limits = this.getLastSnapshot()?.sharedState
3233
+ ?.answerCoverage;
3234
+ return limits === undefined ? undefined : structuredClone(limits);
3235
+ }
3236
+ /**
3237
+ * How far the last run's answer stands — folded from its COMMITTED record,
3238
+ * never from how sure the model sounded: `known` · `consistent` (checks ran,
3239
+ * none fired — never "verified") · `not-sure` (with the reasons) · `ask` (the
3240
+ * run paused on a question still waiting for a person) · `not-assessed`
3241
+ * (nothing on the record could be checked).
3242
+ *
3243
+ * The same pure fold as `assessAnswer` on `agentfootprint/observe`, over this
3244
+ * agent's last snapshot; it reads committed state, never events, so a later
3245
+ * reader of the same recording folds the same standing. It resolves
3246
+ * asynchronously because the fold is loaded through `import()` on first use —
3247
+ * an agent that never asks does not carry it.
3248
+ *
3249
+ * Resolves to `undefined` when there is no answer to assess: before the first
3250
+ * run, while a run is in flight, and after a run that threw or whose answer a
3251
+ * rule refused (the typed error carries that verdict).
3252
+ *
3253
+ * `declarations` is the answer account's object, accepted as it is: the fold
3254
+ * reads `tools[name].rowsAt`, where an object result keeps its rows.
3255
+ *
3256
+ * @example
3257
+ * ```ts
3258
+ * await agent.run({ message: 'Which ports on switch A are down?' });
3259
+ * const a = await agent.assessment();
3260
+ * if (a?.standing === 'not-sure') {
3261
+ * for (const r of a.reasons) console.log(r.reason); // e.g. 'empty-undeclared'
3262
+ * }
3263
+ * ```
3264
+ */
3265
+ async assessment(declarations) {
3266
+ const snapshot = this.getLastSnapshot();
3267
+ if (snapshot === undefined)
3268
+ return undefined;
3269
+ // Settled before the fold, which cannot tell a crash from its committed state: an answer
3270
+ // this run RETURNED (`lastRunAnswer`, cleared at every run's start), or a pause — the fold
3271
+ // reads that one from the committed state itself. Read BEFORE the import resolves, so a
3272
+ // run started meanwhile cannot change which run is assessed.
3273
+ const paused = this.lastExecutor?.isPaused() === true;
3274
+ if (!paused && this.lastRunAnswer === undefined)
3275
+ return undefined;
3276
+ // Loaded on first use, off the default graph (the findings/peel.ts precedent).
3277
+ const { assessAnswer } = await Promise.resolve().then(() => __importStar(require('./agent/assessment/assess.js')));
3278
+ return assessAnswer({ snapshot }, declarations);
3279
+ }
3168
3280
  finalizeResult(executor, result) {
3169
3281
  const paused = this.detectPause(executor, result);
3170
3282
  if (paused)
@@ -3989,6 +4101,15 @@ class Agent extends RunnerBase_js_1.RunnerBase {
3989
4101
  const routeDecider = this.answerValidationConfig === undefined
3990
4102
  ? baseRouteDecider
3991
4103
  : (0, answerValidation_js_1.withAnswerValidation)(baseRouteDecider, this.answerValidationConfig, this.outputSchemaParser, artifactStore, () => this.consentOutstanding.size > 0);
4104
+ // `.limitsTravelWithTheAnswer()` on a TYPED answer — decided once, here.
4105
+ // An output schema makes the answer JSON, and the appended block would
4106
+ // make it JSON followed by prose, which `runTyped()` cannot parse. So the
4107
+ // limits travel as DATA: the Route decider's terminal decision commits the
4108
+ // fold (`withAnswerCoverage`) and the Final branch projects it onto
4109
+ // `turn_end`. A prose answer keeps the block; an agent that asked for
4110
+ // neither is handed the decider it always had.
4111
+ const limitsAsData = this.limitsTravelWithTheAnswerValue && this.outputSchemaParser !== undefined;
4112
+ const terminalRouteDecider = limitsAsData ? (0, answerCoverage_js_1.withAnswerCoverage)(routeDecider) : routeDecider;
3992
4113
  // toolCallsHandler extracted to ./agent/stages/toolCalls.ts (v2.11.2).
3993
4114
  const toolCallsHandler = (0, toolCalls_js_1.buildToolCallsHandler)({
3994
4115
  registryByName,
@@ -4164,7 +4285,7 @@ class Agent extends RunnerBase_js_1.RunnerBase {
4164
4285
  maxIterations,
4165
4286
  seed,
4166
4287
  callLLM,
4167
- routeDecider,
4288
+ routeDecider: terminalRouteDecider,
4168
4289
  toolCallsHandler,
4169
4290
  // The re-ask branch — mounted only when there are retries to spend. A
4170
4291
  // `'tool-forced'` agent with `retries: 0` gets the constrained shape and
@@ -4219,11 +4340,14 @@ class Agent extends RunnerBase_js_1.RunnerBase {
4219
4340
  // The declared ontology (9.106.0): the grouped chart carries the run
4220
4341
  // constant across the sf-llm-call boundary, under the arm.
4221
4342
  ...(this.ontology !== undefined && { hasOntology: true }),
4222
- // `.limitsTravelWithTheAnswer()` (this release) — value-conditional, the
4343
+ // `.limitsTravelWithTheAnswer()` — value-conditional, the
4223
4344
  // `resolvedModel` precedent: absent from the deps object entirely for an
4224
4345
  // agent that did not ask, so both builders mount the final-branch stage
4225
- // function they have always mounted.
4226
- ...(this.limitsTravelWithTheAnswerValue && { attachCoverageLimits: true }),
4346
+ // function they have always mounted. ONE of the two, never both: a prose
4347
+ // answer gets the appended block, a TYPED one (`limitsAsData`, above)
4348
+ // gets the stage that appends nothing and carries the limits as data.
4349
+ ...(this.limitsTravelWithTheAnswerValue && !limitsAsData && { attachCoverageLimits: true }),
4350
+ ...(limitsAsData && { coverageLimitsAsData: true }),
4227
4351
  ...(this.answerValidationConfig !== undefined && { hasAnswerValidation: true }),
4228
4352
  // The out-of-budget wrap-up branch (9.56.0) — the conditional-mount law
4229
4353
  // above, decided once beside the Route decider that routes to it so the