hippo-memory 1.71.0 → 2.0.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 (625) hide show
  1. package/README.md +7 -5
  2. package/dist/agent-memories/apply.d.ts +0 -11
  3. package/dist/agent-memories/apply.js +11 -26
  4. package/dist/agent-memories/claude-code.js +1 -1
  5. package/dist/agent-memories/codex.js +1 -1
  6. package/dist/agent-memories/copilot.d.ts +1 -3
  7. package/dist/agent-memories/copilot.js +1 -17
  8. package/dist/agent-memories/project-folds.d.ts +10 -0
  9. package/dist/agent-memories/project-folds.js +34 -0
  10. package/dist/agent-memories/sync.js +8 -6
  11. package/dist/api/assemble.js +3 -2
  12. package/dist/api/audit.d.ts +30 -2
  13. package/dist/api/audit.js +48 -7
  14. package/dist/api/auth.d.ts +11 -9
  15. package/dist/api/auth.js +39 -40
  16. package/dist/api/auto-sleep.d.ts +5 -2
  17. package/dist/api/auto-sleep.js +7 -3
  18. package/dist/api/availability.js +2 -3
  19. package/dist/api/cards.d.ts +42 -0
  20. package/dist/api/cards.js +52 -0
  21. package/dist/api/conflicts.d.ts +28 -0
  22. package/dist/api/conflicts.js +61 -0
  23. package/dist/api/context-auto.js +4 -2
  24. package/dist/api/context-render.js +4 -3
  25. package/dist/api/context-select.js +15 -29
  26. package/dist/api/context.js +29 -18
  27. package/dist/api/continuity.d.ts +47 -0
  28. package/dist/api/continuity.js +66 -0
  29. package/dist/api/dormant.js +4 -2
  30. package/dist/api/drill-down.js +3 -2
  31. package/dist/api/embeddings.d.ts +17 -0
  32. package/dist/api/embeddings.js +28 -0
  33. package/dist/api/error-reply.d.ts +8 -0
  34. package/dist/api/error-reply.js +21 -0
  35. package/dist/api/forget.d.ts +7 -3
  36. package/dist/api/forget.js +24 -14
  37. package/dist/api/goals.d.ts +1 -1
  38. package/dist/api/goals.js +5 -9
  39. package/dist/api/graph.js +2 -1
  40. package/dist/api/index.d.ts +4 -2
  41. package/dist/api/index.js +4 -2
  42. package/dist/api/instruction-detect.js +5 -1
  43. package/dist/api/learn.d.ts +5 -1
  44. package/dist/api/learn.js +71 -44
  45. package/dist/api/ledger-db.d.ts +2 -6
  46. package/dist/api/ledger-db.js +4 -36
  47. package/dist/api/memories.d.ts +21 -1
  48. package/dist/api/memories.js +51 -1
  49. package/dist/api/objects.d.ts +1 -1
  50. package/dist/api/objects.js +2 -1
  51. package/dist/api/outcome.d.ts +5 -5
  52. package/dist/api/outcome.js +17 -16
  53. package/dist/api/pilot-arm.d.ts +1 -11
  54. package/dist/api/pilot-arm.js +5 -45
  55. package/dist/api/predictions.d.ts +3 -1
  56. package/dist/api/predictions.js +8 -1
  57. package/dist/api/projects.d.ts +21 -0
  58. package/dist/api/projects.js +24 -0
  59. package/dist/api/promote.d.ts +3 -3
  60. package/dist/api/promote.js +26 -28
  61. package/dist/api/prompt-hook.d.ts +17 -2
  62. package/dist/api/prompt-hook.js +45 -4
  63. package/dist/api/quarantine.js +2 -1
  64. package/dist/api/recall-cli.d.ts +3 -0
  65. package/dist/api/recall-cli.js +13 -4
  66. package/dist/api/recall-core.js +10 -5
  67. package/dist/api/recall-pipeline.js +23 -24
  68. package/dist/api/recall-request.js +1 -1
  69. package/dist/api/recall-types.d.ts +3 -1
  70. package/dist/api/recall.d.ts +0 -3
  71. package/dist/api/recall.js +51 -73
  72. package/dist/api/remember-local.d.ts +5 -2
  73. package/dist/api/remember-local.js +15 -7
  74. package/dist/api/remember.d.ts +7 -3
  75. package/dist/api/remember.js +19 -16
  76. package/dist/api/session-end.d.ts +46 -0
  77. package/dist/api/session-end.js +131 -0
  78. package/dist/api/sleep-run.d.ts +1 -1
  79. package/dist/api/sleep-run.js +16 -11
  80. package/dist/api/sleep.js +4 -3
  81. package/dist/api/status.d.ts +24 -0
  82. package/dist/api/status.js +40 -0
  83. package/dist/api/store-init.d.ts +4 -0
  84. package/dist/api/store-init.js +9 -0
  85. package/dist/api/tokens.js +2 -2
  86. package/dist/api/types.d.ts +3 -7
  87. package/dist/api/types.js +6 -1
  88. package/dist/capture/caller-session.d.ts +0 -3
  89. package/dist/capture/caller-session.js +1 -12
  90. package/dist/capture/capture-error.d.ts +2 -2
  91. package/dist/capture/capture-error.js +23 -18
  92. package/dist/capture/codex-patch.js +3 -1
  93. package/dist/capture/command.js +36 -95
  94. package/dist/capture/compact.js +10 -5
  95. package/dist/capture/compaction-items-caller.js +11 -20
  96. package/dist/capture/compaction-record.d.ts +7 -93
  97. package/dist/capture/compaction-record.js +29 -426
  98. package/dist/capture/compaction-spool.d.ts +7 -7
  99. package/dist/capture/compaction-spool.js +60 -51
  100. package/dist/capture/copilot-transcript.d.ts +2 -1
  101. package/dist/capture/copilot-transcript.js +2 -1
  102. package/dist/capture/extract.d.ts +5 -2
  103. package/dist/capture/extract.js +4 -3
  104. package/dist/capture/failure-caller.d.ts +1 -1
  105. package/dist/capture/failure-caller.js +21 -16
  106. package/dist/capture/failure-reading.d.ts +2 -1
  107. package/dist/capture/failure-reading.js +7 -9
  108. package/dist/capture/pre-compact-caller.js +5 -4
  109. package/dist/capture/session-digest.js +4 -3
  110. package/dist/capture/session-end-caller.js +3 -2
  111. package/dist/capture/session-worker.js +22 -10
  112. package/dist/capture/transcript.d.ts +2 -1
  113. package/dist/capture/transcript.js +6 -3
  114. package/dist/capture/working-state.d.ts +4 -2
  115. package/dist/capture/working-state.js +4 -2
  116. package/dist/cli/audit-prune.d.ts +0 -25
  117. package/dist/cli/audit-prune.js +1 -27
  118. package/dist/cli/audit.js +20 -20
  119. package/dist/cli/auth.d.ts +1 -1
  120. package/dist/cli/auth.js +16 -17
  121. package/dist/cli/briefs.d.ts +1 -4
  122. package/dist/cli/briefs.js +37 -292
  123. package/dist/cli/card.js +41 -33
  124. package/dist/cli/client.d.ts +6 -2
  125. package/dist/cli/client.js +37 -21
  126. package/dist/cli/compact-resume-payload.js +1 -1
  127. package/dist/cli/context.d.ts +1 -1
  128. package/dist/cli/context.js +13 -13
  129. package/dist/cli/continuity.d.ts +1 -1
  130. package/dist/cli/continuity.js +36 -39
  131. package/dist/cli/curate.d.ts +2 -2
  132. package/dist/cli/curate.js +32 -36
  133. package/dist/cli/dag.d.ts +1 -1
  134. package/dist/cli/dag.js +23 -8
  135. package/dist/cli/decisions.d.ts +0 -2
  136. package/dist/cli/decisions.js +7 -285
  137. package/dist/cli/eval.d.ts +1 -1
  138. package/dist/cli/eval.js +12 -13
  139. package/dist/cli/explain.d.ts +1 -1
  140. package/dist/cli/explain.js +21 -25
  141. package/dist/cli/flag-values.js +16 -6
  142. package/dist/cli/flags.d.ts +0 -289
  143. package/dist/cli/flags.js +3 -138
  144. package/dist/cli/graph.d.ts +3 -0
  145. package/dist/cli/graph.js +83 -0
  146. package/dist/cli/hook-runtime.d.ts +6 -6
  147. package/dist/cli/hook-runtime.js +9 -36
  148. package/dist/cli/incidents.d.ts +3 -0
  149. package/dist/cli/incidents.js +112 -0
  150. package/dist/cli/init.d.ts +1 -1
  151. package/dist/cli/init.js +28 -19
  152. package/dist/cli/install-steps.d.ts +2 -1
  153. package/dist/cli/install-steps.js +11 -3
  154. package/dist/cli/last-sleep.js +1 -1
  155. package/dist/cli/maintenance.d.ts +3 -3
  156. package/dist/cli/maintenance.js +29 -30
  157. package/dist/cli/notes.d.ts +2 -0
  158. package/dist/cli/notes.js +41 -0
  159. package/dist/cli/object-verbs.d.ts +2 -2
  160. package/dist/cli/object-verbs.js +4 -4
  161. package/dist/cli/open-browser.d.ts +14 -0
  162. package/dist/cli/open-browser.js +26 -0
  163. package/dist/cli/playbooks.d.ts +3 -4
  164. package/dist/cli/playbooks.js +126 -290
  165. package/dist/cli/predictions.d.ts +3 -0
  166. package/dist/cli/predictions.js +174 -0
  167. package/dist/cli/print.js +2 -1
  168. package/dist/cli/projects.js +30 -22
  169. package/dist/cli/quality-repair-once.d.ts +1 -1
  170. package/dist/cli/quality-repair-once.js +6 -5
  171. package/dist/cli/recall.js +13 -7
  172. package/dist/cli/refine-llm.js +18 -17
  173. package/dist/cli/remember.js +17 -17
  174. package/dist/cli/scheduler.js +2 -1
  175. package/dist/cli/serve.js +11 -6
  176. package/dist/cli/session-hooks.d.ts +1 -5
  177. package/dist/cli/session-hooks.js +89 -168
  178. package/dist/cli/setup.d.ts +1 -1
  179. package/dist/cli/setup.js +17 -10
  180. package/dist/cli/shared.d.ts +2 -6
  181. package/dist/cli/shared.js +9 -29
  182. package/dist/cli/sleep-render.js +6 -3
  183. package/dist/cli/sleep.js +6 -37
  184. package/dist/cli/status.d.ts +3 -3
  185. package/dist/cli/status.js +32 -35
  186. package/dist/cli/transfer.d.ts +3 -3
  187. package/dist/cli/transfer.js +64 -43
  188. package/dist/cli/usage.d.ts +1 -80
  189. package/dist/cli/usage.js +2 -696
  190. package/dist/cli/verb-row.d.ts +34 -0
  191. package/dist/cli/verb-row.js +6 -0
  192. package/dist/cli/verbs/health.d.ts +27 -0
  193. package/dist/cli/verbs/health.js +234 -0
  194. package/dist/cli/verbs/memory.d.ts +16 -0
  195. package/dist/cli/verbs/memory.js +236 -0
  196. package/dist/cli/verbs/objects.d.ts +20 -0
  197. package/dist/cli/verbs/objects.js +257 -0
  198. package/dist/cli/verbs/sessions.d.ts +31 -0
  199. package/dist/cli/verbs/sessions.js +314 -0
  200. package/dist/cli/verbs.d.ts +81 -0
  201. package/dist/cli/verbs.js +7 -0
  202. package/dist/cli/versioned-verbs.d.ts +51 -0
  203. package/dist/cli/versioned-verbs.js +79 -0
  204. package/dist/cli.d.ts +1 -272
  205. package/dist/cli.js +24 -315
  206. package/dist/connectors/dlq.d.ts +39 -3
  207. package/dist/connectors/dlq.js +39 -4
  208. package/dist/connectors/github/backfill.js +12 -3
  209. package/dist/connectors/github/deletion.js +2 -1
  210. package/dist/connectors/github/dlq.js +31 -73
  211. package/dist/connectors/github/ingest.d.ts +2 -6
  212. package/dist/connectors/github/ingest.js +5 -29
  213. package/dist/connectors/github/tenant-routing.js +2 -1
  214. package/dist/connectors/github/webhook.d.ts +2 -1
  215. package/dist/connectors/github/webhook.js +35 -2
  216. package/dist/connectors/ingest.d.ts +10 -0
  217. package/dist/connectors/ingest.js +29 -0
  218. package/dist/connectors/slack/deletion.js +2 -1
  219. package/dist/connectors/slack/dlq.js +49 -71
  220. package/dist/connectors/slack/ingest.d.ts +2 -6
  221. package/dist/connectors/slack/ingest.js +4 -28
  222. package/dist/connectors/slack/tenant-routing.js +2 -1
  223. package/dist/connectors/slack/webhook.d.ts +2 -1
  224. package/dist/connectors/slack/webhook.js +2 -1
  225. package/dist/consolidate/conflicts.js +2 -2
  226. package/dist/consolidate/dag.js +29 -18
  227. package/dist/consolidate/dedupe.d.ts +7 -0
  228. package/dist/consolidate/dedupe.js +6 -4
  229. package/dist/consolidate/llm-passes.js +45 -19
  230. package/dist/consolidate/memory-value.js +3 -2
  231. package/dist/consolidate/merge.js +8 -8
  232. package/dist/consolidate/physics-pass.d.ts +6 -1
  233. package/dist/consolidate/physics-pass.js +26 -6
  234. package/dist/consolidate/replay.js +11 -4
  235. package/dist/consolidate/sleep.js +9 -6
  236. package/dist/consolidate/traces.js +5 -5
  237. package/dist/core/ablation.d.ts +10 -0
  238. package/dist/core/ablation.js +12 -0
  239. package/dist/{sharing/scope.d.ts → core/active-scope.d.ts} +1 -1
  240. package/dist/{sharing/scope.js → core/active-scope.js} +1 -1
  241. package/dist/core/ambient.d.ts +4 -0
  242. package/dist/core/ambient.js +39 -18
  243. package/dist/core/api-errors.d.ts +16 -0
  244. package/dist/core/api-errors.js +20 -0
  245. package/dist/core/card.js +3 -1
  246. package/dist/core/compaction-timing.d.ts +7 -0
  247. package/dist/core/compaction-timing.js +9 -0
  248. package/dist/core/config.d.ts +1 -1
  249. package/dist/core/config.js +22 -18
  250. package/dist/core/cosine.d.ts +3 -0
  251. package/dist/core/cosine.js +19 -0
  252. package/dist/{store → core}/markdown.d.ts +1 -1
  253. package/dist/{store → core}/markdown.js +4 -4
  254. package/dist/core/memory-audit.d.ts +17 -0
  255. package/dist/core/memory-audit.js +54 -0
  256. package/dist/core/memory.d.ts +14 -8
  257. package/dist/core/memory.js +35 -23
  258. package/dist/{consolidate → core}/merged-row.d.ts +1 -1
  259. package/dist/{consolidate → core}/merged-row.js +1 -1
  260. package/dist/core/physics.d.ts +3 -1
  261. package/dist/core/physics.js +23 -18
  262. package/dist/core/prediction.d.ts +3 -0
  263. package/dist/core/prediction.js +6 -0
  264. package/dist/core/project-identity.d.ts +3 -0
  265. package/dist/core/project-identity.js +12 -2
  266. package/dist/{store → core}/recall-scope.d.ts +1 -10
  267. package/dist/{store → core}/recall-scope.js +2 -19
  268. package/dist/core/search-types.d.ts +1 -1
  269. package/dist/dashboard/dashboard-actions.d.ts +2 -2
  270. package/dist/dashboard/dashboard-actions.js +5 -5
  271. package/dist/dashboard/dashboard-queries.js +2 -7
  272. package/dist/dashboard/dashboard-snapshot.js +1 -3
  273. package/dist/dashboard/dashboard.js +16 -5
  274. package/dist/db/busy.d.ts +2 -7
  275. package/dist/db/busy.js +21 -26
  276. package/dist/db/connect.d.ts +2 -2
  277. package/dist/db/connect.js +7 -7
  278. package/dist/db/index.d.ts +3 -3
  279. package/dist/db/index.js +3 -3
  280. package/dist/db/migrate.js +14 -7
  281. package/dist/db/migrations/index.d.ts +1 -1
  282. package/dist/db/migrations/index.js +3 -1
  283. package/dist/db/migrations/v11.js +1 -0
  284. package/dist/db/migrations/v14.js +1 -0
  285. package/dist/db/migrations/v55.d.ts +3 -0
  286. package/dist/db/migrations/v55.js +17 -0
  287. package/dist/db/open.d.ts +0 -2
  288. package/dist/db/open.js +6 -10
  289. package/dist/db/physics-state.d.ts +0 -4
  290. package/dist/db/physics-state.js +7 -33
  291. package/dist/db/request-stores.d.ts +1 -0
  292. package/dist/db/request-stores.js +2 -1
  293. package/dist/db/tables.d.ts +2 -0
  294. package/dist/db/tables.js +6 -2
  295. package/dist/db/vector-store.js +19 -5
  296. package/dist/db/wal-checkpointer.js +2 -1
  297. package/dist/doctor.d.ts +3 -0
  298. package/dist/doctor.js +18 -15
  299. package/dist/embeddings/local.d.ts +62 -0
  300. package/dist/embeddings/local.js +146 -0
  301. package/dist/{store/embeddings → embeddings}/provider.d.ts +5 -2
  302. package/dist/{store/embeddings → embeddings}/provider.js +19 -25
  303. package/dist/embeddings/transformers.d.ts +26 -0
  304. package/dist/embeddings/transformers.js +89 -0
  305. package/dist/entry/session-text.d.ts +1 -1
  306. package/dist/entry/session-text.js +1 -1
  307. package/dist/entry/store-kit.d.ts +37 -0
  308. package/dist/entry/store-kit.js +25 -0
  309. package/dist/entry/testing.d.ts +6 -0
  310. package/dist/entry/testing.js +7 -0
  311. package/dist/eval/eval-suite.js +17 -11
  312. package/dist/eval/eval.js +1 -1
  313. package/dist/eval/judgment.js +12 -5
  314. package/dist/graph/extract.js +3 -3
  315. package/dist/graph/recall.js +1 -1
  316. package/dist/graph/view.js +13 -5
  317. package/dist/graph/write.d.ts +1 -1
  318. package/dist/graph/write.js +9 -9
  319. package/dist/hooks/codex-session.js +3 -2
  320. package/dist/hooks/codex-wrapper.js +2 -1
  321. package/dist/hooks/json-hooks.js +10 -4
  322. package/dist/hooks/opencode.js +8 -5
  323. package/dist/hooks/shared.d.ts +0 -6
  324. package/dist/hooks/shared.js +4 -15
  325. package/dist/importers/core.js +4 -4
  326. package/dist/importers/sources.js +5 -4
  327. package/dist/importers/vault.d.ts +1 -1
  328. package/dist/importers/vault.js +25 -23
  329. package/dist/index.d.ts +5 -4
  330. package/dist/index.js +4 -3
  331. package/dist/learn/autolearn.d.ts +15 -3
  332. package/dist/learn/autolearn.js +38 -16
  333. package/dist/learn/churn-git.js +13 -4
  334. package/dist/learn/extract.d.ts +3 -2
  335. package/dist/learn/extract.js +7 -5
  336. package/dist/learn/forward-claim-detector.js +4 -2
  337. package/dist/learn/invalidation.d.ts +2 -1
  338. package/dist/learn/invalidation.js +18 -9
  339. package/dist/mcp/admin-tools.d.ts +2 -2
  340. package/dist/mcp/admin-tools.js +27 -48
  341. package/dist/mcp/format.js +3 -2
  342. package/dist/mcp/framing.d.ts +1 -0
  343. package/dist/mcp/framing.js +1 -1
  344. package/dist/mcp/memory-tools.d.ts +1 -1
  345. package/dist/mcp/memory-tools.js +8 -9
  346. package/dist/mcp/protocol.d.ts +11 -7
  347. package/dist/mcp/protocol.js +25 -12
  348. package/dist/mcp/recall-tools.js +12 -9
  349. package/dist/mcp/request.js +22 -20
  350. package/dist/mcp/stdio.js +37 -16
  351. package/dist/mcp/tool-args.js +2 -1
  352. package/dist/mcp/tools.js +44 -21
  353. package/dist/objects/customer-notes.d.ts +2 -2
  354. package/dist/objects/decisions.d.ts +2 -2
  355. package/dist/objects/descriptor.d.ts +1 -1
  356. package/dist/objects/incidents.d.ts +2 -2
  357. package/dist/objects/lifecycle.d.ts +1 -1
  358. package/dist/objects/lifecycle.js +2 -1
  359. package/dist/objects/policies.d.ts +2 -2
  360. package/dist/objects/policies.js +2 -1
  361. package/dist/objects/processes.d.ts +2 -2
  362. package/dist/objects/project-briefs.d.ts +2 -2
  363. package/dist/objects/project-briefs.js +2 -3
  364. package/dist/objects/skills.d.ts +2 -2
  365. package/dist/postinstall.js +1 -1
  366. package/dist/rerankers/clef.js +3 -6
  367. package/dist/rerankers/cross-encoder.d.ts +31 -2
  368. package/dist/rerankers/cross-encoder.js +71 -98
  369. package/dist/rerankers/index.js +5 -4
  370. package/dist/rerankers/jev.js +2 -4
  371. package/dist/rerankers/outage-warning.d.ts +3 -1
  372. package/dist/rerankers/outage-warning.js +5 -3
  373. package/dist/search/bm25-search.js +1 -1
  374. package/dist/search/boosts.js +1 -1
  375. package/dist/search/embed-memories.d.ts +12 -0
  376. package/dist/search/embed-memories.js +389 -0
  377. package/dist/search/engine.d.ts +8 -0
  378. package/dist/search/engine.js +10 -0
  379. package/dist/search/goal-boost.d.ts +1 -1
  380. package/dist/search/goal-boost.js +6 -3
  381. package/dist/search/hybrid.d.ts +9 -2
  382. package/dist/search/hybrid.js +9 -4
  383. package/dist/search/physics-search.d.ts +6 -3
  384. package/dist/search/physics-search.js +22 -15
  385. package/dist/search/rerank.js +1 -1
  386. package/dist/search/vector.d.ts +8 -6
  387. package/dist/search/vector.js +14 -17
  388. package/dist/server/auth.d.ts +3 -1
  389. package/dist/server/auth.js +24 -15
  390. package/dist/server/boot.d.ts +2 -1
  391. package/dist/server/boot.js +34 -84
  392. package/dist/server/client-ip.d.ts +9 -6
  393. package/dist/server/client-ip.js +36 -17
  394. package/dist/server/deadline.d.ts +0 -2
  395. package/dist/server/deadline.js +1 -7
  396. package/dist/server/defaults.d.ts +1 -0
  397. package/dist/server/defaults.js +1 -0
  398. package/dist/server/mcp-http.js +16 -11
  399. package/dist/server/request.js +7 -2
  400. package/dist/server/route-checks.d.ts +7 -0
  401. package/dist/server/route-checks.js +42 -0
  402. package/dist/server/route-table.d.ts +9 -8
  403. package/dist/server/route-table.js +127 -133
  404. package/dist/server/routes/admin.d.ts +8 -8
  405. package/dist/server/routes/admin.js +13 -21
  406. package/dist/server/routes/customer-notes.d.ts +6 -6
  407. package/dist/server/routes/customer-notes.js +1 -2
  408. package/dist/server/routes/decisions.d.ts +6 -6
  409. package/dist/server/routes/decisions.js +2 -4
  410. package/dist/server/routes/incidents.d.ts +6 -6
  411. package/dist/server/routes/incidents.js +2 -5
  412. package/dist/server/routes/memories.d.ts +9 -9
  413. package/dist/server/routes/memories.js +19 -40
  414. package/dist/server/routes/object-routes.d.ts +6 -6
  415. package/dist/server/routes/object-routes.js +4 -7
  416. package/dist/server/routes/policies.d.ts +7 -7
  417. package/dist/server/routes/policies.js +2 -4
  418. package/dist/server/routes/predictions.d.ts +6 -6
  419. package/dist/server/routes/predictions.js +9 -13
  420. package/dist/server/routes/processes.d.ts +6 -6
  421. package/dist/server/routes/processes.js +1 -2
  422. package/dist/server/routes/project-briefs.d.ts +7 -7
  423. package/dist/server/routes/project-briefs.js +2 -4
  424. package/dist/server/routes/recall.d.ts +5 -5
  425. package/dist/server/routes/recall.js +6 -10
  426. package/dist/server/routes/skills.d.ts +7 -7
  427. package/dist/server/routes/skills.js +2 -4
  428. package/dist/server/server-detect.d.ts +7 -1
  429. package/dist/server/server-detect.js +36 -11
  430. package/dist/server/sleep-child.js +26 -7
  431. package/dist/server/sleep-offload.d.ts +11 -5
  432. package/dist/server/sleep-offload.js +17 -11
  433. package/dist/server/tls.d.ts +7 -2
  434. package/dist/server/tls.js +17 -5
  435. package/dist/server/types.d.ts +29 -12
  436. package/dist/server/validation.d.ts +1 -2
  437. package/dist/server/validation.js +6 -11
  438. package/dist/server.d.ts +4 -40
  439. package/dist/server.js +3 -29
  440. package/dist/sharing/global-store.d.ts +4 -5
  441. package/dist/sharing/global-store.js +8 -13
  442. package/dist/sharing/global-sync.js +38 -45
  443. package/dist/sharing/project-merge.d.ts +7 -14
  444. package/dist/sharing/project-merge.js +57 -121
  445. package/dist/sharing/search-both.js +10 -3
  446. package/dist/sharing/share.d.ts +2 -0
  447. package/dist/sharing/share.js +39 -38
  448. package/dist/store/ambient.js +7 -7
  449. package/dist/store/audit-event.d.ts +1 -1
  450. package/dist/store/audit.d.ts +0 -16
  451. package/dist/store/audit.js +4 -55
  452. package/dist/store/auth.d.ts +4 -2
  453. package/dist/store/auth.js +7 -3
  454. package/dist/store/candidates.d.ts +3 -2
  455. package/dist/store/candidates.js +59 -30
  456. package/dist/store/capture-write.d.ts +17 -0
  457. package/dist/store/capture-write.js +35 -0
  458. package/dist/store/cards.js +6 -5
  459. package/dist/store/compaction-caller.d.ts +18 -0
  460. package/dist/store/compaction-caller.js +29 -0
  461. package/dist/store/compactions-record.d.ts +115 -0
  462. package/dist/store/compactions-record.js +418 -0
  463. package/dist/store/compactions.js +6 -4
  464. package/dist/store/conflicts.d.ts +7 -1
  465. package/dist/store/conflicts.js +13 -9
  466. package/dist/store/connectors/dlq.d.ts +10 -0
  467. package/dist/store/connectors/dlq.js +33 -0
  468. package/dist/store/connectors/event-log.d.ts +13 -0
  469. package/dist/store/connectors/event-log.js +24 -0
  470. package/dist/store/connectors/github.d.ts +7 -9
  471. package/dist/store/connectors/github.js +35 -60
  472. package/dist/store/connectors/slack.d.ts +2 -11
  473. package/dist/store/connectors/slack.js +24 -57
  474. package/dist/store/dashboard-reads.js +8 -6
  475. package/dist/store/delete-and-batch.d.ts +6 -0
  476. package/dist/store/delete-and-batch.js +40 -6
  477. package/dist/store/delivery-recorder.d.ts +12 -5
  478. package/dist/store/delivery-recorder.js +13 -11
  479. package/dist/store/descendant-page.js +8 -4
  480. package/dist/store/dormant.d.ts +1 -1
  481. package/dist/store/dormant.js +10 -4
  482. package/dist/store/embeddings/index.d.ts +2 -12
  483. package/dist/store/embeddings/index.js +5 -374
  484. package/dist/store/entry-reads.d.ts +35 -4
  485. package/dist/store/entry-reads.js +86 -19
  486. package/dist/store/entry-row.js +9 -2
  487. package/dist/store/entry-writes.d.ts +15 -12
  488. package/dist/store/entry-writes.js +54 -22
  489. package/dist/store/failure-log-at.d.ts +8 -0
  490. package/dist/store/failure-log-at.js +16 -0
  491. package/dist/store/failure-log.d.ts +2 -2
  492. package/dist/store/failure-log.js +5 -3
  493. package/dist/{trust → store}/gated-write.d.ts +3 -1
  494. package/dist/{trust → store}/gated-write.js +7 -5
  495. package/dist/store/goals.js +22 -19
  496. package/dist/store/graph-reads.js +9 -6
  497. package/dist/store/graph-view-rows.js +2 -1
  498. package/dist/store/graph-writes.d.ts +6 -6
  499. package/dist/store/graph-writes.js +48 -71
  500. package/dist/store/half-life.js +2 -2
  501. package/dist/store/handoffs.d.ts +10 -4
  502. package/dist/store/handoffs.js +10 -9
  503. package/dist/store/index-and-stats.js +2 -2
  504. package/dist/store/index.d.ts +2 -2
  505. package/dist/store/index.js +1 -1
  506. package/dist/store/key-writes.js +1 -1
  507. package/dist/store/ledger-turn.d.ts +9 -0
  508. package/dist/store/ledger-turn.js +38 -0
  509. package/dist/store/mirrors.js +19 -10
  510. package/dist/store/open.d.ts +2 -5
  511. package/dist/store/open.js +42 -54
  512. package/dist/store/pilot-arm.d.ts +12 -0
  513. package/dist/store/pilot-arm.js +42 -0
  514. package/dist/store/port.d.ts +68 -6
  515. package/dist/store/port.js +8 -0
  516. package/dist/store/predictions.d.ts +1 -2
  517. package/dist/store/predictions.js +3 -6
  518. package/dist/store/project-tags.d.ts +62 -0
  519. package/dist/store/project-tags.js +67 -0
  520. package/dist/store/quality-repair.js +1 -1
  521. package/dist/store/quarantine.js +2 -1
  522. package/dist/store/recall-trace.d.ts +2 -0
  523. package/dist/store/recall-trace.js +20 -3
  524. package/dist/store/rejected-values.js +16 -10
  525. package/dist/store/rejection-probe.d.ts +4 -0
  526. package/dist/store/rejection-probe.js +19 -0
  527. package/dist/store/rejection.d.ts +0 -17
  528. package/dist/store/rejection.js +1 -21
  529. package/dist/store/rows.js +2 -2
  530. package/dist/store/rule-sql.d.ts +11 -0
  531. package/dist/store/rule-sql.js +31 -6
  532. package/dist/store/search-rows.d.ts +5 -5
  533. package/dist/store/search-rows.js +18 -16
  534. package/dist/store/sessions.js +4 -2
  535. package/dist/store/set-aside.d.ts +14 -0
  536. package/dist/store/set-aside.js +23 -0
  537. package/dist/store/sqlite/busy.d.ts +3 -0
  538. package/dist/store/sqlite/busy.js +8 -0
  539. package/dist/store/sqlite/connector-events-group.js +4 -7
  540. package/dist/store/sqlite/connector-writes-group.js +6 -4
  541. package/dist/store/sqlite/entry-writes-group.d.ts +3 -3
  542. package/dist/store/sqlite/entry-writes-group.js +3 -3
  543. package/dist/store/sqlite/error-codec.js +4 -3
  544. package/dist/store/sqlite/executor.js +10 -4
  545. package/dist/store/sqlite/graph-reads-group.js +1 -1
  546. package/dist/store/sqlite/held-connection.d.ts +11 -0
  547. package/dist/store/sqlite/held-connection.js +41 -0
  548. package/dist/store/sqlite/last-recall-group.d.ts +13 -0
  549. package/dist/store/sqlite/last-recall-group.js +57 -0
  550. package/dist/store/sqlite/local.d.ts +0 -17
  551. package/dist/store/sqlite/local.js +0 -31
  552. package/dist/store/sqlite/maintenance-groups.d.ts +6 -0
  553. package/dist/store/sqlite/maintenance-groups.js +60 -0
  554. package/dist/store/sqlite/object-rows.d.ts +1 -1
  555. package/dist/store/sqlite/objects-group.d.ts +1 -1
  556. package/dist/store/sqlite/objects-group.js +15 -7
  557. package/dist/store/sqlite/store.d.ts +1 -8
  558. package/dist/store/sqlite/store.js +13 -22
  559. package/dist/store/summaries.d.ts +10 -3
  560. package/dist/store/summaries.js +54 -11
  561. package/dist/store/token-ledger-rows.js +3 -2
  562. package/dist/store/token-ledger.d.ts +0 -7
  563. package/dist/store/token-ledger.js +0 -35
  564. package/dist/store/vector-index.js +8 -4
  565. package/dist/support-bundle.js +2 -3
  566. package/dist/trust/reject-flow.js +2 -2
  567. package/dist/util/agent-homes.d.ts +15 -0
  568. package/dist/util/agent-homes.js +34 -0
  569. package/dist/util/anthropic-messages.d.ts +6 -1
  570. package/dist/util/anthropic-messages.js +26 -7
  571. package/dist/util/atomic-write.d.ts +5 -2
  572. package/dist/util/atomic-write.js +12 -8
  573. package/dist/util/chunked.d.ts +3 -0
  574. package/dist/util/chunked.js +10 -0
  575. package/dist/util/ellipsize.d.ts +3 -0
  576. package/dist/util/ellipsize.js +5 -0
  577. package/dist/util/env.d.ts +12 -2
  578. package/dist/util/env.js +12 -2
  579. package/dist/util/format.d.ts +5 -0
  580. package/dist/util/format.js +10 -0
  581. package/dist/util/hook-payload.d.ts +8 -0
  582. package/dist/util/hook-payload.js +37 -0
  583. package/dist/util/http-retry.d.ts +6 -0
  584. package/dist/util/http-retry.js +43 -5
  585. package/dist/util/http-util.d.ts +0 -8
  586. package/dist/util/http-util.js +10 -21
  587. package/dist/util/limits.d.ts +7 -0
  588. package/dist/util/limits.js +14 -0
  589. package/dist/util/log.d.ts +4 -0
  590. package/dist/util/log.js +9 -0
  591. package/dist/util/request-scope.d.ts +9 -3
  592. package/dist/util/request-scope.js +18 -9
  593. package/dist/util/secret-detect.js +6 -1
  594. package/dist/util/spawn-argv.d.ts +9 -0
  595. package/dist/util/spawn-argv.js +57 -0
  596. package/dist/util/sql.d.ts +3 -0
  597. package/dist/util/sql.js +6 -0
  598. package/dist/util/sqlite-blocked.d.ts +2 -0
  599. package/dist/util/sqlite-blocked.js +5 -0
  600. package/dist/util/stream-tee.d.ts +3 -0
  601. package/dist/util/stream-tee.js +35 -0
  602. package/dist/util/time.d.ts +4 -0
  603. package/dist/util/time.js +4 -0
  604. package/dist/util/vector-codec.d.ts +4 -0
  605. package/dist/util/vector-codec.js +15 -0
  606. package/dist/util/version.d.ts +1 -1
  607. package/dist/util/version.js +1 -1
  608. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  609. package/extensions/openclaw-plugin/package.json +1 -1
  610. package/openclaw.plugin.json +1 -1
  611. package/package.json +4 -2
  612. package/dist/api/on-store.d.ts +0 -16
  613. package/dist/api/on-store.js +0 -38
  614. package/dist/store/embeddings/local.d.ts +0 -15
  615. package/dist/store/embeddings/local.js +0 -175
  616. /package/dist/{cli → api}/correction-latency.d.ts +0 -0
  617. /package/dist/{cli → api}/correction-latency.js +0 -0
  618. /package/dist/{cli → api}/provenance-coverage.d.ts +0 -0
  619. /package/dist/{cli → api}/provenance-coverage.js +0 -0
  620. /package/dist/{store → core}/object-types.d.ts +0 -0
  621. /package/dist/{store → core}/object-types.js +0 -0
  622. /package/dist/{capture → util}/compaction-items.d.ts +0 -0
  623. /package/dist/{capture → util}/compaction-items.js +0 -0
  624. /package/dist/{store → util}/yaml.d.ts +0 -0
  625. /package/dist/{store → util}/yaml.js +0 -0
