agentfootprint 8.9.0 → 8.11.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 (269) hide show
  1. package/AGENTS.md +2 -0
  2. package/CLAUDE.md +4 -0
  3. package/README.md +12 -11
  4. package/bin/agentfootprint-index.mjs +192 -0
  5. package/dist/adapters/observability/cloudwatch.js +133 -19
  6. package/dist/adapters/observability/cloudwatch.js.map +1 -1
  7. package/dist/adapters/observability/deliveryErrors.js +55 -0
  8. package/dist/adapters/observability/deliveryErrors.js.map +1 -0
  9. package/dist/adapters/observability/otel.js +11 -8
  10. package/dist/adapters/observability/otel.js.map +1 -1
  11. package/dist/adapters/observability/xray.js +30 -10
  12. package/dist/adapters/observability/xray.js.map +1 -1
  13. package/dist/core/Agent.js +24 -10
  14. package/dist/core/Agent.js.map +1 -1
  15. package/dist/core/LLMCall.js +11 -1
  16. package/dist/core/LLMCall.js.map +1 -1
  17. package/dist/core/slots/buildMessagesSlot.js +1 -1
  18. package/dist/core/slots/buildMessagesSlot.js.map +1 -1
  19. package/dist/core/slots/buildSystemPromptSlot.js +1 -1
  20. package/dist/core/slots/buildSystemPromptSlot.js.map +1 -1
  21. package/dist/core/slots/buildToolsSlot.js +1 -1
  22. package/dist/core/slots/buildToolsSlot.js.map +1 -1
  23. package/dist/doors/rag.js +66 -0
  24. package/dist/doors/rag.js.map +1 -0
  25. package/dist/esm/adapters/observability/cloudwatch.d.ts +43 -5
  26. package/dist/esm/adapters/observability/cloudwatch.js +133 -19
  27. package/dist/esm/adapters/observability/cloudwatch.js.map +1 -1
  28. package/dist/esm/adapters/observability/deliveryErrors.d.ts +39 -0
  29. package/dist/esm/adapters/observability/deliveryErrors.js +51 -0
  30. package/dist/esm/adapters/observability/deliveryErrors.js.map +1 -0
  31. package/dist/esm/adapters/observability/otel.d.ts +14 -0
  32. package/dist/esm/adapters/observability/otel.js +11 -8
  33. package/dist/esm/adapters/observability/otel.js.map +1 -1
  34. package/dist/esm/adapters/observability/xray.d.ts +15 -0
  35. package/dist/esm/adapters/observability/xray.js +30 -10
  36. package/dist/esm/adapters/observability/xray.js.map +1 -1
  37. package/dist/esm/core/Agent.d.ts +2 -0
  38. package/dist/esm/core/Agent.js +24 -10
  39. package/dist/esm/core/Agent.js.map +1 -1
  40. package/dist/esm/core/LLMCall.d.ts +16 -0
  41. package/dist/esm/core/LLMCall.js +11 -1
  42. package/dist/esm/core/LLMCall.js.map +1 -1
  43. package/dist/esm/core/agent/types.d.ts +28 -0
  44. package/dist/esm/core/slots/buildMessagesSlot.d.ts +2 -1
  45. package/dist/esm/core/slots/buildMessagesSlot.js +1 -1
  46. package/dist/esm/core/slots/buildMessagesSlot.js.map +1 -1
  47. package/dist/esm/core/slots/buildSystemPromptSlot.d.ts +2 -1
  48. package/dist/esm/core/slots/buildSystemPromptSlot.js +1 -1
  49. package/dist/esm/core/slots/buildSystemPromptSlot.js.map +1 -1
  50. package/dist/esm/core/slots/buildToolsSlot.d.ts +2 -1
  51. package/dist/esm/core/slots/buildToolsSlot.js +1 -1
  52. package/dist/esm/core/slots/buildToolsSlot.js.map +1 -1
  53. package/dist/esm/doors/rag.d.ts +49 -0
  54. package/dist/esm/doors/rag.js +50 -0
  55. package/dist/esm/doors/rag.js.map +1 -0
  56. package/dist/esm/lib/injection-engine/SkillRegistry.d.ts +5 -3
  57. package/dist/esm/lib/injection-engine/SkillRegistry.js +5 -3
  58. package/dist/esm/lib/injection-engine/SkillRegistry.js.map +1 -1
  59. package/dist/esm/lib/injection-engine/factories/defineSkill.d.ts +68 -43
  60. package/dist/esm/lib/injection-engine/factories/defineSkill.js +15 -10
  61. package/dist/esm/lib/injection-engine/factories/defineSkill.js.map +1 -1
  62. package/dist/esm/lib/injection-engine/types.d.ts +25 -13
  63. package/dist/esm/lib/injection-engine/types.js +4 -2
  64. package/dist/esm/lib/injection-engine/types.js.map +1 -1
  65. package/dist/esm/lib/mcp/index.d.ts +1 -0
  66. package/dist/esm/lib/mcp/mcpClient.js +15 -6
  67. package/dist/esm/lib/mcp/mcpClient.js.map +1 -1
  68. package/dist/esm/lib/mcp/throttleRetry.d.ts +115 -0
  69. package/dist/esm/lib/mcp/throttleRetry.js +188 -0
  70. package/dist/esm/lib/mcp/throttleRetry.js.map +1 -0
  71. package/dist/esm/lib/mcp/types.d.ts +34 -0
  72. package/dist/esm/rag/hash.d.ts +2 -0
  73. package/dist/esm/rag/hash.js +22 -0
  74. package/dist/esm/rag/hash.js.map +1 -0
  75. package/dist/esm/rag/index.d.ts +17 -0
  76. package/dist/esm/rag/index.js +17 -0
  77. package/dist/esm/rag/index.js.map +1 -0
  78. package/dist/esm/rag/indexCorpus.d.ts +102 -0
  79. package/dist/esm/rag/indexCorpus.js +339 -0
  80. package/dist/esm/rag/indexCorpus.js.map +1 -0
  81. package/dist/esm/rag/indexFolder.d.ts +40 -0
  82. package/dist/esm/rag/indexFolder.js +42 -0
  83. package/dist/esm/rag/indexFolder.js.map +1 -0
  84. package/dist/esm/rag/loadDocuments.d.ts +34 -0
  85. package/dist/esm/rag/loadDocuments.js +160 -0
  86. package/dist/esm/rag/loadDocuments.js.map +1 -0
  87. package/dist/esm/rag/loaders/html.d.ts +42 -0
  88. package/dist/esm/rag/loaders/html.js +48 -0
  89. package/dist/esm/rag/loaders/html.js.map +1 -0
  90. package/dist/esm/rag/loaders/index.d.ts +30 -0
  91. package/dist/esm/rag/loaders/index.js +24 -0
  92. package/dist/esm/rag/loaders/index.js.map +1 -0
  93. package/dist/esm/rag/loaders/markdown.d.ts +27 -0
  94. package/dist/esm/rag/loaders/markdown.js +12 -0
  95. package/dist/esm/rag/loaders/markdown.js.map +1 -0
  96. package/dist/esm/rag/loaders/mock.d.ts +28 -0
  97. package/dist/esm/rag/loaders/mock.js +14 -0
  98. package/dist/esm/rag/loaders/mock.js.map +1 -0
  99. package/dist/esm/rag/loaders/pdf.d.ts +39 -0
  100. package/dist/esm/rag/loaders/pdf.js +90 -0
  101. package/dist/esm/rag/loaders/pdf.js.map +1 -0
  102. package/dist/esm/rag/loaders/text.d.ts +23 -0
  103. package/dist/esm/rag/loaders/text.js +21 -0
  104. package/dist/esm/rag/loaders/text.js.map +1 -0
  105. package/dist/esm/rag/splitDocuments.d.ts +21 -0
  106. package/dist/esm/rag/splitDocuments.js +65 -0
  107. package/dist/esm/rag/splitDocuments.js.map +1 -0
  108. package/dist/esm/rag/splitters/byHeading.d.ts +36 -0
  109. package/dist/esm/rag/splitters/byHeading.js +66 -0
  110. package/dist/esm/rag/splitters/byHeading.js.map +1 -0
  111. package/dist/esm/rag/splitters/byParagraph.d.ts +24 -0
  112. package/dist/esm/rag/splitters/byParagraph.js +16 -0
  113. package/dist/esm/rag/splitters/byParagraph.js.map +1 -0
  114. package/dist/esm/rag/splitters/constants.d.ts +19 -0
  115. package/dist/esm/rag/splitters/constants.js +20 -0
  116. package/dist/esm/rag/splitters/constants.js.map +1 -0
  117. package/dist/esm/rag/splitters/fixedWithOverlap.d.ts +31 -0
  118. package/dist/esm/rag/splitters/fixedWithOverlap.js +34 -0
  119. package/dist/esm/rag/splitters/fixedWithOverlap.js.map +1 -0
  120. package/dist/esm/rag/splitters/index.d.ts +50 -0
  121. package/dist/esm/rag/splitters/index.js +51 -0
  122. package/dist/esm/rag/splitters/index.js.map +1 -0
  123. package/dist/esm/rag/splitters/shared.d.ts +87 -0
  124. package/dist/esm/rag/splitters/shared.js +188 -0
  125. package/dist/esm/rag/splitters/shared.js.map +1 -0
  126. package/dist/esm/rag/splitters/wholeDocument.d.ts +16 -0
  127. package/dist/esm/rag/splitters/wholeDocument.js +10 -0
  128. package/dist/esm/rag/splitters/wholeDocument.js.map +1 -0
  129. package/dist/esm/rag/types.d.ts +200 -0
  130. package/dist/esm/rag/types.js +20 -0
  131. package/dist/esm/rag/types.js.map +1 -0
  132. package/dist/esm/strategies/attach.d.ts +18 -3
  133. package/dist/esm/strategies/attach.js.map +1 -1
  134. package/dist/esm/strategies/types.d.ts +74 -20
  135. package/dist/esm/strategies/types.js +15 -10
  136. package/dist/esm/strategies/types.js.map +1 -1
  137. package/dist/lib/injection-engine/SkillRegistry.js +5 -3
  138. package/dist/lib/injection-engine/SkillRegistry.js.map +1 -1
  139. package/dist/lib/injection-engine/factories/defineSkill.js +15 -10
  140. package/dist/lib/injection-engine/factories/defineSkill.js.map +1 -1
  141. package/dist/lib/injection-engine/types.js +4 -2
  142. package/dist/lib/injection-engine/types.js.map +1 -1
  143. package/dist/lib/mcp/mcpClient.js +15 -6
  144. package/dist/lib/mcp/mcpClient.js.map +1 -1
  145. package/dist/lib/mcp/throttleRetry.js +193 -0
  146. package/dist/lib/mcp/throttleRetry.js.map +1 -0
  147. package/dist/rag/hash.js +26 -0
  148. package/dist/rag/hash.js.map +1 -0
  149. package/dist/rag/index.js +40 -0
  150. package/dist/rag/index.js.map +1 -0
  151. package/dist/rag/indexCorpus.js +344 -0
  152. package/dist/rag/indexCorpus.js.map +1 -0
  153. package/dist/rag/indexFolder.js +46 -0
  154. package/dist/rag/indexFolder.js.map +1 -0
  155. package/dist/rag/loadDocuments.js +164 -0
  156. package/dist/rag/loadDocuments.js.map +1 -0
  157. package/dist/rag/loaders/html.js +53 -0
  158. package/dist/rag/loaders/html.js.map +1 -0
  159. package/dist/rag/loaders/index.js +33 -0
  160. package/dist/rag/loaders/index.js.map +1 -0
  161. package/dist/rag/loaders/markdown.js +16 -0
  162. package/dist/rag/loaders/markdown.js.map +1 -0
  163. package/dist/rag/loaders/mock.js +18 -0
  164. package/dist/rag/loaders/mock.js.map +1 -0
  165. package/dist/rag/loaders/pdf.js +118 -0
  166. package/dist/rag/loaders/pdf.js.map +1 -0
  167. package/dist/rag/loaders/text.js +26 -0
  168. package/dist/rag/loaders/text.js.map +1 -0
  169. package/dist/rag/splitDocuments.js +69 -0
  170. package/dist/rag/splitDocuments.js.map +1 -0
  171. package/dist/rag/splitters/byHeading.js +70 -0
  172. package/dist/rag/splitters/byHeading.js.map +1 -0
  173. package/dist/rag/splitters/byParagraph.js +20 -0
  174. package/dist/rag/splitters/byParagraph.js.map +1 -0
  175. package/dist/rag/splitters/constants.js +23 -0
  176. package/dist/rag/splitters/constants.js.map +1 -0
  177. package/dist/rag/splitters/fixedWithOverlap.js +38 -0
  178. package/dist/rag/splitters/fixedWithOverlap.js.map +1 -0
  179. package/dist/rag/splitters/index.js +60 -0
  180. package/dist/rag/splitters/index.js.map +1 -0
  181. package/dist/rag/splitters/shared.js +199 -0
  182. package/dist/rag/splitters/shared.js.map +1 -0
  183. package/dist/rag/splitters/wholeDocument.js +14 -0
  184. package/dist/rag/splitters/wholeDocument.js.map +1 -0
  185. package/dist/rag/types.js +21 -0
  186. package/dist/rag/types.js.map +1 -0
  187. package/dist/strategies/attach.js.map +1 -1
  188. package/dist/strategies/types.js +15 -10
  189. package/dist/strategies/types.js.map +1 -1
  190. package/dist/types/adapters/observability/cloudwatch.d.ts +43 -5
  191. package/dist/types/adapters/observability/cloudwatch.d.ts.map +1 -1
  192. package/dist/types/adapters/observability/deliveryErrors.d.ts +40 -0
  193. package/dist/types/adapters/observability/deliveryErrors.d.ts.map +1 -0
  194. package/dist/types/adapters/observability/otel.d.ts +14 -0
  195. package/dist/types/adapters/observability/otel.d.ts.map +1 -1
  196. package/dist/types/adapters/observability/xray.d.ts +15 -0
  197. package/dist/types/adapters/observability/xray.d.ts.map +1 -1
  198. package/dist/types/core/Agent.d.ts +2 -0
  199. package/dist/types/core/Agent.d.ts.map +1 -1
  200. package/dist/types/core/LLMCall.d.ts +16 -0
  201. package/dist/types/core/LLMCall.d.ts.map +1 -1
  202. package/dist/types/core/agent/types.d.ts +28 -0
  203. package/dist/types/core/agent/types.d.ts.map +1 -1
  204. package/dist/types/core/slots/buildMessagesSlot.d.ts +2 -1
  205. package/dist/types/core/slots/buildMessagesSlot.d.ts.map +1 -1
  206. package/dist/types/core/slots/buildSystemPromptSlot.d.ts +2 -1
  207. package/dist/types/core/slots/buildSystemPromptSlot.d.ts.map +1 -1
  208. package/dist/types/core/slots/buildToolsSlot.d.ts +2 -1
  209. package/dist/types/core/slots/buildToolsSlot.d.ts.map +1 -1
  210. package/dist/types/doors/rag.d.ts +50 -0
  211. package/dist/types/doors/rag.d.ts.map +1 -0
  212. package/dist/types/lib/injection-engine/SkillRegistry.d.ts +5 -3
  213. package/dist/types/lib/injection-engine/SkillRegistry.d.ts.map +1 -1
  214. package/dist/types/lib/injection-engine/factories/defineSkill.d.ts +68 -43
  215. package/dist/types/lib/injection-engine/factories/defineSkill.d.ts.map +1 -1
  216. package/dist/types/lib/injection-engine/types.d.ts +25 -13
  217. package/dist/types/lib/injection-engine/types.d.ts.map +1 -1
  218. package/dist/types/lib/mcp/index.d.ts +1 -0
  219. package/dist/types/lib/mcp/index.d.ts.map +1 -1
  220. package/dist/types/lib/mcp/mcpClient.d.ts.map +1 -1
  221. package/dist/types/lib/mcp/throttleRetry.d.ts +116 -0
  222. package/dist/types/lib/mcp/throttleRetry.d.ts.map +1 -0
  223. package/dist/types/lib/mcp/types.d.ts +34 -0
  224. package/dist/types/lib/mcp/types.d.ts.map +1 -1
  225. package/dist/types/rag/hash.d.ts +3 -0
  226. package/dist/types/rag/hash.d.ts.map +1 -0
  227. package/dist/types/rag/index.d.ts +18 -0
  228. package/dist/types/rag/index.d.ts.map +1 -0
  229. package/dist/types/rag/indexCorpus.d.ts +103 -0
  230. package/dist/types/rag/indexCorpus.d.ts.map +1 -0
  231. package/dist/types/rag/indexFolder.d.ts +41 -0
  232. package/dist/types/rag/indexFolder.d.ts.map +1 -0
  233. package/dist/types/rag/loadDocuments.d.ts +35 -0
  234. package/dist/types/rag/loadDocuments.d.ts.map +1 -0
  235. package/dist/types/rag/loaders/html.d.ts +43 -0
  236. package/dist/types/rag/loaders/html.d.ts.map +1 -0
  237. package/dist/types/rag/loaders/index.d.ts +31 -0
  238. package/dist/types/rag/loaders/index.d.ts.map +1 -0
  239. package/dist/types/rag/loaders/markdown.d.ts +28 -0
  240. package/dist/types/rag/loaders/markdown.d.ts.map +1 -0
  241. package/dist/types/rag/loaders/mock.d.ts +29 -0
  242. package/dist/types/rag/loaders/mock.d.ts.map +1 -0
  243. package/dist/types/rag/loaders/pdf.d.ts +40 -0
  244. package/dist/types/rag/loaders/pdf.d.ts.map +1 -0
  245. package/dist/types/rag/loaders/text.d.ts +24 -0
  246. package/dist/types/rag/loaders/text.d.ts.map +1 -0
  247. package/dist/types/rag/splitDocuments.d.ts +22 -0
  248. package/dist/types/rag/splitDocuments.d.ts.map +1 -0
  249. package/dist/types/rag/splitters/byHeading.d.ts +37 -0
  250. package/dist/types/rag/splitters/byHeading.d.ts.map +1 -0
  251. package/dist/types/rag/splitters/byParagraph.d.ts +25 -0
  252. package/dist/types/rag/splitters/byParagraph.d.ts.map +1 -0
  253. package/dist/types/rag/splitters/constants.d.ts +20 -0
  254. package/dist/types/rag/splitters/constants.d.ts.map +1 -0
  255. package/dist/types/rag/splitters/fixedWithOverlap.d.ts +32 -0
  256. package/dist/types/rag/splitters/fixedWithOverlap.d.ts.map +1 -0
  257. package/dist/types/rag/splitters/index.d.ts +51 -0
  258. package/dist/types/rag/splitters/index.d.ts.map +1 -0
  259. package/dist/types/rag/splitters/shared.d.ts +88 -0
  260. package/dist/types/rag/splitters/shared.d.ts.map +1 -0
  261. package/dist/types/rag/splitters/wholeDocument.d.ts +17 -0
  262. package/dist/types/rag/splitters/wholeDocument.d.ts.map +1 -0
  263. package/dist/types/rag/types.d.ts +201 -0
  264. package/dist/types/rag/types.d.ts.map +1 -0
  265. package/dist/types/strategies/attach.d.ts +18 -3
  266. package/dist/types/strategies/attach.d.ts.map +1 -1
  267. package/dist/types/strategies/types.d.ts +74 -20
  268. package/dist/types/strategies/types.d.ts.map +1 -1
  269. package/package.json +23 -4
package/AGENTS.md CHANGED
@@ -152,6 +152,8 @@ agent.rag(docs);
152
152
  await agent.run({ message: 'How long do refunds take?' });
153
153
  ```
154
154
 
155
+ **Building the index** (8.10.0) is `agentfootprint/rag`: `indexFolder('./docs', { to: store, embedder })`, or the `loadDocuments` → `splitDocuments` → `indexCorpus` pieces, or `npx agentfootprint-index ./docs --to ./corpus.db`. Loaders for text/Markdown/HTML (zero-dep) and PDF (lazy `unpdf`, per-page text so citations can name a page). Re-running embeds only what changed. `defineRAG` stays on the MAIN barrel — it is run-time wiring; that door is index time.
156
+
155
157
  `defineRAG` runs on `defineMemory({ type: SEMANTIC, strategy: TOP_K })`. Same machinery, three deliberate differences: a corpus is **read-only** (it never stores the conversation), it reads under its **own namespace** rather than the run's identity, and its chunks render as **citable `<source>` blocks**. For conversation memory alongside a corpus, register both — `.rag(defineRAG(...))` and `.memory(defineMemory(...))`, each with its own store.
156
158
 
157
159
  **Why did the agent read this passage?** `agentfootprint.memory.retrieved` carries every candidate with its score — including the ones that were rejected and why. `agentfootprint.memory.attached` fires per chunk that reached the prompt. `agentfootprint.context.injected` reports `source: 'rag'` with that chunk's `retrievalScore` / `rankPosition` / `threshold`. The whole record is on root state as `retrievalEvidence_<id>`, where a backward slice can reach it.
package/CLAUDE.md CHANGED
@@ -16,6 +16,7 @@ Entry points (package.json exports): `.` core API · `/observe` ALL observabilit
16
16
  | adapters/ | hexagonal ports (types.ts = ALL port interfaces) + vendor impls (llm/, memory/, identity/, observability/). memory/sqliteVector.ts (8.9.0) = the only FULL MemoryStore we ship with `search` besides InMemoryStore — exact cosine over a resident Float32Array matrix, hydrated per namespace on first search and dropped on any write to it |
17
17
  | recorders/core/ | bridges footprintjs events → typed EventDispatcher (ContextRecorder, EmitBridge, typedEmit) — auto-attached by Agent.createExecutor; most factories also exported via `/observe` for manual wiring (EmitBridge itself stays internal) |
18
18
  | recorders/observability/ | consumer recorders over the typed stream (RunStepRecorder, FlowchartRecorder, Status, Trace replay) + `recordRun` — THE producer of a recording `{snapshot, events, structure}` (the shape lens's `observeRecording` consumes; `structure` = `getSpec().buildTimeStructure`, which no snapshot carries). Anything that saves a run goes through it |
19
+ | rag/ | (8.10.0, door `/rag`) index-TIME: `DocumentLoader` adapters (text/markdown/html zero-dep, pdf via lazy `unpdf`) + `Splitter` factories + `indexCorpus` — a REAL footprintjs chart whose commit log IS the indexing report. `defineRAG` deliberately stays on the MAIN barrel (run-time wiring); this door is the half that runs once, before any agent exists |
19
20
  | lib/ | first-party sub-libraries: injection-engine/, context-bisect/ (localizeContextBug, toBacktrackTrace + sliceToBacktrackTrace — the atui board serializers), influence-core/, trace-toolpack/ (selfExplain; 6 tools incl. variable-first `backtrack(variable, element?)`), context-ledger/ (which pieces EARNED their tokens — post-run offers/uses/outcomes bookkeeping + demote-never-starve gates `ledgerToolGate`/`ledgerEntryScorer`/`ledgerGated`; grouped-mode folds sf-llm-call inner logs, unmeterable runs → undefined; /observe), mcp/, rag/, tool-lint/ |
20
21
  | memory/ | store/ (MemoryStore port) + pipeline presets + stages + beats/facts + causal/ (dev-only, TOP_K+search()-only) + wire/mountMemoryPipeline + retrieval/ (8.8.0: the `RetrievalStrategy` seam + `RetrievalEvidence`, the record a retrieval leaves — `topK()` is what every earlier release did unnamed) |
21
22
  | events/ | EventDispatcher (wildcard subs), registry (EVENT_NAMES, AgentfootprintEventMap), payloads |
@@ -39,6 +40,8 @@ Traps: `src/observability/` holds the finder IMPLEMENTATIONS (canonical home; `d
39
40
  - **New typed event (3-step)**: payload interface in events/payloads.ts + entry in `AgentfootprintEventMap` (registry.ts:198) + append to `ALL_EVENT_TYPES` (registry.ts:488, count-asserted by tests). New DOMAIN also needs a bridge attach in Agent.createExecutor or emits never reach the dispatcher — AND a hand-edit to `DomainWildcard` (dispatcher.ts:67-82; already missing validation/credential/reliability).