@@ -2,26 +2,15 @@
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  import { DEFAULT_HALF_LIFE_DAYS, Layer } from '../core/memory.js';
5
- import { closeHippoDb, currentRequestStores, openHippoDb, getMeta, setMeta, withWriteScope } from '../db/index.js';
5
+ import { closeHippoDb, currentRequestStores, openHippoDb, getMeta, setMeta, withWriteScope, pruneConsolidationRuns, } from '../db/index.js';
6
6
  import { openHippoDbWithFacts } from '../db/open.js';
7
- import { findHippoStoreDir } from '../core/project-identity.js';
8
- import { realpathOrResolve } from '../util/real-path.js';
9
- import { RejectedValueError } from './rejection.js';
10
- import { errorMessage, log } from '../util/log.js';
7
+ import { RejectedValueError } from '../core/api-errors.js';
8
+ import { errorFields, errorMessage, log } from '../util/log.js';
11
9
  import { audit } from './audit-event.js';
12
- import { deserializeEntry } from './markdown.js';
10
+ import { deserializeEntry } from '../core/markdown.js';
13
11
  import { stampOriginProjectForImport, upsertEntryRow } from './entry-row.js';
14
12
  import { ensureMirrorDirectories, syncMirrorFiles, layerDir } from './mirrors.js';