40
41
  - **Strategy (vendor sink)**: shapes in strategies/types.ts (Observability :130, Cost :169, LiveStatus :201, Lens :234); attach via `agent.enable.*` or `registerObservabilityStrategy` (strategies/registry.ts). New vendor = export from observability-providers.ts, NOT a new subpath.
41
42
  - **Memory store**: implement `MemoryStore` (memory/store/types.ts:113; `search?` REQUIRED for causal memory); pass to `defineMemory({store})`. Memory TYPE/STRATEGY unions are CLOSED (define.types.ts:57/74 — new one edits defineMemory dispatch + a pipeline builder).
43
+ - **Document loader** (8.10.0): implement `DocumentLoader` (rag/types.ts) — `{name, extensions, load}`. `loadDocuments` routes by extension, caller-supplied loaders FIRST, so overriding a built-in is passing yours ahead of it rather than editing `DEFAULT_LOADERS`. A loader MUST NOT rewrite text after offsets are conceivable: the HTML stripper replaces tags with EQUAL-LENGTH whitespace for exactly this reason.
44
+ - **Splitter** (8.10.0): implement `Splitter` (`{name, split(doc) → SplitPiece[]}`), a factory function like the window/retrieval families. THE invariant — `doc.text.slice(charStart, charEnd) === piece.text` — is VERIFIED by `splitDocuments`, not trusted. All offset arithmetic lives once in `splitters/shared.ts`; a strategy that does its own is how the invariant breaks.
42
45
  - **Durable store** (8.9.0): `sqliteVectorStore` follows `hosting/sqliteSessions` line for line — lazy `node:sqlite`, WAL read-back on `journalMode`, STRICT tables, schema-identity + schema-version refusals, `':memory:'` refused. TWO things it adds that have no precedent there: `putMany`/`putIfVersion`/`forget` wrap in a transaction (sqliteSessions has none), and the EMBEDDER FINGERPRINT (`'<id>@<dims>'`, one per namespace in `af_index_meta`) is refused at write AND query. `SqliteUnavailableError` is now ONE class in `lib/sqliteUnavailable.ts` re-exported by both doors — a second class of that name is a duplicate type the build refuses.
43
46
  - **Retrieval rule** (8.8.0): implement `RetrievalStrategy` (memory/retrieval/types.ts) — `select(pool) → verdict[]`, one verdict per candidate, order preserved; it never touches the store and never embeds. Pass as `defineRAG({retrieval})`. `topK()` is the only shipped one; rerank/MMR are named-but-deferred adapters behind the same interface. `TopKStrategy` is a UNION whose arms exclude (`{topK,threshold}` vs `{retrieval}`) — refused in the type AND at runtime, because two spellings of one rule can disagree.
44
47
  - **Injection/skill**: `Injection = {id, flavor, trigger, inject}` (lib/injection-engine/types.ts:161); trigger is a closed 4-variant union (:30 — new kind edits evaluator.ts:40-74 switch). Factories defineSkill etc.; skill graph via `skillGraph()` (skillGraph.ts:392) with pluggable `EntryScorer` (entryScorer.ts:64). `SkillGraphConfig` is a UNION (flat arm `start`/`steps` vs tree arm) — the contradictions it encodes are ALSO refused at build (`.tree()` + `.entry()`/`.route()`, a non-leaf in `skills[]` under a tree, two skills claiming one id, a second `.skillGraph()` on one agent).
@@ -51,6 +54,7 @@ Traps: `src/observability/` holds the finder IMPLEMENTATIONS (canonical home; `d
51
54
  ## Change-impact map
52
55
  - **conventions.ts** (STAGE_IDS/SUBFLOW_IDS/INJECTION_KEYS) → chart builders that mount by id, ContextRecorder slot attribution, localizer loop-head detection (lib/context-bisect/trajectory.ts:17-33), `stageRole`/`milestoneFor` (Lens contract), BoundaryRecorder. Renaming an id is the whole blast radius.
53
56
  - **BoundaryRecorder wiring is THREE connections, all at record time**: `runner.attach` (boundaries), `.subscribe(runner)` (what's inside them), `{getCommitCount}` (where each sits on the commit axis). The third fails SILENTLY — every event stamps `commitIdxBefore: 0`, `boundaryIndex` stays empty by design, and an offline step strip has nothing to place. Unrecoverable after the run (the commit log never records WHEN a boundary was crossed). Wired by `attachFlowchart` (which `enable.flowchart`/`enable.localObservability` both go through) and by `recordRun`; a new entry point must pass all three.
57
+ - **indexCorpus fan-out** (8.10.0) → `maxBranches` on `addParallelForEach` TRUNCATES surplus items rather than queueing them, so the chart fans out over a WINDOW (`take-window` → `embed` → `tally-window` → `more-batches-decider` `{loopTo: 'take-window'}`) that can never exceed the ceiling. A single fan-out over all batches would silently index only the first `maxConcurrentBatches` — pinned by the 12-batches-through-a-window-of-2 test. `embedded` is summed from each branch's `written`, never from the plan's queue, and the fan-out is `failFast: true` because a half-indexed corpus keeps answering.
54
58
  - **Embedder fingerprint** (8.9.0) → `Embedder.id` (optional; every shipped embedder sets one, and NONE include dims — the store appends `@<dims>` itself, so an id carrying its own size double-stamps) + `indexDocuments` defaulting `embedderId` to it + `SqliteVectorStore.reconcileFingerprint` (the only comparison site). Rule: dimensions ALWAYS decide, model ids decide only when BOTH sides named themselves — refusing on an absent name would block the majority of callers who never pass `embedderId`.
55
59
  - **Retrieval record** (8.8.0) → FOUR stages write one object in sequence: `loadRelevant` (candidates+scores+threshold verdicts) → `pickByBudget` (re-marks admitted→over-budget/over-max-entries) → `formatDefault` (`promptFragment` + `promptPosition`) → the read mount's outputMapper lifts it to root as `retrievalEvidence_<id>`. `memoryRecallInjections` then splits ONE recall into one ActiveInjection PER CHUNK — guarded by a byte-equality check (`fragments.join('\n\n') === systemContent`) that falls back to the single injection rather than change the prompt. `rank` (score order) and `promptPosition` (picker order) are DIFFERENT and both load-bearing: joining fragments in rank order reproduces the right bytes in a sequence the model never saw.
56
60
  - **AgentState** → all 8 stages/ files, both builders' mappers, memory-wire STRING-TYPED keys ('runIdentity'/'turnNumber'/… buildAgentChart.ts:177-180 — not refactor-safe), finalizeResult's `reliabilityFail*`/`policyHalt*` reads (rename silently kills the typed errors).
package/README.md CHANGED
@@ -18,11 +18,11 @@
18
18
 
19
19
  <p align="center">
20
20
  <a href="https://footprintjs.github.io/agentThinkingUI/">
21
- <img src="docs/assets/hero-atui.png" alt="An agent run replayed in AgentThinkingUI — the LLM 'brain' calls the Flight-search tool, the step inspector shows the tool's raw output and the brain's reasoning about it, and the timeline scrubs every step of the run." width="100%">
21
+ <img src="docs/assets/hero-atui.png" alt="An agent run replayed in Story Lens — the LLM 'brain' calls the Flight-search tool, the step inspector shows the tool's raw output and the brain's reasoning about it, and the timeline scrubs every step of the run." width="100%">
22
22
  </a>
23
23
  </p>
24
24
  <p align="center">
25
- <sub>A real run, replayed — rendered with <a href="https://github.com/footprintjs/agentThinkingUI"><b>AgentThinkingUI</b></a> (<code>npm i agentthinkingui</code>). Every frame is generated from the run's own trace; <a href="https://footprintjs.github.io/agentThinkingUI/">▶ watch it live</a>.</sub>
25
+ <sub>A real run, replayed — rendered with <a href="https://github.com/footprintjs/agentThinkingUI"><b>Story Lens</b></a> (<code>npm i agentthinkingui</code>). Every frame is generated from the run's own trace; <a href="https://footprintjs.github.io/agentThinkingUI/">▶ watch it live</a>.</sub>
26
26
  </p>
27
27
 
28
28
  <p align="center">
@@ -139,11 +139,12 @@ const agent = Agent.create({ provider, model })
139
139
  id: 'refund-policy',
140
140
  prompt: 'Never promise a refund before checking the policy tool.',
141
141
  }))
142
- .skill(defineSkill({ // guidance + tools unlocks when the LLM asks
142
+ .skill(defineSkill({ // guidance the LLM loads when it asks
143
143
  id: 'billing',
144
144
  description: 'Use for refunds, charges, billing questions.',
145
145
  body: 'When handling billing: confirm identity first, then…',
146
146
  tools: [refundTool],
147
+ autoActivate: 'currentSkill', // ...and scope its tools to that window too
147
148
  }))
148
149
  .build();
149
150
  ```
@@ -277,7 +278,7 @@ confident, wrong #1. Guides: [ranking-confidence](docs/guides/ranking-confidence
277
278
  [missing-context](docs/guides/missing-context.md).
278
279
 
279
280
  **The same walk, visual.** `toBacktrackTrace()` serializes the report into
280
- [AgentThinkingUI](https://github.com/footprintjs/agentThinkingUI)'s `<BacktrackView>`
281
+ [Story Lens](https://github.com/footprintjs/agentThinkingUI)'s `<BacktrackView>`
281
282
  — the "why?" board, triggerable from any decision point (final answer, a mid-loop tool
282
283
  choice, a deterministic `decide()` rule):
283
284
 
@@ -368,7 +369,7 @@ That's the whole model: `Injection = slot × trigger × cache`.
368
369
  | `llm-activated` | runtime — agent-driven | LLM calls `read_skill('id')` | `.skill(defineSkill({ id: 'refund-policy', description, body, viaToolName: 'read_skill' }))` | `system` (body) + `tools` |
369
370
 
370
371
  > [!NOTE]
371
- > The "Illustration" column shows the shape of each flavor — the typed builder methods (`.steering` / `.instruction` / `.skill` / `.fact` / `.rag`) take an `Injection` (or `MemoryDefinition` for `.rag`) produced by the matching `defineSteering` / `defineInstruction` / `defineSkill` / `defineFact` / `defineRAG` factory. A `Skill` targets more than one slot at once: `tools` (the schemas it unlocks) and `system` (its body — or, with `surfaceMode: 'tool-only'`, the `read_skill` result instead). The `messages` slot both projects the conversation and accepts delivery: `slot: 'messages'` (with a `role` you name) appends to the window itself, subject to what the attached provider carries inside `messages` and to a sequence rule that defers rather than reorders.
372
+ > The "Illustration" column shows the shape of each flavor — the typed builder methods (`.steering` / `.instruction` / `.skill` / `.fact` / `.rag`) take an `Injection` (or `MemoryDefinition` for `.rag`) produced by the matching `defineSteering` / `defineInstruction` / `defineSkill` / `defineFact` / `defineRAG` factory. A `Skill` targets more than one slot at once: `tools` (the schemas it contributes — registered up front and visible from iteration 1 unless the Skill sets `autoActivate: 'currentSkill'`, which scopes them to the iterations where it is active) and `system` (its body — or, with `surfaceMode: 'tool-only'`, the `read_skill` result instead). The `messages` slot both projects the conversation and accepts delivery: `slot: 'messages'` (with a `role` you name) appends to the window itself, subject to what the attached provider carries inside `messages` and to a sequence rule that defers rather than reorders.
372
373
 
373
374
  **3 slots × 4 triggers × N flavors = the entire context-engineering surface.**
374
375
 
@@ -657,10 +658,10 @@ There is deliberately no list of "watch moments" to go with `.act()`'s five. A r
657
658
  ## 🐛 Debug — see what your agent did
658
659
 
659
660
  <p align="center">
660
- <img src="docs/assets/lens-run.png" alt="A real agent run in the Lens: the conversation (with live PII redaction), the executed path lit on the merge-tree flowchart, the WHAT-HAPPENED timeline of every iteration/context/LLM turn/route, run stats, and the step inspector — all generated from the run's own trace." width="100%">
661
+ <img src="docs/assets/lens-run.png" alt="A real agent run in Why Lens: the conversation (with live PII redaction), the executed path lit on the merge-tree flowchart, the WHAT-HAPPENED timeline of every iteration/context/LLM turn/route, run stats, and the step inspector — all generated from the run's own trace." width="100%">
661
662
  </p>
662
663
  <p align="center">
663
- <sub>One real run, fully explained — the <a href="https://github.com/footprintjs/agentfootprint-lens"><b>Lens</b></a> (<code>npm i agentfootprint-lens</code>): conversation · executed path · per-step timeline · stats, every pixel from the trace.</sub>
664
+ <sub>One real run, fully explained — <a href="https://github.com/footprintjs/agentfootprint-lens"><b>Why Lens</b></a> (<code>npm i agentfootprint-lens</code>): conversation · executed path · per-step timeline · stats, every pixel from the trace.</sub>
664
665
  </p>
665
666
 
666
667
  Because we own the loop, every decision and execution is captured during traversal — not bolted on. The default capture is the **causal trace**: every stage, read, write, and decision evidence as a JSON-portable, scrubbable, queryable, exportable artifact — and every LLM call backtracks to four typed answers: **what** was injected, **who** triggered it (which rule), **when** it fired, **how** it landed (slot · position · cache). Beyond the default, wire custom recorders for cost, latency, or quality scoring — any observation hook fires on the same stream.
@@ -677,9 +678,9 @@ Four views, one trace — pick by question:
677
678
 
678
679
  | View | Shows | When to use |
679
680
  |---|---|---|
680
- | **AgentThinkingUI** (the hero up top) | The run replayed as an animated, scrubbable story — the brain, the tools, the reasoning | Show anyone *what the agent did* |
681
+ | **Story Lens** (the hero up top) | The run replayed as an animated, scrubbable story — the brain, the tools, the reasoning | Show anyone *what the agent did* |
681
682
  | **BacktrackView** ([the board above](#one-contextual-error-walked-end-to-end)) | A decision walked backwards — suspects, influence meters, ablation stamps, custody rewind | Answer *why it decided that* |
682
- | **Lens** | Agent-centric — User/Agent[3 slots]/Tool flowchart with iteration scrubber and round commentary | Live debugging, "what did the agent see at step 5?" |
683
+ | **Why Lens** | Agent-centric — User/Agent[3 slots]/Tool flowchart with iteration scrubber and round commentary | Live debugging, "what did the agent see at step 5?" |
683
684
  | **Explainable Trace** | Structural — subflow tree, full flowchart, memory inspector, per-stage execution timeline | Architecture review, root-cause analysis |
684
685
 
685
686
  And two **conversational** doors over the same evidence — ask instead of look:
@@ -852,8 +853,8 @@ The flowchart, recorders, and tests don't change between dev and prod.
852
853
  - OTel GenAI span export · hash-chained tamper-evident audit bundles with an offline verifier
853
854
 
854
855
  **Tooling**
855
- - **AgentThinkingUI** — animated run player + BacktrackView why-board (separate `agentthinkingui` package)
856
- - **Lens** · **Explainable Trace** — two visual replays of the causal trace (separate `agentfootprint-lens` package)
856
+ - **Story Lens** — animated run player + BacktrackView why-board (separate `agentthinkingui` package)
857
+ - **Why Lens** · **Explainable Trace** — two visual replays of the causal trace (separate `agentfootprint-lens` package)
857
858
  - AI-coding-tool support — Claude Code · Cursor · Windsurf · Cline · Kiro · Copilot
858
859
 
859
860
  </details>
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * agentfootprint-index — build a corpus index from the command line.
4
+ *
5
+ * The third bin in this package, after `agentfootprint-setup` and
6
+ * `agentfootprint-lint-tools`. It exists because indexing is a BOOT-TIME job:
7
+ * a cron entry, a deploy step, a thing you run once after adding documents.
8
+ * Making that a script every consumer writes for themselves — argument
9
+ * parsing, embedder selection, a progress line — is the kind of small friction
10
+ * that gets in the way of the first ten minutes.
11
+ *
12
+ * npx agentfootprint-index ./docs --to ./corpus.db
13
+ * npx agentfootprint-index ./docs --to ./corpus.db --embedder local --split heading
14
+ * npx agentfootprint-index ./docs --to ./corpus.db --dry-run
15
+ *
16
+ * It prints the same `IndexReport` the API returns, because the report IS the
17
+ * output: a second run over unchanged documents prints `embedded 0`, which is
18
+ * the whole point of an incremental index and the fastest way to see it work.
19
+ */
20
+
21
+ import { parseArgs } from 'node:util';
22
+ import { existsSync, statSync } from 'node:fs';
23
+ import { resolve } from 'node:path';
24
+
25
+ const USAGE = `
26
+ agentfootprint-index — build a corpus index
27
+
28
+ USAGE
29
+ agentfootprint-index <dir> --to <file.db> [options]
30
+
31
+ REQUIRED
32
+ <dir> Directory of documents to index
33
+ --to <file.db> SQLite index file (created if missing)
34
+
35
+ OPTIONS
36
+ --embedder <name> static (default) | local | openai | mock
37
+ static: bundled weights, no key, no network
38
+ local: on-device sentence-transformer, no key
39
+ openai: hosted, needs OPENAI_API_KEY
40
+ mock: letter frequency — plumbing only, NOT semantic
41
+ --split <name> heading (default) | paragraph | fixed | whole
42
+ --chars <n> Target chunk size. Default 1000
43
+ --overlap <n> Overlap between chunks. Default 150
44
+ --include <.ext,...> Extensions to index. Default: every format we read
45
+ --corpus <name> Namespace to index into. Default '_global'
46
+ --no-recursive Do not descend into subdirectories
47
+ --no-remove Keep chunks whose document disappeared
48
+ --dry-run Report what WOULD happen; write nothing
49
+ --json Print the report as JSON
50
+ -h, --help This
51
+
52
+ EXAMPLES
53
+ agentfootprint-index ./docs --to ./corpus.db
54
+ agentfootprint-index ./docs --to ./corpus.db --embedder local --chars 800
55
+ agentfootprint-index ./docs --to ./corpus.db --dry-run --json
56
+ `;
57
+
58
+ function fail(message) {
59
+ process.stderr.write(`agentfootprint-index: ${message}\n`);
60
+ process.stderr.write('Run with --help for usage.\n');
61
+ process.exit(1);
62
+ }
63
+
64
+ const { values, positionals } = (() => {
65
+ try {
66
+ return parseArgs({
67
+ allowPositionals: true,
68
+ options: {
69
+ to: { type: 'string' },
70
+ embedder: { type: 'string' },
71
+ split: { type: 'string' },
72
+ chars: { type: 'string' },
73
+ overlap: { type: 'string' },
74
+ include: { type: 'string' },
75
+ corpus: { type: 'string' },
76
+ recursive: { type: 'boolean', default: true },
77
+ remove: { type: 'boolean', default: true },
78
+ 'dry-run': { type: 'boolean', default: false },
79
+ json: { type: 'boolean', default: false },
80
+ help: { type: 'boolean', short: 'h', default: false },
81
+ },
82
+ });
83
+ } catch (err) {
84
+ fail(err instanceof Error ? err.message : String(err));
85
+ }
86
+ })();
87
+
88
+ if (values.help || positionals.length === 0) {
89
+ process.stdout.write(USAGE);
90
+ process.exit(values.help ? 0 : 1);
91
+ }
92
+
93
+ const dir = resolve(positionals[0]);
94
+ if (!existsSync(dir) || !statSync(dir).isDirectory()) {
95
+ fail(`'${positionals[0]}' is not a directory.`);
96
+ }
97
+ if (!values.to && !values['dry-run']) {
98
+ fail('--to <file.db> is required (or use --dry-run to report without writing).');
99
+ }
100
+
101
+ // Imported through the package's own entry points, so the CLI exercises the
102
+ // same doors a consumer does — a break in the export map fails here too.
103
+ const { indexCorpus } = await import('../dist/esm/doors/rag.js');
104
+ const { byHeading, byParagraph, fixedWithOverlap, wholeDocument } = await import(
105
+ '../dist/esm/doors/rag.js'
106
+ );
107
+ const { sqliteVectorStore, InMemoryStore, mockEmbedder } = await import(
108
+ '../dist/esm/doors/memory.js'
109
+ );
110
+ const providers = await import('../dist/esm/doors/providers.js');
111
+
112
+ const chars = values.chars === undefined ? undefined : Number(values.chars);
113
+ const overlap = values.overlap === undefined ? undefined : Number(values.overlap);
114
+ if (chars !== undefined && !Number.isFinite(chars)) fail('--chars must be a number.');
115
+ if (overlap !== undefined && !Number.isFinite(overlap)) fail('--overlap must be a number.');
116
+
117
+ const splitters = {
118
+ heading: () => byHeading({ ...(chars && { maxChars: chars }), ...(overlap !== undefined && { overlapChars: overlap }) }),
119
+ paragraph: () => byParagraph({ ...(chars && { maxChars: chars }), ...(overlap !== undefined && { overlapChars: overlap }) }),
120
+ fixed: () => fixedWithOverlap({ ...(chars && { chars }), ...(overlap !== undefined && { overlapChars: overlap }) }),
121
+ whole: () => wholeDocument(),
122
+ };
123
+ const splitName = values.split ?? 'heading';
124
+ if (!(splitName in splitters)) {
125
+ fail(`unknown --split '${splitName}'. One of: ${Object.keys(splitters).join(', ')}.`);
126
+ }
127
+
128
+ const embedders = {
129
+ static: () => providers.staticEmbedder(),
130
+ local: () => providers.localEmbedder(),
131
+ openai: () => providers.openaiEmbedder(),
132
+ mock: () => mockEmbedder(),
133
+ };
134
+ const embedderName = values.embedder ?? 'static';
135
+ if (!(embedderName in embedders)) {
136
+ fail(`unknown --embedder '${embedderName}'. One of: ${Object.keys(embedders).join(', ')}.`);
137
+ }
138
+
139
+ let embedder;
140
+ try {
141
+ embedder = embedders[embedderName]();
142
+ } catch (err) {
143
+ fail(err instanceof Error ? err.message : String(err));
144
+ }
145
+
146
+ // --dry-run writes to a throwaway in-memory index, so it reports real chunk
147
+ // counts and real splitting without touching the file. It still EMBEDS —
148
+ // the alternative is reporting a count that the real run might not match.
149
+ const store = values['dry-run'] ? new InMemoryStore() : sqliteVectorStore({ file: resolve(values.to) });
150
+
151
+ try {
152
+ const report = await indexCorpus({
153
+ source: {
154
+ dir,
155
+ recursive: values.recursive,
156
+ ...(values.include && { include: values.include.split(',').map((e) => e.trim()) }),
157
+ },
158
+ store,
159
+ embedder,
160
+ splitter: splitters[splitName](),
161
+ removeMissing: values.remove,
162
+ ...(values.corpus && { corpus: { conversationId: values.corpus } }),
163
+ });
164
+
165
+ if (values.json) {
166
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
167
+ } else {
168
+ const parts = [
169
+ `discovered ${report.discovered}`,
170
+ `loaded ${report.loaded}`,
171
+ `chunks ${report.chunks}`,
172
+ `embedded ${report.embedded}`,
173
+ `skipped ${report.skipped}`,
174
+ `removed ${report.removed}`,
175
+ ];
176
+ process.stdout.write(
177
+ `${values['dry-run'] ? '[dry run] ' : ''}${parts.join(' · ')} (${report.elapsedMs}ms, ${report.splitter}, ${report.embedderFingerprint})\n`,
178
+ );
179
+ for (const failure of report.failed) {
180
+ process.stdout.write(` failed: ${failure.uri} — ${failure.reason}\n`);
181
+ }
182
+ if (report.truncated.length > 0) {
183
+ process.stdout.write(
184
+ ` ${report.truncated.length} chunk(s) longer than the embedder reads — they were clipped. Lower --chars.\n`,
185
+ );
186
+ }
187
+ }
188
+ if (typeof store.close === 'function') store.close();
189
+ } catch (err) {
190
+ process.stderr.write(`agentfootprint-index: ${err instanceof Error ? err.message : String(err)}\n`);
191
+ process.exit(1);
192
+ }
@@ -42,6 +42,7 @@
42
42
  Object.defineProperty(exports, "__esModule", { value: true });
43
43
  exports.cloudwatchObservability = exports._buildCloudWatchObservability = void 0;
44
44
  const lazyRequire_js_1 = require("../../lib/lazyRequire.js");
45
+ const deliveryErrors_js_1 = require("./deliveryErrors.js");
45
46
  // ─── Generic base — also used by agentcoreObservability ──────────────
46
47
  /**
47
48
  * Internal: shared CloudWatch Logs base used by every adapter that
@@ -69,7 +70,20 @@ function _buildCloudWatchObservability(opts, strategyName) {
69
70
  let lastFlushPromise = Promise.resolve();
70
71
  let timer;
71
72
  let stopped = false;
72
- let onErrorHook;
73
+ // What we know about the log stream (8.11.0). This is the per-(group,stream)
74
+ // create latch — group and stream are fixed for the life of a strategy, so
75
+ // one tri-state IS that latch:
76
+ // 'unknown' — never delivered yet, or the stream vanished under us
77
+ // 'created' — we created it, or a put has succeeded against it
78
+ // 'unavailable' — a create attempt failed for a reason retrying won't fix
79
+ // (no permission, missing GROUP). Never attempt again.
80
+ // 'created' is deliberately NOT terminal: a retention policy can delete a
81
+ // stream mid-process, and the next ResourceNotFoundException should heal it.
82
+ // Only 'unavailable' is terminal, which is what stops a create/put loop.
83
+ let streamState = 'unknown';
84
+ // The fallback when the consumer wires nothing. Rate-limited; a
85
+ // consumer-supplied sink is not.
86
+ const consoleSink = (0, deliveryErrors_js_1.rateLimitedConsoleSink)(strategyName);
73
87
  // Lazy-resolved on first flush so consumers who never trigger a
74
88
  // flush (because nothing was emitted) don't even hit the SDK.
75
89
  let client = opts._client;
@@ -87,6 +101,21 @@ function _buildCloudWatchObservability(opts, strategyName) {
87
101
  void doFlush();
88
102
  }, flushIntervalMs);
89
103
  }
104
+ /** Route a delivery failure through whatever `_onError` IS RIGHT NOW.
105
+ * Reading it at call time (rather than capturing a hook at construction)
106
+ * is the whole fix: a consumer who assigns `_onError` after building the
107
+ * strategy gets delivery failures, and one who doesn't still gets the
108
+ * default console sink. */
109
+ function reportFailure(err) {
110
+ strategy._onError?.(err);
111
+ }
112
+ async function putBatch(client, batch) {
113
+ await client.putLogEvents({
114
+ logGroupName: opts.logGroupName,
115
+ logStreamName,
116
+ logEvents: batch,
117
+ });
118
+ }
90
119
  async function doFlush() {
91
120
  if (buffer.length === 0 || stopped)
92
121
  return;
@@ -95,14 +124,57 @@ function _buildCloudWatchObservability(opts, strategyName) {
95
124
  const batch = buffer.splice(0);
96
125
  bufferBytes = 0;
97
126
  try {
98
- await ensureClient().putLogEvents({
99
- logGroupName: opts.logGroupName,
100
- logStreamName,
101
- logEvents: batch,
102
- });
127
+ const client = ensureClient();
128
+ try {
129
+ await putBatch(client, batch);
130
+ streamState = 'created'; // a successful put proves the stream is there
131
+ return;
132
+ }
133
+ catch (err) {
134
+ // The one recoverable shape: CloudWatch rejects a put into a stream
135
+ // that doesn't exist. Before 8.11.0 this adapter asserted that
136
+ // precondition and never established it, so any stream name that
137
+ // didn't already exist dropped every event, forever, in silence —
138
+ // and the documented `${HOSTNAME}/${Date.now()}` convention can never
139
+ // pre-exist, so following the docs guaranteed the bug.
140
+ if (!isMissingStreamError(err) || streamState === 'unavailable')
141
+ throw err;
142
+ if (!client.createLogStream) {
143
+ throw new Error(`log stream '${logStreamName}' does not exist in log group ` +
144
+ `'${opts.logGroupName}', and the injected \`_client\` has no ` +
145
+ `\`createLogStream\` to create it.`);
146
+ }
147
+ try {
148
+ await client.createLogStream({
149
+ logGroupName: opts.logGroupName,
150
+ logStreamName,
151
+ });
152
+ }
153
+ catch (createErr) {
154
+ // Two processes racing to create the same stream is normal and
155
+ // expected — whoever lost the race still has a usable stream.
156
+ if (!isAlreadyExistsError(createErr)) {
157
+ // Anything else (no `logs:CreateLogStream`, or the log GROUP
158
+ // itself is missing) will not fix itself. Latch it off so we
159
+ // never loop, and say which of the two it probably is.
160
+ streamState = 'unavailable';
161
+ throw new Error(`could not create log stream '${logStreamName}' in log group ` +
162
+ `'${opts.logGroupName}': ${errorMessage(createErr)}. Check that the log ` +
163
+ `GROUP exists (this adapter never creates one) and that the role allows ` +
164
+ `\`logs:CreateLogStream\`.`);
165
+ }
166
+ }
167
+ streamState = 'created';
168
+ // Exactly ONE re-put of the same batch. Not a retry loop: if this
169
+ // fails the events are gone, and saying so is better than growing a
170
+ // buffer during an outage.
171
+ await putBatch(client, batch);
172
+ }
103
173
  }
104
174
  catch (err) {
105
- onErrorHook?.(err instanceof Error ? err : new Error(String(err)));
175
+ const cause = errorMessage(err);
176
+ reportFailure(new Error(`${batch.length} event(s) dropped shipping to log group '${opts.logGroupName}' ` +
177
+ `stream '${logStreamName}': ${cause}`));
106
178
  }
107
179
  }
108
180
  function enqueue(event) {
@@ -121,10 +193,16 @@ function _buildCloudWatchObservability(opts, strategyName) {
121
193
  scheduleTimedFlush();
122
194
  }
123
195
  }
124
- return {
196
+ const strategy = {
125
197
  name: strategyName,
126
198
  capabilities: { events: true, logs: true },
127
199
  exportEvent: enqueue,
200
+ /**
201
+ * Drain the buffer. **The framework does not call this for you** — wire it
202
+ * into your own shutdown or the last batch is lost:
203
+ *
204
+ * process.on('SIGTERM', async () => { await strategy.flush(); strategy.stop(); });
205
+ */
128
206
  async flush() {
129
207
  // Drain anything pending. Awaits both an in-flight put AND any
130
208
  // newly-buffered events that arrived during it.
@@ -140,6 +218,8 @@ function _buildCloudWatchObservability(opts, strategyName) {
140
218
  break;
141
219
  }
142
220
  },
221
+ /** Stop the flush timer. **The framework does not call this for you** —
222
+ * an un-stopped strategy keeps a `setTimeout` alive. */
143
223
  stop() {
144
224
  stopped = true;
145
225
  if (timer) {
@@ -147,22 +227,46 @@ function _buildCloudWatchObservability(opts, strategyName) {
147
227
  timer = undefined;
148
228
  }
149
229
  },
230
+ /**
231
+ * Where errors go. Two callers: the dispatch layer (when `exportEvent`
232
+ * itself throws) and this adapter's own delivery path.
233
+ *
234
+ * Before 8.11.0 this method lazily installed the console fallback INSIDE
235
+ * itself, so a delivery failure — which reads the hook rather than calling
236
+ * this method — found `undefined` and vanished. The fallback now exists
237
+ * from construction, and delivery failures route through this method,
238
+ * which means overriding it works: assign `_onError`, or pass `onError`
239
+ * in the factory options, and you receive delivery failures too.
240
+ */
150
241
  _onError(err, event) {
151
- // Capture for use inside doFlush (the strategy doesn't know what
152
- // the consumer's error sink is unless they wire `_onError` via
153
- // the strategy options. We store it on this hook so put-failures
154
- // route correctly).
155
- onErrorHook =
156
- onErrorHook ??
157
- ((e) => {
158
- // eslint-disable-next-line no-console
159
- console.error(`[${strategyName}Observability] flush failed:`, e.message);
160
- });
161
- onErrorHook(err, event);
242
+ (opts.onError ?? consoleSink)(err, event);
162
243
  },
163
244
  };
245
+ return strategy;
164
246
  }
165
247
  exports._buildCloudWatchObservability = _buildCloudWatchObservability;
248
+ // ─── Error shape helpers ─────────────────────────────────────────────
249
+ function errorMessage(err) {
250
+ return err instanceof Error ? err.message : String(err);
251
+ }
252
+ /**
253
+ * Did this put fail because the log stream isn't there?
254
+ *
255
+ * Matched on `name` first (how the AWS SDK v3 surfaces it) with a message
256
+ * fallback, because a transport shim or an injected test double may carry the
257
+ * code only in the text.
258
+ */
259
+ function isMissingStreamError(err) {
260
+ if (err?.name === 'ResourceNotFoundException')
261
+ return true;
262
+ return /ResourceNotFoundException|log stream does not exist/i.test(errorMessage(err));
263
+ }
264
+ /** Lost a create race with another process. Not an error — the stream exists. */
265
+ function isAlreadyExistsError(err) {
266
+ if (err?.name === 'ResourceAlreadyExistsException')
267
+ return true;
268
+ return /ResourceAlreadyExistsException|already exists/i.test(errorMessage(err));
269
+ }
166
270
  // ─── Public factory: cloudwatchObservability ─────────────────────────
167
271
  /**
168
272
  * Generic CloudWatch Logs observability adapter. See
@@ -191,6 +295,7 @@ function createCloudWatchClient(region, strategyName) {
191
295
  `\`CloudWatchLogsClient\` / \`PutLogEventsCommand\` was not found. Update the SDK.`);
192
296
  }
193
297
  const sdkClient = new mod.CloudWatchLogsClient({ ...(region && { region }) });
298
+ const createLogStreamCommand = mod.CreateLogStreamCommand;
194
299
  return {
195
300
  async putLogEvents(input) {
196
301
  // Cast the SDK constructor to the call shape — same trick as
@@ -198,6 +303,15 @@ function createCloudWatchClient(region, strategyName) {
198
303
  const cmd = new mod.PutLogEventsCommand(input);
199
304
  await sdkClient.send(cmd);
200
305
  },
306
+ // Only offered when the installed SDK has the command. An older SDK
307
+ // without it degrades to a reported failure rather than a crash — the
308
+ // adapter checks for this method before reaching for it.
309
+ ...(createLogStreamCommand && {
310
+ async createLogStream(input) {
311
+ const cmd = new createLogStreamCommand(input);
312
+ await sdkClient.send(cmd);
313
+ },
314
+ }),
201
315
  };
202
316
  }
203
317
  //# sourceMappingURL=cloudwatch.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cloudwatch.js","sourceRoot":"","sources":["../../../src/adapters/observability/cloudwatch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cloudwatch.js","sourceRoot":"","sources":["../../../src/adapters/observability/cloudwatch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ /**
3
+ * deliveryErrors — where an exporter's failures go when nobody is listening.
4
+ *
5
+ * **Telemetry that fails invisibly is indistinguishable from telemetry that
6
+ * works.** Every network-shipping observability adapter has the same problem:
7
+ * `exportEvent` is sync and non-throwing by contract, so the actual delivery
8
+ * happens later, inside a flush the caller never sees. If that flush fails and
9
+ * nothing is wired up, the process keeps running, the dashboard stays empty,
10
+ * and nobody learns anything until someone goes looking for a run that isn't
11
+ * there.
12
+ *
13
+ * Before 8.11.0 each adapter installed its console fallback lazily, INSIDE its
14
+ * own `_onError` method — so a delivery failure, which read the hook rather
15
+ * than calling the method, found `undefined` and vanished. Every CloudWatch,
16
+ * X-Ray and OTEL delivery failure was silent in the ordinary case.
17
+ *
18
+ * The rule this module encodes: **an unheard failure still gets said out loud,
19
+ * but saying it must not become the second outage.** A backend that has been
20
+ * down for an hour would otherwise produce one `console.error` per flush
21
+ * interval for an hour.
22
+ *
23
+ * Role: leaf helper for `adapters/observability/*`. No imports beyond the
24
+ * event type; composed as a field, never inherited.
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.rateLimitedConsoleSink = void 0;
28
+ /**
29
+ * The default sink: loud on the first failure, then logarithmically quieter.
30
+ *
31
+ * Reports at failures 1, 2, 4, 8, 16, 32 … and stamps the running count, so an
32
+ * ongoing outage is visible (you can see the number climbing) without flooding
33
+ * the log. A consumer-supplied sink is NOT rate-limited — they asked for every
34
+ * failure and can do their own filtering.
35
+ *
36
+ * @param strategyName the adapter's `name` (e.g. `'cloudwatch'`), used to
37
+ * prefix the line so a multi-exporter process says which one failed.
38
+ */
39
+ function rateLimitedConsoleSink(strategyName) {
40
+ let failures = 0;
41
+ let reportAt = 1;
42
+ return (error) => {
43
+ failures++;
44
+ if (failures < reportAt)
45
+ return;
46
+ // Double the gap each time we speak, so the log cost is logarithmic in
47
+ // the length of the outage.
48
+ reportAt = failures * 2;
49
+ const count = failures > 1 ? ` (delivery failure #${failures})` : '';
50
+ // eslint-disable-next-line no-console
51
+ console.error(`[${strategyName}Observability] delivery failed${count}:`, error.message);
52
+ };
53
+ }
54
+ exports.rateLimitedConsoleSink = rateLimitedConsoleSink;
55
+ //# sourceMappingURL=deliveryErrors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deliveryErrors.js","sourceRoot":"","sources":["../../../src/adapters/observability/deliveryErrors.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;;;AAOH;;;;;;;;;;GAUG;AACH,SAAgB,sBAAsB,CAAC,YAAoB;IACzD,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,OAAO,CAAC,KAAY,EAAQ,EAAE;QAC5B,QAAQ,EAAE,CAAC;QACX,IAAI,QAAQ,GAAG,QAAQ;YAAE,OAAO;QAChC,uEAAuE;QACvE,4BAA4B;QAC5B,QAAQ,GAAG,QAAQ,GAAG,CAAC,CAAC;QACxB,MAAM,KAAK,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,uBAAuB,QAAQ,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACrE,sCAAsC;QACtC,OAAO,CAAC,KAAK,CAAC,IAAI,YAAY,iCAAiC,KAAK,GAAG,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC1F,CAAC,CAAC;AACJ,CAAC;AAbD,wDAaC"}