15
13
  import { isJsonObject } from '../util/json.js';
16
- /** Nearest ancestor store like git; the strict join is the fallback so `hippo init` still creates `<cwd>/.hippo`. */
17
- export function getHippoRoot(cwd = process.cwd(), opts) {
18
- return findHippoStoreDir(cwd, opts) ?? path.join(realpathOrResolve(cwd), '.hippo');
19
- }
20
- export function isInitialized(hippoRoot) {
21
- // autoInstallHooks / setupDailySchedule can create a bare .hippo with no hippo.db; counting
22
- // that as initialized makes `hippo init` skip initStore, so only hippo.db counts.
23
- return fs.existsSync(path.join(hippoRoot, 'hippo.db'));
24
- }
25
14
  export function initStore(hippoRoot) {
26
15
  closeHippoDb(openStore(hippoRoot));
27
16
  }
@@ -48,13 +37,15 @@ export function openStore(hippoRoot, opts) {
48
37
  try {
49
38
  closeHippoDb(db);
50
39
  }
51
- catch {
52
- // Best effort only; surface the original init error.
40
+ catch (closeErr) {
41
+ // The init error is the one thrown; the close failure is only logged.
42
+ log.error(`openStore: closing the handle after a failed init failed: ${errorMessage(closeErr)}`, errorFields(closeErr));
53
43
  }
54
44
  throw error;
55
45
  }
56
46
  }
57
- /** One call on a handle of its own, closed after; pass openStore where the call also sets up mirror folders and legacy rows. */
47
+ /** One call on a handle of its own, closed after (a request scope's handle stays open);
48
+ * pass openStore where the call also sets up mirror folders and legacy rows. */
58
49
  export function onHandle(hippoRoot, fn, open = openHippoDb) {
59
50
  const db = open(hippoRoot);
60
51
  try {
@@ -142,6 +133,8 @@ function importLegacyIndexAndStats(db, hippoRoot) {
142
133
  const row = run;
143
134
  insertRun.run(String(row.timestamp ?? new Date().toISOString()), Number(row.decayed ?? 0), Number(row.merged ?? 0), Number(row.removed ?? 0));
144
135
  }
136
+ // Same cap as every later sleep, so stats.json and the table agree from the first open.
137
+ pruneConsolidationRuns(db);
145
138
  }
146
139
  export function loadLegacyEntriesFromMarkdown(hippoRoot) {
147
140
  const entries = [];
@@ -152,52 +145,47 @@ export function loadLegacyEntriesFromMarkdown(hippoRoot) {
152
145
  for (const file of fs.readdirSync(dir)) {
153
146
  if (!file.endsWith('.md'))
154
147
  continue;
155
- const raw = fs.readFileSync(path.join(dir, file), 'utf8');
156
- const entry = deserializeEntry(raw);
157
- if (entry)
158
- entries.push(entry);
148
+ try {
149
+ const entry = deserializeEntry(fs.readFileSync(path.join(dir, file), 'utf8'));
150
+ if (entry)
151
+ entries.push(entry);
152
+ }
153
+ catch (err) {
154
+ log.warn(`store: skipped unreadable legacy file ${file}: ${errorMessage(err)}`);
155
+ }
159
156
  }
160
157
  }
161
158
  return entries;
162
159
  }
163
- function loadLegacyIndexFile(hippoRoot) {
164
- const indexPath = path.join(hippoRoot, 'index.json');
165
- if (!fs.existsSync(indexPath)) {
166
- return { version: 1, entries: {}, last_retrieval_ids: [], last_trace_id: null };
167
- }
160
+ function readLegacyJsonObject(file) {
161
+ let parsed;
168
162
  try {
169
- // SAFETY: index.json is only written by writeIndexMirror below, which serializes a HippoIndex;
170
- // a hand-edited file that violates the shape falls through to the catch block's fallback.
171
- return JSON.parse(fs.readFileSync(indexPath, 'utf8'));
163
+ parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
172
164
  }
173
165
  catch (err) {
174
- log.debug(`store: unreadable index.json read as empty: ${errorMessage(err)}`);
175
- return { version: 1, entries: {}, last_retrieval_ids: [], last_trace_id: null };
166
+ log.warn(`store: unreadable ${path.basename(file)} read as empty: ${errorMessage(err)}`);
167
+ return null;
168
+ }
169
+ if (!isJsonObject(parsed)) {
170
+ log.warn(`store: ${path.basename(file)} is not a JSON object, read as empty`);
171
+ return null;
176
172
  }
173
+ return parsed;
174
+ }
175
+ function loadLegacyIndexFile(hippoRoot) {
176
+ const indexPath = path.join(hippoRoot, 'index.json');
177
+ const empty = { version: 1, entries: {}, last_retrieval_ids: [], last_trace_id: null };
178
+ if (!fs.existsSync(indexPath))
179
+ return empty;
180
+ // SAFETY: index.json is written by writeIndexMirror as a HippoIndex; readers default every field, so only the object shape is checked.
181
+ return readLegacyJsonObject(indexPath) ?? empty;
177
182
  }
178
183
  function loadLegacyStatsFile(hippoRoot) {
179
184
  const statsPath = path.join(hippoRoot, 'stats.json');
180
- if (!fs.existsSync(statsPath)) {
181
- return {
182
- total_remembered: 0,
183
- total_recalled: 0,
184
- total_forgotten: 0,
185
- consolidation_runs: [],
186
- };
187
- }
188
- try {
189
- // SAFETY: stats.json is only written by writeStatsMirror below with exactly these four fields; callers also guard every read with `?? 0` / `Array.isArray`,
190
- // so a hand-edited file is tolerated even if this cast is wrong.
191
- return JSON.parse(fs.readFileSync(statsPath, 'utf8'));
192
- }
193
- catch (err) {
194
- log.debug(`store: unreadable stats.json read as zero: ${errorMessage(err)}`);
195
- return {
196
- total_remembered: 0,
197
- total_recalled: 0,
198
- total_forgotten: 0,
199
- consolidation_runs: [],
200
- };
201
- }
185
+ const empty = { total_remembered: 0, total_recalled: 0, total_forgotten: 0, consolidation_runs: [] };
186
+ if (!fs.existsSync(statsPath))
187
+ return empty;
188
+ // SAFETY: callers guard every field read with `?? 0` / `Array.isArray`, so only the object shape is checked.
189
+ return readLegacyJsonObject(statsPath) ?? empty;
202
190
  }
203
191
  //# sourceMappingURL=open.js.map
@@ -0,0 +1,12 @@
1
+ import { type DatabaseSyncLike } from '../db/index.js';
2
+ export type PilotArm = 'hippo' | 'holdout';
3
+ /** Deterministic split: the same session and rate always land in the same arm. */
4
+ export declare function hashArm(sessionId: string, rateBp: number): PilotArm;
5
+ /** The session's first stored arm, or null; never writes. Without `tenantId` any tenant's row counts, since on one machine a session has one arm. */
6
+ export declare function readPilotArm(db: DatabaseSyncLike, sessionId: string, tenantId?: string): PilotArm | null;
7
+ /** The stored arm, else the hash arm written once; on any error the hash arm comes back unrecorded. */
8
+ export declare function ensurePilotArm(db: DatabaseSyncLike, tenantId: string, sessionId: string, rateBp: number, opts?: {
9
+ ownTenantOnly?: boolean;
10
+ now?: string;
11
+ }): PilotArm;
12
+ //# sourceMappingURL=pilot-arm.d.ts.map
@@ -0,0 +1,42 @@
1
+ // Pilot arm: one token_ledger row per session names its arm, `hippo` or `holdout`, so a pilot can compare them.
2
+ // `items` holds the holdout rate in basis points; readers outside this repo depend on these rows, so their shape is fixed.
3
+ import { createHash } from 'node:crypto';
4
+ import { HOOK_DB_WAIT_MS, scopedBusyWait, withWriteScope } from '../db/index.js';
5
+ import { errorMessage, log } from '../util/log.js';
6
+ import { firstPilotArmHash } from './token-ledger-rows.js';
7
+ import { recordTokenUse } from './token-ledger.js';
8
+ /** Deterministic split: the same session and rate always land in the same arm. */
9
+ export function hashArm(sessionId, rateBp) {
10
+ const bucket = parseInt(createHash('sha256').update(sessionId).digest('hex').slice(0, 8), 16) % 10000;
11
+ return bucket < rateBp ? 'holdout' : 'hippo';
12
+ }
13
+ /** The session's first stored arm, or null; never writes. Without `tenantId` any tenant's row counts, since on one machine a session has one arm. */
14
+ export function readPilotArm(db, sessionId, tenantId) {
15
+ const row = firstPilotArmHash(db, sessionId, tenantId);
16
+ return row?.block_hash === 'holdout' || row?.block_hash === 'hippo' ? row.block_hash : null;
17
+ }
18
+ /** The stored arm, else the hash arm written once; on any error the hash arm comes back unrecorded. */
19
+ // The wait is the caller's lock-wait scope (a hook's 1 s, a server request's 250 ms); it holds on a handle opened in that scope.
20
+ export function ensurePilotArm(db, tenantId, sessionId, rateBp, opts = {}) {
21
+ const hashed = hashArm(sessionId, rateBp);
22
+ const readTenant = opts.ownTenantOnly ? tenantId : undefined;
23
+ try {
24
+ // A stored row is the common case after the first prompt, so it must not take the write lock.
25
+ const existing = readPilotArm(db, sessionId, readTenant);
26
+ if (existing !== null)
27
+ return existing;
28
+ return withWriteScope(db, 'pilot_arm', () => {
29
+ const stored = readPilotArm(db, sessionId, readTenant);
30
+ if (stored === null) {
31
+ recordTokenUse(db, { tenantId, sessionId, surface: 'pilot', event: 'arm', items: rateBp, tokens: 0, hash: hashed, now: opts.now });
32
+ }
33
+ return stored ?? hashed;
34
+ }, { busyWaitMs: scopedBusyWait() ?? HOOK_DB_WAIT_MS });
35
+ }
36
+ catch (err) {
37
+ // A prompt hook must not fail on pilot bookkeeping; concurrent callers still agree on the hash arm.
38
+ log.debug(`pilot arm not stored, using the hash arm: ${errorMessage(err)}`);
39
+ return hashed;
40
+ }
41
+ }
42
+ //# sourceMappingURL=pilot-arm.js.map
@@ -1,7 +1,8 @@
1
1
  import type { AmbientTallies } from '../core/ambient.js';
2
2
  import type { AmbientStoreFilter } from './ambient.js';
3
3
  import type { ApiKeyListRow, ApiKeyRecord, ListApiKeysOpts, NewApiKey } from './auth.js';
4
- import type { AppendAuditOpts, AuditEvent, ListAuditAfterOpts, QueryAuditOpts } from './audit.js';
4
+ import type { AppendAuditOpts, AuditEvent, AuditPrune, ListAuditAfterOpts, QueryAuditOpts } from './audit.js';
5
+ import type { QualityRepairResult } from './quality-repair.js';
5
6
  import type { EmbeddingIndexState } from './vector-index.js';
6
7
  import type { Entity, Relation } from './graph-rows.js';
7
8
  import type { ActiveGoals, GetActiveGoalsOpts, GoalRecallLogRow } from './goals.js';
@@ -10,13 +11,14 @@ import type { JsonValue } from '../util/json.js';
10
11
  import type { KeysetPosition } from '../util/keyset.js';
11
12
  import type { MemoryEntry } from '../core/memory.js';
12
13
  import type { PhysicsParticle } from '../core/physics.js';
13
- import type { BriefReceipt, Incident, IncidentFields, ObjectByKind, ObjectFields, ObjectKind, Policy, SavableKind, Skill } from './object-types.js';
14
+ import type { BriefReceipt, Incident, IncidentFields, ObjectByKind, ObjectFields, ObjectKind, Policy, SavableKind, Skill } from '../core/object-types.js';
14
15
  import type { PlanningFallacyEvidence } from './planning-fallacy-evidence.js';
15
- import type { ClosureState, Prediction, PredictionBaserate, SavePredictionOpts } from './predictions.js';
16
+ import type { Prediction, PredictionBaserate, SavePredictionOpts } from './predictions.js';
17
+ import type { ClosureState } from '../core/prediction.js';
16
18
  import type { QuarantineRow, QuarantineStatus } from './quarantine.js';
17
- import type { ScopeActor } from './recall-scope.js';
19
+ import type { ScopeActor } from '../core/recall-scope.js';
18
20
  import type { RecallTraceInput } from './recall-trace.js';
19
- import type { AmbientLoadResult, AmbientRecallRequest, ContextCandidateFilter, RecentOrigins } from './candidates.js';
21
+ import type { AmbientLoadResult, AmbientRecallRequest, ContextCandidateFilter, HeldText, RecentOrigins } from './candidates.js';
20
22
  import type { GitHubDlqInsert, GitHubRouting } from './connectors/github.js';
21
23
  import type { SlackDlqInsert, SlackTeamRoute } from './connectors/slack.js';
22
24
  import type { StrengthenOptions } from './entry-writes.js';
@@ -410,6 +412,18 @@ export interface Readiness {
410
412
  /** Resolves once one cheap read has answered; rejects with the store's own error when it cannot. */
411
413
  ping(): Promise<void>;
412
414
  }
415
+ /** A recall's writes when the store keeps it as its last one: `strengthen.ids` are the ids it returned. */
416
+ export type LastRecallWrites = RecallWrites & Required<Pick<RecallWrites, 'strengthen'>>;
417
+ /** The last recall a store keeps, so a later outcome can name it, and the outcome linked to a recall trace. */
418
+ export interface LastRecall {
419
+ /** entryWrites.applyOutcome, then a link from recall trace `traceId` to the ids applied; resolves to those ids. */
420
+ applyTracedOutcome(outcome: OutcomeWrite, traceId: number): Promise<string[]>;
421
+ /** applyTracedOutcome on the ids and trace of the last recall, read together so an outcome never pairs them with an older trace. */
422
+ applyOutcomeToLast(target: EntryTarget, good: boolean): Promise<string[]>;
423
+ /** finishRecall, then the ids this store lacks strengthened under `globalRoot`, then the ids and their trace saved as the last
424
+ * recall in one write; resolves to the trace id, null when none was asked or its write failed. */
425
+ finishRecall(writes: LastRecallWrites, globalRoot: string | undefined): Promise<number | null>;
426
+ }
413
427
  /** Which of a tenant's quarantine records a list reads; `limit` is 100 when unset. */
414
428
  export interface QuarantineListQuery {
415
429
  readonly status: QuarantineStatus | 'all';
@@ -574,8 +588,49 @@ export interface VectorViews {
574
588
  /** The same ids and values as `VectorReads.storedVectors`, each value a Float32 view. */
575
589
  storedVectorViews(ids: readonly string[]): Promise<Map<string, Float32Array>>;
576
590
  }
591
+ /** The store is hippo.db and its files under `hippoRoot`, so a path that reopens them by folder (sleep, its child process, the global store) may run. */
592
+ export interface LocalFiles {
593
+ readonly hippoRoot: string;
594
+ }
595
+ /** The reads and writes behind the memory-quality audit, its repair and audit-log retention. */
596
+ export interface AuditMaintenance {
597
+ /** Every memory of the tenant, in `loadAllEntries` order. */
598
+ tenantEntries(tenantId: string): Promise<MemoryEntry[]>;
599
+ /** The ids of every memory a typed object row names, in any tenant; an automatic pass never deletes one. */
600
+ objectBackedIds(): Promise<Set<string>>;
601
+ /** Deletes each removal's row with its own forget row, leaving a pinned, raw, kept or object-backing row; resolves to the ids deleted. */
602
+ deleteAutomatically(removals: readonly {
603
+ readonly id: string;
604
+ readonly reason: string;
605
+ }[], actor: string): Promise<string[]>;
606
+ /** Counts (dryRun) or deletes the tenant's audit rows before `cutoff`, then appends one audit_prune row; resolves to the count. */
607
+ pruneAuditRows(prune: AuditPrune): Promise<number>;
608
+ /** A preview, or with `apply` the moves of certain defects to dormant storage behind a backup. */
609
+ repairQuality(tenantId: string, apply: boolean): Promise<QualityRepairResult>;
610
+ /** repairQuality applied once per store; null once it ran there. */
611
+ repairQualityOnce(tenantId: string): Promise<QualityRepairResult | null>;
612
+ }
613
+ /** The reads and writes behind git auto-learn's batch. */
614
+ export interface LessonWrites {
615
+ /** Every memory of the tenant, in `loadAllEntries` order. */
616
+ tenantEntries(tenantId: string): Promise<MemoryEntry[]>;
617
+ /** Content, source and origin of the tenant's rows holding any of `words`, among the rows a team member or `ownScope`'s owner may touch. */
618
+ textsHoldingWords(tenantId: string, words: readonly string[], ownScope: string | null): Promise<HeldText[]>;
619
+ /** Writes each entry in one transaction; a rejected value writes its refusal row and is skipped. Resolves to the ids written. */
620
+ writeSkippingRejected(entries: readonly MemoryEntry[], actor: string): Promise<string[]>;
621
+ /** Rewrites each entry in one transaction, all or none. */
622
+ writeTogether(entries: readonly MemoryEntry[]): Promise<void>;
623
+ /** Adds `count` to the store-wide remembered counter. */
624
+ bumpRemembered(count: number): Promise<void>;
625
+ }
577
626
  /** The optional groups: a store sets each one whole or leaves it unset, and a route or MCP tool names the one it needs. */
578
627
  export interface StoreGroups {
628
+ /** Unset on any store but hippo.db; a request then runs fenced from hippo.db, and a route or tool that names no group answers 501. */
629
+ readonly localFiles: LocalFiles;
630
+ /** Unset on a store without it, where the quality audit, its repair and audit prune answer 501. */
631
+ readonly auditMaintenance: AuditMaintenance;
632
+ /** Unset on a store without it, where git learn answers 501. */
633
+ readonly lessonWrites: LessonWrites;
579
634
  /** Unset on a store built before them, where hybrid and physics recall under an embedding provider answer 501. */
580
635
  readonly vectors: VectorReads;
581
636
  /** Unset on a store without them, where recall scores the number[] copies `vectors` returns, to the same ranking. */
@@ -598,15 +653,22 @@ export interface StoreGroups {
598
653
  readonly objects: Objects;
599
654
  /** Unset on a store built before it, where GET /ready answers 200 with `store: "unchecked"`. */
600
655
  readonly readiness: Readiness;
656
+ /** Unset on a store that keeps no last recall, where outcomeForLastRecall and a traced outcome answer 501 and context writes no last recall. */
657
+ readonly lastRecall: LastRecall;
601
658
  }
602
659
  export type StoreGroup = 'base' | keyof StoreGroups;
603
660
  export declare function hasGroup(store: HippoStore, group: StoreGroup): boolean;
604
661
  /** The group's methods; a store without them throws StoreNotPortedError, which answers 501 as any unported path does. */
605
662
  export declare function requireGroup<G extends keyof StoreGroups>(store: HippoStore, group: G): StoreGroups[G];
663
+ export declare const STORE_BUSY_MESSAGE = "store busy (another hippo process holds the write lock); retry shortly";
664
+ /** A store behind the port throws this when its lock wait runs out, so the server answers 503 as it does for a busy hippo.db. */
665
+ export declare class StoreBusyError extends Error {
666
+ constructor(message?: string, options?: ErrorOptions);
667
+ }
606
668
  /** What `serve()` reads and writes through. Each method is atomic and no transaction spans an
607
669
  * await, since SQLite's lock wait blocks the event loop; a lock timeout throws `StoreBusyError`. */
608
670
  export interface HippoStore extends Partial<StoreGroups> {
609
- /** 'sqlite' is hippo.db under the served root. Under any other kind, an unported route answers 501 and a hippo.db open inside a request throws. */
671
+ /** A name for logs and errors; what a store can do is the groups it sets, `localFiles` above all. */
610
672
  readonly kind: string;
611
673
  /** The api_keys row for `keyId` with its scope grants, revoked or not; null when no row matches. */
612
674
  findApiKey(keyId: string): Promise<ApiKeyRecord | null>;
@@ -14,4 +14,12 @@ export function requireGroup(store, group) {
14
14
  throw new StoreNotPortedError(store.kind, group);
15
15
  return methods;
16
16
  }
17
+ export const STORE_BUSY_MESSAGE = 'store busy (another hippo process holds the write lock); retry shortly';
18
+ /** A store behind the port throws this when its lock wait runs out, so the server answers 503 as it does for a busy hippo.db. */
19
+ export class StoreBusyError extends Error {
20
+ constructor(message = STORE_BUSY_MESSAGE, options) {
21
+ super(message, options);
22
+ this.name = 'StoreBusyError';
23
+ }
24
+ }
17
25
  //# sourceMappingURL=port.js.map
@@ -4,8 +4,7 @@
4
4
  import { type MemoryEntry } from '../core/memory.js';
5
5
  import { type KeysetPosition } from '../util/keyset.js';
6
6
  import type { PredictionSave } from './port.js';
7
- export type ClosureState = 'open' | 'closed' | 'closed-unknown';
8
- export declare const VALID_CLOSURE_STATES: ReadonlySet<ClosureState>;
7
+ import { type ClosureState } from '../core/prediction.js';
9
8
  export interface Prediction {
10
9
  id: number;
11
10
  /** Nullable: ON DELETE SET NULL allows memory deletion (forget /
@@ -1,6 +1,7 @@
1
1
  /** First-class prediction: an ex-ante claim closed against an outcome. The `predictions` table is the source of truth; the memory row only mirrors the claim.
2
2
  * Every helper requires tenantId; `trg_predictions_tenant_match_*` triggers enforce the prediction's tenant matches the referenced memory's.
3
3
  * `savePrediction` writes memory and prediction inside `writeEntry`'s SAVEPOINT (via afterWrite), so a failure in either rolls back both. */
4
+ import { DEFAULT_LIST_LIMIT } from '../util/limits.js';
4
5
  import { BadRequestError, NotFoundError } from '../core/api-errors.js';
5
6
  import { withWriteScope } from '../db/index.js';
6
7
  import { onHandle } from './open.js';
@@ -10,15 +11,11 @@ import { createMemory, Layer } from '../core/memory.js';
10
11
  import { appendAuditEvent } from './audit.js';
11
12
  import { loadConfig } from '../core/config.js';
12
13
  import { keysetAfter } from '../util/keyset.js';
13
- const DEFAULT_PREDICTION_PAGE_SIZE = 100;
14
+ import { VALID_CLOSURE_STATES } from '../core/prediction.js';
15
+ const DEFAULT_PREDICTION_PAGE_SIZE = DEFAULT_LIST_LIMIT;
14
16
  const SELECT_COLUMNS = `id, memory_id, tenant_id, class_tag, claim_text,
15
17
  estimate_value, estimate_unit, target_date,
16
18
  actual_value, closure_state, closed_at, closure_note, created_at`;
17
- export const VALID_CLOSURE_STATES = new Set([
18
- 'open',
19
- 'closed',
20
- 'closed-unknown',
21
- ]);
22
19
  function rowToPrediction(row) {
23
20
  return {
24
21
  id: row.id,
@@ -0,0 +1,62 @@
1
+ import { type DatabaseSyncLike } from '../db/index.js';
2
+ import { type MemoryEntry } from '../core/memory.js';
3
+ import { type AppendAuditOpts, type AuditEvent } from './audit.js';
4
+ import { type DormantReason, type DormantSnapshot } from './dormant.js';
5
+ import { type SetAsideResult, type SetAsideWhy } from './set-aside.js';
6
+ export type ProjectTagOp = 'project_merge' | 'project_repair';
7
+ export interface ProjectTagReads {
8
+ entries(): MemoryEntry[];
9
+ liveEntriesBySourcePrefix(prefix: string): MemoryEntry[];
10
+ entriesByIds(ids: readonly string[]): Map<string, MemoryEntry>;
11
+ dormant(): DormantSnapshot[];
12
+ auditEvents(op: ProjectTagOp): AuditEvent[];
13
+ compactionTranscripts(): Array<{
14
+ transcript: string;
15
+ cwd: string | null;
16
+ }>;
17
+ compactionOriginsWithCwd(): Array<{
18
+ origin: string;
19
+ cwd: string;
20
+ }>;
21
+ holdsOrigin(table: 'memories' | 'compactions', name: string): boolean;
22
+ }
23
+ export interface ProjectTagWrites extends ProjectTagReads {
24
+ setAside(tag: string, row: MemoryEntry, why: SetAsideWhy): SetAsideResult;
25
+ restampOrigin(from: string, into: string): void;
26
+ restampCompactions(from: string, into: string): number;
27
+ replaceDormant(id: string, entry: MemoryEntry): void;
28
+ stampOrigins(rows: ReadonlyArray<{
29
+ readonly id: string;
30
+ readonly origin: string;
31
+ }>): void;
32
+ /** A live row to dormant storage, restorable, as sleep's dormant move does. */
33
+ retire(row: MemoryEntry, reason: DormantReason, now: Date): void;
34
+ audit(op: ProjectTagOp, metadata: AppendAuditOpts['metadata']): void;
35
+ }
36
+ /** A rewrite's answer plus the ids whose mirrors are rewritten, and those whose mirrors go, once it commits. */
37
+ export interface TagRewrite<T> {
38
+ readonly result: T;
39
+ readonly rewrite: readonly string[];
40
+ readonly purge: readonly string[];
41
+ }
42
+ export interface ProjectTagStore extends ProjectTagReads {
43
+ readonly hippoRoot: string;
44
+ /** One transaction; a dry run rolls back with no backup, else the store is backed up first and mirrors follow the commit. */
45
+ rewrite<T>(opts: {
46
+ readonly dryRun: boolean;
47
+ readonly backupLabel: string;
48
+ }, body: (tx: ProjectTagWrites, backup: string | null) => TagRewrite<T>): T;
49
+ repairedOnce(): boolean;
50
+ markRepairedOnce(): void;
51
+ }
52
+ export interface ProjectTagScope {
53
+ readonly hippoRoot: string;
54
+ readonly tenantId: string;
55
+ /** Who the audit rows name. */
56
+ readonly actor: string;
57
+ }
58
+ /** Reads on a handle the caller opened and closes, for doctor's read-only check and the note sync. */
59
+ export declare function projectTagReads(db: DatabaseSyncLike, tenantId: string): ProjectTagReads;
60
+ /** Runs `fn` on hippo.db under `scope.hippoRoot`, opened for this call and closed after. */
61
+ export declare function onProjectTags<T>(scope: ProjectTagScope, fn: (store: ProjectTagStore) => T): T;
62
+ //# sourceMappingURL=project-tags.d.ts.map
@@ -0,0 +1,67 @@
1
+ // The rows `hippo projects` and sleep's tag repair read and write, bound to one hippo.db handle and tenant,
2
+ // so the merge and repair rules in src/sharing/project-merge.ts decide without holding the handle.
3
+ import { getMeta, setMeta, withTrialScope, withWriteScope } from '../db/index.js';
4
+ import { withBackup } from '../db/backup.js';
5
+ import { calculateStrength } from '../core/memory.js';
6
+ import { appendAuditEvent, queryAuditEvents } from './audit.js';
7
+ import { compactionOriginsWithCwd, compactionTranscripts, holdsOrigin, restampCompactionOrigin } from './compactions.js';
8
+ import { insertDormantRow, listDormantSnapshots, replaceDormantEntry } from './dormant.js';
9
+ import { selectAllEntries, selectEntriesByIds, selectLiveEntriesBySourcePrefix } from './entry-reads.js';
10
+ import { deleteEntryRowInTx, restampOriginProjectAt, stampOriginProjectsAt, writeEntryMirrors } from './entry-writes.js';
11
+ import { removeEntryMirrors } from './mirrors.js';
12
+ import { onHandle } from './open.js';
13
+ import { setAsideRow } from './set-aside.js';
14
+ const AUDIT_READ_CAP = 10000;
15
+ const TX_LABEL = 'merge_projects';
16
+ const AUTO_REPAIR_META_KEY = 'project_repair_auto';
17
+ /** Reads on a handle the caller opened and closes, for doctor's read-only check and the note sync. */
18
+ export function projectTagReads(db, tenantId) {
19
+ return {
20
+ entries: () => selectAllEntries(db, tenantId),
21
+ liveEntriesBySourcePrefix: (prefix) => selectLiveEntriesBySourcePrefix(db, tenantId, prefix),
22
+ entriesByIds: (ids) => selectEntriesByIds(db, ids, tenantId),
23
+ dormant: () => listDormantSnapshots(db, tenantId),
24
+ auditEvents: (op) => queryAuditEvents(db, { tenantId, op, limit: AUDIT_READ_CAP }),
25
+ compactionTranscripts: () => compactionTranscripts(db, tenantId),
26
+ compactionOriginsWithCwd: () => compactionOriginsWithCwd(db, tenantId),
27
+ holdsOrigin: (table, name) => holdsOrigin(db, table, tenantId, name),
28
+ };
29
+ }
30
+ function projectTagWrites(db, { tenantId, actor }) {
31
+ return {
32
+ ...projectTagReads(db, tenantId),
33
+ setAside: (tag, row, why) => setAsideRow(db, tag, row, why),
34
+ restampOrigin: (from, into) => restampOriginProjectAt(db, tenantId, from, into),
35
+ restampCompactions: (from, into) => restampCompactionOrigin(db, tenantId, from, into),
36
+ replaceDormant: (id, entry) => replaceDormantEntry(db, tenantId, id, entry),
37
+ stampOrigins: (rows) => stampOriginProjectsAt(db, tenantId, rows),
38
+ retire: (row, reason, now) => {
39
+ insertDormantRow(db, { entry: row, strength: calculateStrength(row, now), reason, dormantAt: now.toISOString() });
40
+ deleteEntryRowInTx(db, row, actor);
41
+ },
42
+ audit: (op, metadata) => appendAuditEvent(db, { tenantId, actor, op, metadata }),
43
+ };
44
+ }
45
+ function rewriteOn(db, scope, opts, body) {
46
+ const tx = projectTagWrites(db, scope);
47
+ if (opts.dryRun)
48
+ return withTrialScope(db, TX_LABEL, () => body(tx, null)).result;
49
+ const done = withBackup(db, scope.hippoRoot, opts.backupLabel, (backup) => withWriteScope(db, TX_LABEL, () => body(tx, backup)));
50
+ // After commit, as the agent memory sync does: a stale mirror would bring the old tag back on the next rebuild.
51
+ for (const entry of selectEntriesByIds(db, done.rewrite, scope.tenantId).values())
52
+ writeEntryMirrors(scope.hippoRoot, entry);
53
+ for (const id of done.purge)
54
+ removeEntryMirrors(scope.hippoRoot, id);
55
+ return done.result;
56
+ }
57
+ /** Runs `fn` on hippo.db under `scope.hippoRoot`, opened for this call and closed after. */
58
+ export function onProjectTags(scope, fn) {
59
+ return onHandle(scope.hippoRoot, (db) => fn({
60
+ ...projectTagReads(db, scope.tenantId),
61
+ hippoRoot: scope.hippoRoot,
62
+ rewrite: (opts, body) => rewriteOn(db, scope, opts, body),
63
+ repairedOnce: () => getMeta(db, AUTO_REPAIR_META_KEY) === '1',
64
+ markRepairedOnce: () => setMeta(db, AUTO_REPAIR_META_KEY, '1'),
65
+ }));
66
+ }
67
+ //# sourceMappingURL=project-tags.js.map
@@ -133,7 +133,7 @@ function setAsideIssue(db, entry, reason, now, backup) {
133
133
  if (getMeta(db, 'fts5_available', '0') === '1' && ftsRowExists(db, entry.id)) {
134
134
  throw new Error(`Quality repair could not remove the full-text row for ${entry.id}`);
135
135
  }
136
- appendAuditEvent(db, { tenantId: entry.tenantId, actor, op: 'quality_repair', targetId: entry.id, metadata: { reason, backup } });
136
+ appendAuditEvent(db, { tenantId: entry.tenant_id, actor, op: 'quality_repair', targetId: entry.id, metadata: { reason, backup } });
137
137
  return true;
138
138
  }
139
139
  function applyPlan(db, root, tenantId, backup, doneKey) {
@@ -1,4 +1,5 @@
1
1
  // The memory_quarantine table: the review queue's rows, and the approve and reject writes with their audit rows.
2
+ import { DEFAULT_LIST_LIMIT } from '../util/limits.js';
2
3
  import { appendAuditEvent } from './audit.js';
3
4
  import { withWriteScopeOr } from '../db/index.js';
4
5
  import { keysetAfter } from '../util/keyset.js';
@@ -27,7 +28,7 @@ function getQuarantineRow(db, tenantId, memoryId) {
27
28
  .get(tenantId, memoryId);
28
29
  return row ? fromDbRow(row) : null;
29
30
  }
30
- function listQuarantineRows(db, tenantId, status = 'pending', limit = 100, afterRow) {
31
+ function listQuarantineRows(db, tenantId, status = 'pending', limit = DEFAULT_LIST_LIMIT, afterRow) {
31
32
  // A pending row whose memory was deleted (e.g. Slack message_deleted) is dead; keep its history, drop it from the queue.
32
33
  const live = status === 'pending'
33
34
  ? ' AND EXISTS (SELECT 1 FROM memories m WHERE m.id = memory_quarantine.memory_id AND m.tenant_id = memory_quarantine.tenant_id)'
@@ -101,6 +101,8 @@ export interface DeliveryEventRow {
101
101
  recall_hash: string | null;
102
102
  emitted_hash: string | null;
103
103
  elapsed_ms: number;
104
+ tool_use_id: string | null;
105
+ failure_id: number | null;
104
106
  candidates: DeliveryCandidateRow[];
105
107
  }
106
108
  /** One event plus its candidates in one write transaction, then prune; fail-soft. The caller must not hold a transaction on `db`. */
@@ -1,11 +1,13 @@
1
1
  /** Retrieval-trace persistence: the single producer for `recall_traces`, `recall_trace_results` and `recall_trace_outcomes`.
2
2
  * One trace row per recall on api.recall, api.getContext and CLI cmdRecall: the (query, shown, outcome) triple the learned components train on.
3
3
  * All writes are fail-soft: a failed trace write is logged to stderr and swallowed, never breaking the surrounding call. */
4
- import { openHippoDb, closeHippoDb, rethrowIfSqliteBlocked, withWriteScope } from '../db/index.js';
4
+ import { openHippoDb, closeHippoDb, withWriteScope } from '../db/index.js';
5
+ import { rethrowIfSqliteBlocked } from '../util/sqlite-blocked.js';
5
6
  import { DELIVERY_LEDGER_VERSION, isBoundaryEvent } from './delivery-recorder.js';
6
7
  import { errorFields, errorMessage, log } from '../util/log.js';
7
8
  import { DAY_MS } from '../util/time.js';
8
9
  import { blockHash } from '../util/token-text.js';
10
+ import { placeholders } from '../util/sql.js';
9
11
  /** Strip a RerankStep to {stage, multiplier, scoreBefore, scoreAfter}: `note` is free-form (the CLI goal-boost step embeds goal tag text) and would leak
10
12
  * user content into `rerank_json`; an allowlist, so any future free-form field is dropped too. */
11
13
  function sanitizeRerankSteps(steps) {
@@ -105,7 +107,7 @@ const DELIVERY_EVENT_COLUMNS = [
105
107
  'session_id', 'session_state', 'host_turn_id', 'turn_seq', 'duplicate_of', 'prompt_hash', 'prompt_length', 'query_hash',
106
108
  'recall_trace_id', 'block_state', 'prompt_recall', 'considered_count', 'filtered_count', 'selected_count', 'emitted_count',
107
109
  'rejected_count', 'rejected_unlisted', 'sections_shown', 'sections_dropped', 'budget_tokens', 'selected_tokens',
108
- 'injected_tokens', 'static_hash', 'recall_hash', 'emitted_hash', 'elapsed_ms',
110
+ 'injected_tokens', 'static_hash', 'recall_hash', 'emitted_hash', 'elapsed_ms', 'tool_use_id', 'failure_id',
109
111
  ];
110
112
  function findDuplicateBoundary(db, input) {
111
113
  // SAFETY: rows carry exactly the `id` and `ts` columns selected.
@@ -117,7 +119,21 @@ function findDuplicateBoundary(db, input) {
117
119
  const at = Date.parse(input.ts);
118
120
  return rows.find((r) => Math.abs(Date.parse(r.ts) - at) <= DELIVERY_DUPLICATE_WINDOW_MS)?.id ?? null;
119
121
  }
122
+ function findDuplicateToolFailure(db, input) {
123
+ // Without a call id a time window would merge parallel failures, so two such fires stay two rows.
124
+ if (input.toolUseId === undefined || input.toolUseId === null)
125
+ return null;
126
+ // SAFETY: a single `id` column, undefined when no row matches.
127
+ const row = db.prepare(`
128
+ SELECT id FROM delivery_events
129
+ WHERE tenant_id = ? AND session_id = ? AND event_type = ? AND tool_use_id = ? AND turn_seq IS NOT NULL
130
+ ORDER BY id LIMIT 1
131
+ `).get(input.tenantId, input.sessionId, input.eventType, input.toolUseId);
132
+ return row?.id ?? null;
133
+ }
120
134
  function findDuplicateTurn(db, input) {
135
+ if (input.eventType === 'tool-failure')
136
+ return findDuplicateToolFailure(db, input);
121
137
  // A boundary has no prompt, and two compactions of one session never start within the window, so time alone decides.
122
138
  if (isBoundaryEvent(input.eventType))
123
139
  return findDuplicateBoundary(db, input);
@@ -165,10 +181,11 @@ export function writeDeliveryEvent(db, input) {
165
181
  input.consideredCount, input.filteredCount, input.selectedCount, input.emittedCount, input.rejectedCount,
166
182
  input.rejectedUnlisted, input.sectionsShown, input.sectionsDropped, input.budgetTokens, input.selectedTokens,
167
183
  input.injectedTokens, input.staticHash, input.recallHash, input.emittedHash, Math.round(input.elapsedMs),
184
+ input.toolUseId ?? null, input.failureId ?? null,
168
185
  ];
169
186
  const eventId = Number(db.prepare(`
170
187
  INSERT INTO delivery_events (${DELIVERY_EVENT_COLUMNS.join(', ')})
171
- VALUES (${DELIVERY_EVENT_COLUMNS.map(() => '?').join(', ')})
188
+ VALUES (${placeholders(DELIVERY_EVENT_COLUMNS.length)})
172
189
  `).run(...values).lastInsertRowid);
173
190
  const insertCandidate = db.prepare(`
174
191
  INSERT INTO delivery_candidates (event_id, tenant_id, memory_id, source_store, pool, stage, outcome, reason, cand_rank, score, tokens)