hippo-memory 1.71.0 → 1.72.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 (555) hide show
  1. package/README.md +1 -1
  2. package/dist/agent-memories/apply.d.ts +0 -11
  3. package/dist/agent-memories/apply.js +6 -21
  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 +1 -1
  12. package/dist/api/audit.d.ts +29 -1
  13. package/dist/api/audit.js +44 -5
  14. package/dist/api/auth.d.ts +7 -4
  15. package/dist/api/auth.js +12 -8
  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-render.js +4 -3
  24. package/dist/api/context-select.js +9 -11
  25. package/dist/api/context.js +17 -8
  26. package/dist/api/continuity.d.ts +47 -0
  27. package/dist/api/continuity.js +66 -0
  28. package/dist/api/dormant.js +4 -2
  29. package/dist/api/drill-down.js +1 -1
  30. package/dist/api/embeddings.d.ts +17 -0
  31. package/dist/api/embeddings.js +28 -0
  32. package/dist/api/error-reply.d.ts +8 -0
  33. package/dist/api/error-reply.js +21 -0
  34. package/dist/api/forget.d.ts +6 -2
  35. package/dist/api/forget.js +18 -7
  36. package/dist/api/goals.d.ts +1 -1
  37. package/dist/api/goals.js +5 -9
  38. package/dist/api/index.d.ts +4 -2
  39. package/dist/api/index.js +4 -2
  40. package/dist/api/instruction-detect.js +5 -1
  41. package/dist/api/learn.d.ts +4 -0
  42. package/dist/api/learn.js +53 -27
  43. package/dist/api/ledger-db.d.ts +2 -6
  44. package/dist/api/ledger-db.js +4 -36
  45. package/dist/api/memories.d.ts +19 -1
  46. package/dist/api/memories.js +39 -0
  47. package/dist/api/objects.d.ts +1 -1
  48. package/dist/api/outcome.js +1 -1
  49. package/dist/api/pilot-arm.d.ts +1 -11
  50. package/dist/api/pilot-arm.js +5 -45
  51. package/dist/api/predictions.d.ts +3 -1
  52. package/dist/api/predictions.js +6 -0
  53. package/dist/api/projects.d.ts +21 -0
  54. package/dist/api/projects.js +24 -0
  55. package/dist/api/promote.js +1 -1
  56. package/dist/api/prompt-hook.d.ts +15 -2
  57. package/dist/api/prompt-hook.js +44 -4
  58. package/dist/api/recall-cli.d.ts +3 -0
  59. package/dist/api/recall-cli.js +13 -4
  60. package/dist/api/recall-core.js +3 -3
  61. package/dist/api/recall-pipeline.js +6 -7
  62. package/dist/api/recall-request.js +1 -1
  63. package/dist/api/recall.d.ts +0 -3
  64. package/dist/api/recall.js +19 -44
  65. package/dist/api/remember-local.js +1 -1
  66. package/dist/api/remember.d.ts +2 -1
  67. package/dist/api/remember.js +3 -2
  68. package/dist/api/session-end.d.ts +22 -0
  69. package/dist/api/session-end.js +101 -0
  70. package/dist/api/sleep-run.d.ts +1 -1
  71. package/dist/api/sleep-run.js +13 -9
  72. package/dist/api/status.d.ts +24 -0
  73. package/dist/api/status.js +40 -0
  74. package/dist/api/store-init.d.ts +4 -0
  75. package/dist/api/store-init.js +9 -0
  76. package/dist/api/tokens.js +1 -1
  77. package/dist/api/types.d.ts +2 -0
  78. package/dist/api/types.js +6 -1
  79. package/dist/capture/caller-session.d.ts +0 -3
  80. package/dist/capture/caller-session.js +1 -12
  81. package/dist/capture/capture-error.d.ts +2 -2
  82. package/dist/capture/capture-error.js +23 -18
  83. package/dist/capture/codex-patch.js +3 -1
  84. package/dist/capture/command.js +36 -95
  85. package/dist/capture/compact.js +10 -5
  86. package/dist/capture/compaction-items-caller.js +11 -20
  87. package/dist/capture/compaction-record.d.ts +7 -93
  88. package/dist/capture/compaction-record.js +29 -426
  89. package/dist/capture/compaction-spool.d.ts +4 -2
  90. package/dist/capture/compaction-spool.js +20 -6
  91. package/dist/capture/copilot-transcript.d.ts +2 -1
  92. package/dist/capture/copilot-transcript.js +2 -1
  93. package/dist/capture/extract.d.ts +5 -2
  94. package/dist/capture/extract.js +4 -3
  95. package/dist/capture/failure-caller.d.ts +1 -1
  96. package/dist/capture/failure-caller.js +21 -16
  97. package/dist/capture/failure-reading.d.ts +2 -1
  98. package/dist/capture/failure-reading.js +7 -9
  99. package/dist/capture/pre-compact-caller.js +5 -4
  100. package/dist/capture/session-digest.js +4 -3
  101. package/dist/capture/session-end-caller.js +2 -1
  102. package/dist/capture/session-worker.js +3 -4
  103. package/dist/capture/transcript.d.ts +2 -1
  104. package/dist/capture/transcript.js +6 -3
  105. package/dist/capture/working-state.d.ts +4 -2
  106. package/dist/capture/working-state.js +4 -2
  107. package/dist/cli/audit-prune.d.ts +0 -25
  108. package/dist/cli/audit-prune.js +1 -27
  109. package/dist/cli/audit.js +13 -13
  110. package/dist/cli/auth.d.ts +1 -1
  111. package/dist/cli/auth.js +12 -13
  112. package/dist/cli/briefs.d.ts +1 -4
  113. package/dist/cli/briefs.js +37 -292
  114. package/dist/cli/card.js +41 -33
  115. package/dist/cli/client.d.ts +5 -0
  116. package/dist/cli/client.js +24 -8
  117. package/dist/cli/compact-resume-payload.js +1 -1
  118. package/dist/cli/context.d.ts +1 -1
  119. package/dist/cli/context.js +12 -12
  120. package/dist/cli/continuity.d.ts +1 -1
  121. package/dist/cli/continuity.js +36 -39
  122. package/dist/cli/curate.d.ts +1 -1
  123. package/dist/cli/curate.js +23 -27
  124. package/dist/cli/dag.d.ts +1 -1
  125. package/dist/cli/dag.js +23 -8
  126. package/dist/cli/decisions.d.ts +0 -2
  127. package/dist/cli/decisions.js +4 -281
  128. package/dist/cli/eval.d.ts +1 -1
  129. package/dist/cli/eval.js +12 -13
  130. package/dist/cli/explain.d.ts +1 -1
  131. package/dist/cli/explain.js +21 -25
  132. package/dist/cli/flag-values.js +16 -6
  133. package/dist/cli/flags.d.ts +0 -289
  134. package/dist/cli/flags.js +3 -138
  135. package/dist/cli/graph.d.ts +3 -0
  136. package/dist/cli/graph.js +83 -0
  137. package/dist/cli/hook-runtime.d.ts +3 -5
  138. package/dist/cli/hook-runtime.js +7 -34
  139. package/dist/cli/incidents.d.ts +3 -0
  140. package/dist/cli/incidents.js +112 -0
  141. package/dist/cli/init.d.ts +1 -1
  142. package/dist/cli/init.js +28 -19
  143. package/dist/cli/install-steps.d.ts +2 -1
  144. package/dist/cli/install-steps.js +11 -3
  145. package/dist/cli/last-sleep.js +1 -1
  146. package/dist/cli/lenient-id.d.ts +2 -0
  147. package/dist/cli/lenient-id.js +13 -0
  148. package/dist/cli/maintenance.d.ts +3 -3
  149. package/dist/cli/maintenance.js +29 -30
  150. package/dist/cli/notes.d.ts +2 -0
  151. package/dist/cli/notes.js +41 -0
  152. package/dist/cli/open-browser.d.ts +14 -0
  153. package/dist/cli/open-browser.js +26 -0
  154. package/dist/cli/playbooks.d.ts +3 -4
  155. package/dist/cli/playbooks.js +126 -290
  156. package/dist/cli/predictions.d.ts +3 -0
  157. package/dist/cli/predictions.js +175 -0
  158. package/dist/cli/print.js +2 -1
  159. package/dist/cli/projects.js +30 -22
  160. package/dist/cli/quality-repair-once.d.ts +1 -1
  161. package/dist/cli/quality-repair-once.js +6 -5
  162. package/dist/cli/recall.js +13 -7
  163. package/dist/cli/refine-llm.js +17 -17
  164. package/dist/cli/remember.js +12 -12
  165. package/dist/cli/scheduler.js +2 -1
  166. package/dist/cli/serve.js +3 -3
  167. package/dist/cli/session-hooks.d.ts +1 -1
  168. package/dist/cli/session-hooks.js +45 -115
  169. package/dist/cli/setup.d.ts +1 -1
  170. package/dist/cli/setup.js +17 -10
  171. package/dist/cli/shared.d.ts +2 -6
  172. package/dist/cli/shared.js +9 -29
  173. package/dist/cli/sleep-render.js +6 -3
  174. package/dist/cli/sleep.js +6 -37
  175. package/dist/cli/status.d.ts +3 -3
  176. package/dist/cli/status.js +32 -35
  177. package/dist/cli/transfer.d.ts +1 -1
  178. package/dist/cli/transfer.js +33 -31
  179. package/dist/cli/usage.d.ts +0 -79
  180. package/dist/cli/usage.js +1 -695
  181. package/dist/cli/verb-row.d.ts +32 -0
  182. package/dist/cli/verb-row.js +6 -0
  183. package/dist/cli/verbs/health.d.ts +27 -0
  184. package/dist/cli/verbs/health.js +232 -0
  185. package/dist/cli/verbs/memory.d.ts +16 -0
  186. package/dist/cli/verbs/memory.js +244 -0
  187. package/dist/cli/verbs/objects.d.ts +20 -0
  188. package/dist/cli/verbs/objects.js +254 -0
  189. package/dist/cli/verbs/sessions.d.ts +31 -0
  190. package/dist/cli/verbs/sessions.js +312 -0
  191. package/dist/cli/verbs.d.ts +81 -0
  192. package/dist/cli/verbs.js +7 -0
  193. package/dist/cli/versioned-verbs.d.ts +51 -0
  194. package/dist/cli/versioned-verbs.js +79 -0
  195. package/dist/cli.d.ts +1 -272
  196. package/dist/cli.js +23 -313
  197. package/dist/connectors/dlq.d.ts +39 -3
  198. package/dist/connectors/dlq.js +37 -3
  199. package/dist/connectors/github/backfill.js +12 -3
  200. package/dist/connectors/github/dlq.js +31 -73
  201. package/dist/connectors/github/ingest.d.ts +2 -6
  202. package/dist/connectors/github/ingest.js +3 -28
  203. package/dist/connectors/github/webhook.d.ts +2 -1
  204. package/dist/connectors/github/webhook.js +33 -2
  205. package/dist/connectors/ingest.d.ts +10 -0
  206. package/dist/connectors/ingest.js +28 -0
  207. package/dist/connectors/slack/dlq.js +49 -71
  208. package/dist/connectors/slack/ingest.d.ts +2 -6
  209. package/dist/connectors/slack/ingest.js +2 -27
  210. package/dist/connectors/slack/webhook.d.ts +2 -1
  211. package/dist/connectors/slack/webhook.js +2 -1
  212. package/dist/consolidate/conflicts.js +1 -1
  213. package/dist/consolidate/dag.js +15 -9
  214. package/dist/consolidate/dedupe.d.ts +7 -0
  215. package/dist/consolidate/dedupe.js +5 -3
  216. package/dist/consolidate/llm-passes.js +45 -19
  217. package/dist/consolidate/memory-value.js +2 -1
  218. package/dist/consolidate/merge.js +2 -2
  219. package/dist/consolidate/physics-pass.d.ts +6 -1
  220. package/dist/consolidate/physics-pass.js +26 -6
  221. package/dist/consolidate/replay.js +11 -4
  222. package/dist/consolidate/sleep.js +8 -5
  223. package/dist/consolidate/traces.js +3 -3
  224. package/dist/core/ablation.d.ts +10 -0
  225. package/dist/core/ablation.js +12 -0
  226. package/dist/{sharing/scope.d.ts → core/active-scope.d.ts} +1 -1
  227. package/dist/{sharing/scope.js → core/active-scope.js} +1 -1
  228. package/dist/core/ambient.d.ts +4 -0
  229. package/dist/core/ambient.js +39 -18
  230. package/dist/core/api-errors.d.ts +16 -0
  231. package/dist/core/api-errors.js +20 -0
  232. package/dist/core/card.js +3 -1
  233. package/dist/core/compaction-timing.d.ts +7 -0
  234. package/dist/core/compaction-timing.js +9 -0
  235. package/dist/core/config.d.ts +1 -1
  236. package/dist/core/config.js +7 -2
  237. package/dist/core/cosine.d.ts +3 -0
  238. package/dist/core/cosine.js +19 -0
  239. package/dist/{store → core}/markdown.d.ts +1 -1
  240. package/dist/{store → core}/markdown.js +2 -2
  241. package/dist/core/memory-audit.d.ts +17 -0
  242. package/dist/core/memory-audit.js +54 -0
  243. package/dist/core/memory.d.ts +10 -4
  244. package/dist/core/memory.js +25 -18
  245. package/dist/{consolidate → core}/merged-row.d.ts +1 -1
  246. package/dist/{consolidate → core}/merged-row.js +1 -1
  247. package/dist/core/physics.js +6 -16
  248. package/dist/core/prediction.d.ts +3 -0
  249. package/dist/core/prediction.js +6 -0
  250. package/dist/core/project-identity.d.ts +3 -0
  251. package/dist/core/project-identity.js +10 -1
  252. package/dist/{store → core}/recall-scope.d.ts +1 -10
  253. package/dist/{store → core}/recall-scope.js +2 -19
  254. package/dist/db/busy.d.ts +2 -7
  255. package/dist/db/busy.js +21 -26
  256. package/dist/db/connect.d.ts +0 -1
  257. package/dist/db/connect.js +5 -6
  258. package/dist/db/index.d.ts +3 -3
  259. package/dist/db/index.js +3 -3
  260. package/dist/db/migrations/index.d.ts +1 -1
  261. package/dist/db/migrations/index.js +3 -1
  262. package/dist/db/migrations/v55.d.ts +3 -0
  263. package/dist/db/migrations/v55.js +17 -0
  264. package/dist/db/open.d.ts +0 -2
  265. package/dist/db/open.js +6 -10
  266. package/dist/db/physics-state.js +3 -3
  267. package/dist/db/tables.d.ts +2 -0
  268. package/dist/db/tables.js +6 -2
  269. package/dist/db/vector-store.js +17 -4
  270. package/dist/db/wal-checkpointer.js +2 -1
  271. package/dist/doctor.js +11 -8
  272. package/dist/embeddings/local.d.ts +62 -0
  273. package/dist/embeddings/local.js +146 -0
  274. package/dist/{store/embeddings → embeddings}/provider.d.ts +5 -2
  275. package/dist/{store/embeddings → embeddings}/provider.js +18 -15
  276. package/dist/embeddings/transformers.d.ts +26 -0
  277. package/dist/embeddings/transformers.js +89 -0
  278. package/dist/entry/session-text.d.ts +1 -1
  279. package/dist/entry/session-text.js +1 -1
  280. package/dist/eval/eval-suite.js +17 -11
  281. package/dist/eval/eval.js +1 -1
  282. package/dist/eval/judgment.js +11 -4
  283. package/dist/graph/extract.js +3 -3
  284. package/dist/graph/recall.js +1 -1
  285. package/dist/graph/view.js +13 -5
  286. package/dist/graph/write.d.ts +1 -1
  287. package/dist/graph/write.js +9 -9
  288. package/dist/hooks/codex-session.js +3 -2
  289. package/dist/hooks/codex-wrapper.js +2 -1
  290. package/dist/hooks/json-hooks.js +4 -2
  291. package/dist/hooks/opencode.js +8 -5
  292. package/dist/hooks/shared.d.ts +0 -6
  293. package/dist/hooks/shared.js +4 -15
  294. package/dist/importers/core.js +1 -1
  295. package/dist/importers/sources.js +5 -4
  296. package/dist/importers/vault.js +7 -5
  297. package/dist/index.d.ts +3 -2
  298. package/dist/index.js +3 -2
  299. package/dist/learn/autolearn.d.ts +10 -2
  300. package/dist/learn/autolearn.js +32 -12
  301. package/dist/learn/churn-git.js +13 -4
  302. package/dist/learn/extract.d.ts +3 -2
  303. package/dist/learn/extract.js +6 -4
  304. package/dist/learn/forward-claim-detector.js +4 -2
  305. package/dist/learn/invalidation.js +10 -6
  306. package/dist/mcp/admin-tools.d.ts +2 -2
  307. package/dist/mcp/admin-tools.js +27 -48
  308. package/dist/mcp/format.js +3 -2
  309. package/dist/mcp/framing.d.ts +1 -0
  310. package/dist/mcp/framing.js +1 -1
  311. package/dist/mcp/memory-tools.js +3 -1
  312. package/dist/mcp/protocol.d.ts +11 -7
  313. package/dist/mcp/protocol.js +25 -12
  314. package/dist/mcp/recall-tools.js +12 -9
  315. package/dist/mcp/request.js +2 -1
  316. package/dist/mcp/stdio.js +24 -8
  317. package/dist/mcp/tool-args.js +2 -1
  318. package/dist/mcp/tools.js +44 -21
  319. package/dist/objects/customer-notes.d.ts +2 -2
  320. package/dist/objects/decisions.d.ts +2 -2
  321. package/dist/objects/descriptor.d.ts +1 -1
  322. package/dist/objects/incidents.d.ts +2 -2
  323. package/dist/objects/lifecycle.d.ts +1 -1
  324. package/dist/objects/lifecycle.js +2 -1
  325. package/dist/objects/policies.d.ts +2 -2
  326. package/dist/objects/policies.js +2 -1
  327. package/dist/objects/processes.d.ts +2 -2
  328. package/dist/objects/project-briefs.d.ts +2 -2
  329. package/dist/objects/project-briefs.js +2 -3
  330. package/dist/objects/skills.d.ts +2 -2
  331. package/dist/postinstall.js +1 -1
  332. package/dist/rerankers/clef.js +3 -6
  333. package/dist/rerankers/cross-encoder.d.ts +31 -2
  334. package/dist/rerankers/cross-encoder.js +63 -97
  335. package/dist/rerankers/index.js +5 -4
  336. package/dist/rerankers/jev.js +2 -4
  337. package/dist/rerankers/outage-warning.d.ts +3 -1
  338. package/dist/rerankers/outage-warning.js +5 -3
  339. package/dist/search/bm25-search.js +1 -1
  340. package/dist/search/boosts.js +1 -1
  341. package/dist/search/embed-memories.d.ts +9 -0
  342. package/dist/search/embed-memories.js +377 -0
  343. package/dist/search/goal-boost.d.ts +1 -1
  344. package/dist/search/goal-boost.js +6 -3
  345. package/dist/search/hybrid.d.ts +9 -2
  346. package/dist/search/hybrid.js +9 -4
  347. package/dist/search/physics-search.d.ts +6 -3
  348. package/dist/search/physics-search.js +22 -15
  349. package/dist/search/rerank.js +1 -1
  350. package/dist/search/vector.d.ts +8 -6
  351. package/dist/search/vector.js +10 -13
  352. package/dist/server/auth.d.ts +2 -0
  353. package/dist/server/auth.js +17 -7
  354. package/dist/server/boot.js +7 -66
  355. package/dist/server/client-ip.d.ts +0 -2
  356. package/dist/server/client-ip.js +3 -2
  357. package/dist/server/deadline.d.ts +0 -2
  358. package/dist/server/deadline.js +1 -7
  359. package/dist/server/defaults.d.ts +1 -0
  360. package/dist/server/defaults.js +1 -0
  361. package/dist/server/mcp-http.js +16 -11
  362. package/dist/server/request.js +7 -2
  363. package/dist/server/route-checks.d.ts +7 -0
  364. package/dist/server/route-checks.js +42 -0
  365. package/dist/server/route-table.d.ts +9 -8
  366. package/dist/server/route-table.js +127 -132
  367. package/dist/server/routes/admin.d.ts +8 -8
  368. package/dist/server/routes/admin.js +13 -21
  369. package/dist/server/routes/customer-notes.d.ts +6 -6
  370. package/dist/server/routes/customer-notes.js +1 -2
  371. package/dist/server/routes/decisions.d.ts +6 -6
  372. package/dist/server/routes/decisions.js +2 -4
  373. package/dist/server/routes/incidents.d.ts +6 -6
  374. package/dist/server/routes/incidents.js +2 -5
  375. package/dist/server/routes/memories.d.ts +9 -9
  376. package/dist/server/routes/memories.js +15 -38
  377. package/dist/server/routes/object-routes.d.ts +6 -6
  378. package/dist/server/routes/object-routes.js +4 -7
  379. package/dist/server/routes/policies.d.ts +7 -7
  380. package/dist/server/routes/policies.js +2 -4
  381. package/dist/server/routes/predictions.d.ts +6 -6
  382. package/dist/server/routes/predictions.js +9 -13
  383. package/dist/server/routes/processes.d.ts +6 -6
  384. package/dist/server/routes/processes.js +1 -2
  385. package/dist/server/routes/project-briefs.d.ts +7 -7
  386. package/dist/server/routes/project-briefs.js +2 -4
  387. package/dist/server/routes/recall.d.ts +5 -5
  388. package/dist/server/routes/recall.js +6 -10
  389. package/dist/server/routes/skills.d.ts +7 -7
  390. package/dist/server/routes/skills.js +2 -4
  391. package/dist/server/server-detect.d.ts +7 -1
  392. package/dist/server/server-detect.js +36 -11
  393. package/dist/server/sleep-child.js +26 -7
  394. package/dist/server/sleep-offload.d.ts +11 -5
  395. package/dist/server/sleep-offload.js +17 -11
  396. package/dist/server/types.d.ts +22 -10
  397. package/dist/server/validation.d.ts +1 -2
  398. package/dist/server/validation.js +6 -11
  399. package/dist/server.d.ts +5 -4
  400. package/dist/server.js +5 -4
  401. package/dist/sharing/global-store.js +2 -2
  402. package/dist/sharing/global-sync.js +38 -45
  403. package/dist/sharing/project-merge.d.ts +7 -14
  404. package/dist/sharing/project-merge.js +57 -121
  405. package/dist/sharing/search-both.js +1 -1
  406. package/dist/sharing/share.d.ts +2 -0
  407. package/dist/sharing/share.js +37 -34
  408. package/dist/store/ambient.js +5 -6
  409. package/dist/store/audit-event.d.ts +1 -1
  410. package/dist/store/audit.d.ts +0 -16
  411. package/dist/store/audit.js +4 -55
  412. package/dist/store/auth.d.ts +4 -2
  413. package/dist/store/auth.js +7 -3
  414. package/dist/store/candidates.d.ts +3 -2
  415. package/dist/store/candidates.js +42 -23
  416. package/dist/store/capture-write.d.ts +17 -0
  417. package/dist/store/capture-write.js +35 -0
  418. package/dist/store/cards.js +1 -1
  419. package/dist/store/compaction-caller.d.ts +18 -0
  420. package/dist/store/compaction-caller.js +29 -0
  421. package/dist/store/compactions-record.d.ts +115 -0
  422. package/dist/store/compactions-record.js +417 -0
  423. package/dist/store/compactions.js +6 -4
  424. package/dist/store/conflicts.js +7 -4
  425. package/dist/store/connectors/dlq.d.ts +10 -0
  426. package/dist/store/connectors/dlq.js +32 -0
  427. package/dist/store/connectors/github.d.ts +7 -9
  428. package/dist/store/connectors/github.js +30 -53
  429. package/dist/store/connectors/slack.d.ts +2 -11
  430. package/dist/store/connectors/slack.js +18 -47
  431. package/dist/store/dashboard-reads.js +7 -6
  432. package/dist/store/delete-and-batch.d.ts +6 -0
  433. package/dist/store/delete-and-batch.js +36 -3
  434. package/dist/store/delivery-recorder.d.ts +10 -3
  435. package/dist/store/delivery-recorder.js +12 -5
  436. package/dist/store/descendant-page.js +6 -3
  437. package/dist/store/dormant.d.ts +1 -1
  438. package/dist/store/dormant.js +3 -1
  439. package/dist/store/embeddings/index.d.ts +2 -12
  440. package/dist/store/embeddings/index.js +5 -374
  441. package/dist/store/entry-reads.d.ts +13 -4
  442. package/dist/store/entry-reads.js +47 -11
  443. package/dist/store/entry-writes.d.ts +5 -5
  444. package/dist/store/entry-writes.js +32 -12
  445. package/dist/store/failure-log-at.d.ts +8 -0
  446. package/dist/store/failure-log-at.js +16 -0
  447. package/dist/store/failure-log.d.ts +2 -2
  448. package/dist/store/failure-log.js +5 -3
  449. package/dist/{trust → store}/gated-write.d.ts +3 -1
  450. package/dist/{trust → store}/gated-write.js +7 -5
  451. package/dist/store/goals.js +20 -18
  452. package/dist/store/graph-reads.js +4 -2
  453. package/dist/store/graph-writes.d.ts +6 -6
  454. package/dist/store/graph-writes.js +48 -71
  455. package/dist/store/handoffs.d.ts +1 -1
  456. package/dist/store/handoffs.js +6 -6
  457. package/dist/store/index-and-stats.js +2 -2
  458. package/dist/store/key-writes.js +1 -1
  459. package/dist/store/ledger-turn.d.ts +9 -0
  460. package/dist/store/ledger-turn.js +38 -0
  461. package/dist/store/mirrors.js +10 -6
  462. package/dist/store/open.d.ts +0 -4
  463. package/dist/store/open.js +40 -53
  464. package/dist/store/pilot-arm.d.ts +12 -0
  465. package/dist/store/pilot-arm.js +42 -0
  466. package/dist/store/port.d.ts +9 -3
  467. package/dist/store/port.js +8 -0
  468. package/dist/store/predictions.d.ts +1 -2
  469. package/dist/store/predictions.js +3 -6
  470. package/dist/store/project-tags.d.ts +62 -0
  471. package/dist/store/project-tags.js +67 -0
  472. package/dist/store/quarantine.js +2 -1
  473. package/dist/store/recall-trace.d.ts +2 -0
  474. package/dist/store/recall-trace.js +18 -2
  475. package/dist/store/rejected-values.js +2 -1
  476. package/dist/store/rejection-probe.d.ts +4 -0
  477. package/dist/store/rejection-probe.js +19 -0
  478. package/dist/store/rejection.d.ts +0 -17
  479. package/dist/store/rejection.js +1 -21
  480. package/dist/store/rule-sql.d.ts +9 -0
  481. package/dist/store/rule-sql.js +21 -2
  482. package/dist/store/search-rows.js +9 -4
  483. package/dist/store/sessions.js +4 -2
  484. package/dist/store/set-aside.d.ts +14 -0
  485. package/dist/store/set-aside.js +23 -0
  486. package/dist/store/sqlite/busy.d.ts +3 -0
  487. package/dist/store/sqlite/busy.js +8 -0
  488. package/dist/store/sqlite/connector-events-group.js +4 -7
  489. package/dist/store/sqlite/connector-writes-group.js +3 -3
  490. package/dist/store/sqlite/entry-writes-group.js +2 -2
  491. package/dist/store/sqlite/error-codec.js +4 -3
  492. package/dist/store/sqlite/executor.js +10 -4
  493. package/dist/store/sqlite/graph-reads-group.js +1 -1
  494. package/dist/store/sqlite/held-connection.d.ts +9 -0
  495. package/dist/store/sqlite/held-connection.js +41 -0
  496. package/dist/store/sqlite/object-rows.d.ts +1 -1
  497. package/dist/store/sqlite/objects-group.d.ts +1 -1
  498. package/dist/store/sqlite/objects-group.js +1 -1
  499. package/dist/store/sqlite/store.js +14 -12
  500. package/dist/store/summaries.d.ts +3 -3
  501. package/dist/store/summaries.js +5 -4
  502. package/dist/store/token-ledger.d.ts +0 -7
  503. package/dist/store/token-ledger.js +0 -35
  504. package/dist/store/vector-index.js +3 -2
  505. package/dist/support-bundle.js +2 -3
  506. package/dist/trust/reject-flow.js +2 -2
  507. package/dist/util/agent-homes.d.ts +15 -0
  508. package/dist/util/agent-homes.js +34 -0
  509. package/dist/util/anthropic-messages.d.ts +3 -0
  510. package/dist/util/anthropic-messages.js +22 -5
  511. package/dist/util/chunked.d.ts +3 -0
  512. package/dist/util/chunked.js +10 -0
  513. package/dist/util/ellipsize.d.ts +3 -0
  514. package/dist/util/ellipsize.js +5 -0
  515. package/dist/util/env.d.ts +2 -1
  516. package/dist/util/env.js +2 -1
  517. package/dist/util/format.d.ts +3 -0
  518. package/dist/util/format.js +6 -0
  519. package/dist/util/hook-payload.d.ts +8 -0
  520. package/dist/util/hook-payload.js +37 -0
  521. package/dist/util/http-retry.d.ts +6 -0
  522. package/dist/util/http-retry.js +43 -5
  523. package/dist/util/http-util.d.ts +0 -8
  524. package/dist/util/http-util.js +10 -21
  525. package/dist/util/limits.d.ts +7 -0
  526. package/dist/util/limits.js +14 -0
  527. package/dist/util/log.d.ts +4 -0
  528. package/dist/util/log.js +9 -0
  529. package/dist/util/request-scope.d.ts +3 -1
  530. package/dist/util/request-scope.js +11 -6
  531. package/dist/util/secret-detect.js +6 -1
  532. package/dist/util/sqlite-blocked.d.ts +2 -0
  533. package/dist/util/sqlite-blocked.js +5 -0
  534. package/dist/util/stream-tee.d.ts +3 -0
  535. package/dist/util/stream-tee.js +35 -0
  536. package/dist/util/time.d.ts +4 -0
  537. package/dist/util/time.js +4 -0
  538. package/dist/util/version.d.ts +1 -1
  539. package/dist/util/version.js +1 -1
  540. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  541. package/extensions/openclaw-plugin/package.json +1 -1
  542. package/openclaw.plugin.json +1 -1
  543. package/package.json +2 -2
  544. package/dist/store/embeddings/local.d.ts +0 -15
  545. package/dist/store/embeddings/local.js +0 -175
  546. /package/dist/{cli → api}/correction-latency.d.ts +0 -0
  547. /package/dist/{cli → api}/correction-latency.js +0 -0
  548. /package/dist/{cli → api}/provenance-coverage.d.ts +0 -0
  549. /package/dist/{cli → api}/provenance-coverage.js +0 -0
  550. /package/dist/{store → core}/object-types.d.ts +0 -0
  551. /package/dist/{store → core}/object-types.js +0 -0
  552. /package/dist/{capture → util}/compaction-items.d.ts +0 -0
  553. /package/dist/{capture → util}/compaction-items.js +0 -0
  554. /package/dist/{store → util}/yaml.d.ts +0 -0
  555. /package/dist/{store → util}/yaml.js +0 -0
@@ -1,10 +1,6 @@
1
- import { type Context } from '../../api/index.js';
1
+ import type { Context } from '../../api/index.js';
2
+ import { type IngestResult } from '../ingest.js';
2
3
  import type { GitHubIssueEvent, GitHubIssueCommentEvent, GitHubPullRequestEvent, GitHubPullRequestReviewCommentEvent } from './types.js';
3
- export type IngestStatus = 'ingested' | 'duplicate' | 'skipped' | 'skipped_duplicate';
4
- export interface IngestResult {
5
- status: IngestStatus;
6
- memoryId: string | null;
7
- }
8
4
  /** Discriminated union of the four event shapes V1 ingests; eventName MUST equal the X-GitHub-Event header,
9
5
  * since computeIdempotencyKey folds it into the dedupe key. */
10
6
  export type IngestEvent = {
@@ -1,9 +1,8 @@
1
1
  // GitHub event ingest, once per idempotency key. The key comes from the event's own content, never from the unsigned
2
2
  // X-GitHub-Delivery header, so a replay cannot get past it by rotating the delivery id. The pre-check is only the fast path:
3
3
  // the store logs the key in the memory's own transaction, which is what holds when two workers race.
4
- import { remember } from '../../api/index.js';
5
4
  import { requireGroup, storeFor } from '../../store/index.js';
6
- import { RejectedValueError } from '../../store/rejection.js';
5
+ import { rememberWithEventLog } from '../ingest.js';
7
6
  import { computeIdempotencyKey } from './signature.js';
8
7
  import { issueEventToRememberOpts, issueCommentEventToRememberOpts, pullRequestEventToRememberOpts, prReviewCommentEventToRememberOpts, } from './transform.js';
9
8
  function transformEvent(event) {
@@ -54,31 +53,7 @@ export async function ingestEvent(ctx, input) {
54
53
  const seen = await events.eventRecord(event);
55
54
  if (seen.seen)
56
55
  return { status: 'duplicate', memoryId: seen.memoryId };
57
- const opts = transformEvent(input.event);
58
- if (!opts) {
59
- // Empty body: no memory to write, but mark seen so a retry returns 'duplicate' instead of re-running the transform.
60
- await events.markEventSeen(event);
61
- return { status: 'skipped', memoryId: null };
62
- }
63
- try {
64
- return await rememberWithEventLog(ctx, event, opts);
65
- }
66
- catch (e) {
67
- if (e instanceof RejectedValueError) {
68
- // A tombstone hit is a PERMANENT skip, never DLQ-retried: mark the key seen like the empty-body
69
- // branch above so a GitHub retry of the same delivery acks as done, not error.
70
- await events.markEventSeen(event);
71
- return { status: 'skipped', memoryId: null };
72
- }
73
- throw e;
74
- }
75
- }
76
- async function rememberWithEventLog(ctx, event, opts) {
77
- // No `|| 'connector:github'` fallback: the caller always builds ctx with the connector subject (same as slack/ingest.ts).
78
- const result = await remember(ctx, { ...opts, untrusted: true, event });
79
- // Another worker logged this key between the pre-check and the write: its memory stands and ours was not stored.
80
- if (result.duplicate)
81
- return { status: 'skipped_duplicate', memoryId: result.duplicate.memoryId };
82
- return { status: 'ingested', memoryId: result.id };
56
+ // An empty body is logged too, so a retry returns 'duplicate' instead of re-running the transform.
57
+ return rememberWithEventLog(ctx, event, transformEvent(input.event));
83
58
  }
84
59
  //# sourceMappingURL=ingest.js.map
@@ -1,6 +1,7 @@
1
1
  import type { HippoStore } from '../../store/index.js';
2
2
  import { type WebhookRequest } from '../../util/http-util.js';
3
3
  /** GitHub webhook receiver: HMAC SHA-256 over the raw body, event type from the X-GitHub-Event header, tenant by installation.id then repository.full_name.
4
- * ACKs 200 on signed envelopes (DLQ too); 401 only on a bad signature; 404 when GITHUB_WEBHOOK_SECRET is unset. No Bearer: server.ts checks isPublicRoute. */
4
+ * ACKs 200 on signed envelopes (DLQ too); 401 only on a bad signature; 409 on a delivery id seen in the replay window;
5
+ * 404 when GITHUB_WEBHOOK_SECRET is unset. No Bearer: its route row declares webhook-signature. */
5
6
  export declare function handleGitHubEventsWebhook(request: WebhookRequest, store?: HippoStore): Promise<void>;
6
7
  //# sourceMappingURL=webhook.d.ts.map
@@ -10,7 +10,8 @@ import { computeDeletionKey as computeGitHubDeletionKey } from './signature.js';
10
10
  import { resolveTenantId } from '../../store/tenant.js';
11
11
  import { HttpError, JSON_HEADERS, isHeaderString, closeIfBodyUnread, readWebhookBody, sendJson, } from '../../util/http-util.js';
12
12
  /** GitHub webhook receiver: HMAC SHA-256 over the raw body, event type from the X-GitHub-Event header, tenant by installation.id then repository.full_name.
13
- * ACKs 200 on signed envelopes (DLQ too); 401 only on a bad signature; 404 when GITHUB_WEBHOOK_SECRET is unset. No Bearer: server.ts checks isPublicRoute. */
13
+ * ACKs 200 on signed envelopes (DLQ too); 401 only on a bad signature; 409 on a delivery id seen in the replay window;
14
+ * 404 when GITHUB_WEBHOOK_SECRET is unset. No Bearer: its route row declares webhook-signature. */
14
15
  export async function handleGitHubEventsWebhook(request, store) {
15
16
  const { req, res, opts } = request;
16
17
  // Secret and signature header before the body, so a caller with neither cannot make the server buffer one.
@@ -43,7 +44,37 @@ export async function handleGitHubEventsWebhook(request, store) {
43
44
  }
44
45
  // Signature OK from here on. Everything else is ACK-200; bad envelopes go
45
46
  // to the DLQ and a human can replay later.
46
- await routeSignedDelivery({ hippoRoot: opts.hippoRoot, store, res, rawBody, eventName, deliveryId, signature: sigStr });
47
+ const replayKey = deliveryId === null ? null : `${opts.hippoRoot}\n${deliveryId}`;
48
+ if (replayKey !== null && !claimDelivery(replayKey, Date.now()))
49
+ throw new HttpError(409, 'duplicate X-GitHub-Delivery');
50
+ try {
51
+ await routeSignedDelivery({ hippoRoot: opts.hippoRoot, store, res, rawBody, eventName, deliveryId, signature: sigStr });
52
+ }
53
+ catch (err) {
54
+ // A delivery that failed left nothing stored, so GitHub's redelivery of the same id must get through.
55
+ if (replayKey !== null)
56
+ seenDeliveries.delete(replayKey);
57
+ throw err;
58
+ }
59
+ }
60
+ // GitHub signs no timestamp, so a captured delivery verifies forever; this window is what refuses its replay.
61
+ const DELIVERY_REPLAY_TTL_MS = 24 * 60 * 60 * 1000;
62
+ const DELIVERY_REPLAY_MAX_IDS = 10_000;
63
+ // SHORTCUT: seen ids live in this process and reset on restart or eviction; move them to the store if serve ever runs as several processes.
64
+ const seenDeliveries = new Map();
65
+ /** Claims a delivery id for the window; false when it was claimed within the window already. The oldest claim goes first at the cap. */
66
+ function claimDelivery(key, now) {
67
+ const seenAt = seenDeliveries.get(key);
68
+ if (seenAt !== undefined && now - seenAt < DELIVERY_REPLAY_TTL_MS)
69
+ return false;
70
+ seenDeliveries.delete(key);
71
+ if (seenDeliveries.size >= DELIVERY_REPLAY_MAX_IDS) {
72
+ const oldest = seenDeliveries.keys().next();
73
+ if (!oldest.done)
74
+ seenDeliveries.delete(oldest.value);
75
+ }
76
+ seenDeliveries.set(key, now);
77
+ return true;
47
78
  }
48
79
  const ALLOWED_EVENTS = new Set([
49
80
  'issues',
@@ -0,0 +1,10 @@
1
+ import { type Context, type RememberOpts } from '../api/index.js';
2
+ import type { ConnectorEvent } from '../store/port.js';
3
+ export type IngestStatus = 'ingested' | 'duplicate' | 'skipped' | 'skipped_duplicate';
4
+ export interface IngestResult {
5
+ status: IngestStatus;
6
+ memoryId: string | null;
7
+ }
8
+ /** Stores `opts` once per event key; null `opts` (an empty body) and a tombstoned value are logged with no memory, so a redelivery acks instead of retrying. */
9
+ export declare function rememberWithEventLog(ctx: Context, event: ConnectorEvent, opts: RememberOpts | null): Promise<IngestResult>;
10
+ //# sourceMappingURL=ingest.d.ts.map
@@ -0,0 +1,28 @@
1
+ // The write half every connector's ingest shares, after its own pre-check of the event log.
2
+ import { remember } from '../api/index.js';
3
+ import { requireGroup, storeFor } from '../store/index.js';
4
+ import { RejectedValueError } from '../core/api-errors.js';
5
+ /** Stores `opts` once per event key; null `opts` (an empty body) and a tombstoned value are logged with no memory, so a redelivery acks instead of retrying. */
6
+ export async function rememberWithEventLog(ctx, event, opts) {
7
+ if (!opts)
8
+ return markSkipped(ctx, event);
9
+ try {
10
+ // No actor fallback: every caller builds ctx with its connector subject.
11
+ const result = await remember(ctx, { ...opts, untrusted: true, event });
12
+ // Another worker logged this key between the pre-check and the write: its memory stands and ours was not stored.
13
+ if (result.duplicate)
14
+ return { status: 'skipped_duplicate', memoryId: result.duplicate.memoryId };
15
+ return { status: 'ingested', memoryId: result.id };
16
+ }
17
+ catch (e) {
18
+ // A tombstone refusal is permanent, so a DLQ retry would hit it forever.
19
+ if (e instanceof RejectedValueError)
20
+ return markSkipped(ctx, event);
21
+ throw e;
22
+ }
23
+ }
24
+ async function markSkipped(ctx, event) {
25
+ await requireGroup(storeFor(ctx), 'connectorEvents').markEventSeen(event);
26
+ return { status: 'skipped', memoryId: null };
27
+ }
28
+ //# sourceMappingURL=ingest.js.map
@@ -1,96 +1,75 @@
1
1
  import { adminActor } from '../../api/index.js';
2
- import { bumpSlackDlqRetryCount, listSlackDlq, markSlackDlqRetried, slackDlqEntry, } from '../../store/connectors/slack.js';
3
- import { replayFailed } from '../dlq.js';
2
+ import { listSlackDlq, markSlackDlqRetried, slackDlqEntry, } from '../../store/connectors/slack.js';
3
+ import { replayParked } from '../dlq.js';
4
4
  import { ingestMessage } from './ingest.js';
5
5
  import { resolveTenantForSlackTeam } from './tenant-routing.js';
6
6
  import { verifySlackSignature } from './signature.js';
7
7
  import { isSlackEventEnvelope, isSlackMessageEvent } from './types.js';
8
8
  import { handleMessageDeleted } from './deletion.js';
9
- import { errorMessage } from '../../util/log.js';
9
+ import { YEAR_S } from '../../util/time.js';
10
10
  /** Slack's dead-letter table; `teamId` and `slackTimestamp` are the columns only it has. */
11
11
  export const slackDlq = {
12
12
  letter: (row) => ({ connector: 'slack', ...row }),
13
13
  list: listSlackDlq,
14
+ entry: slackDlqEntry,
15
+ bump: markSlackDlqRetried,
14
16
  };
15
17
  /** Replay a DLQ row via the normal ingest path (`hippo slack dlq replay <id> [--force]`), re-verifying the signature with the CURRENT secret unless forced.
16
18
  * Rows with a NULL signature need --force; replays use today's routing, because the DLQ exists to be drained after the operator fixed routing. */
17
19
  export async function replayDlqEntry(ctx, id, opts = {}) {
18
- const row = slackDlqEntry(ctx.hippoRoot, id);
19
- if (!row)
20
- return replayFailed('not_found', 0, `dlq id ${id} not found`);
21
- // Signature verification (current secret, not previous).
22
- if (!opts.force) {
23
- const sigFailure = checkReplaySignature(row, opts);
24
- if (sigFailure)
25
- return sigFailure;
26
- }
27
- // Parse + dispatch.
28
- let parsed;
29
- try {
30
- parsed = JSON.parse(row.rawPayload);
31
- }
32
- catch (e) {
33
- return failAndBump(ctx.hippoRoot, row, 'parse_error', `still unparseable: ${errorMessage(e)}`);
34
- }
35
- if (!isSlackEventEnvelope(parsed)) {
36
- return failAndBump(ctx.hippoRoot, row, 'unhandled', 'not an event_callback envelope');
37
- }
38
- // Resolve tenant against current state. If still unroutable, bail.
39
- const tenant = await resolveTenantForSlackTeam(ctx.hippoRoot, parsed.team_id);
40
- if (!tenant) {
41
- return failAndBump(ctx.hippoRoot, row, 'unroutable', `team_id ${parsed.team_id} still unroutable`);
42
- }
43
- const replayCtx = {
44
- hippoRoot: ctx.hippoRoot,
45
- tenantId: tenant,
46
- actor: adminActor('connector:slack:replay'),
47
- };
48
- return dispatchReplay(replayCtx, row, parsed);
20
+ return replayParked({
21
+ dlq: slackDlq,
22
+ refuseSignature: (row) => refuseSignature(row, opts),
23
+ isEnvelope: isSlackEventEnvelope,
24
+ notEnvelope: 'not an event_callback envelope',
25
+ reingest: (_row, envelope) => reingest(ctx.hippoRoot, envelope),
26
+ }, ctx.hippoRoot, id, opts.force === true);
49
27
  }
50
- /** The failure result when the row cannot pass the signature gate, else null; never bumps the count. */
51
- function checkReplaySignature(row, opts) {
28
+ /** A missing signature or timestamp refuses even without a secret; a mismatch needs the current secret to show. */
29
+ function refuseSignature(row, opts) {
52
30
  if (!row.signature || !row.slackTimestamp) {
53
- return replayFailed('sig_missing', row.retryCount, 'row has no signature/timestamp (legacy, or redacted before storing); pass --force to replay');
31
+ return {
32
+ status: 'sig_missing',
33
+ reason: 'row has no signature/timestamp (legacy, or redacted before storing); pass --force to replay',
34
+ };
54
35
  }
55
- if (opts.signingSecret) {
56
- const ok = verifySlackSignature({
57
- rawBody: row.rawPayload,
58
- signature: row.signature,
59
- timestamp: row.slackTimestamp,
60
- signingSecret: opts.signingSecret,
61
- now: opts.now,
62
- // Replays happen long after the fact — give them a wider skew unless overridden.
63
- skewSeconds: opts.skewSeconds ?? 60 * 60 * 24 * 365,
64
- });
65
- if (!ok) {
66
- return replayFailed('sig_fail', row.retryCount, 'signature did not verify against current SLACK_SIGNING_SECRET; pass --force to replay anyway');
67
- }
68
- }
69
- return null;
70
- }
71
- function failAndBump(hippoRoot, row, status, reason) {
72
- bumpSlackDlqRetryCount(hippoRoot, row.id);
73
- return replayFailed(status, row.retryCount + 1, reason);
36
+ if (!opts.signingSecret)
37
+ return null;
38
+ const ok = verifySlackSignature({
39
+ rawBody: row.rawPayload,
40
+ signature: row.signature,
41
+ timestamp: row.slackTimestamp,
42
+ signingSecret: opts.signingSecret,
43
+ now: opts.now,
44
+ // Replays happen long after the fact, so give them a wider skew unless overridden.
45
+ skewSeconds: opts.skewSeconds ?? YEAR_S,
46
+ });
47
+ if (ok)
48
+ return null;
49
+ return {
50
+ status: 'sig_fail',
51
+ reason: 'signature did not verify against current SLACK_SIGNING_SECRET; pass --force to replay anyway',
52
+ };
74
53
  }
75
- async function dispatchReplay(replayCtx, row, parsed) {
76
- const { hippoRoot } = replayCtx;
77
- const id = row.id;
78
- const inner = parsed.event;
79
- if (!isSlackMessageEvent(inner)) {
80
- return failAndBump(hippoRoot, row, 'unhandled', `unhandled inner event type`);
81
- }
54
+ async function reingest(hippoRoot, envelope) {
55
+ const tenantId = await resolveTenantForSlackTeam(hippoRoot, envelope.team_id);
56
+ if (!tenantId)
57
+ return { ok: false, status: 'unroutable', reason: `team_id ${envelope.team_id} still unroutable` };
58
+ const inner = envelope.event;
59
+ if (!isSlackMessageEvent(inner))
60
+ return { ok: false, status: 'unhandled', reason: 'unhandled inner event type' };
61
+ const replayCtx = { hippoRoot, tenantId, actor: adminActor('connector:slack:replay') };
82
62
  if (inner.subtype === 'message_deleted' && inner.deleted_ts) {
83
63
  const r = await handleMessageDeleted(replayCtx, {
84
- teamId: parsed.team_id,
64
+ teamId: envelope.team_id,
85
65
  channelId: inner.channel,
86
66
  deletedTs: inner.deleted_ts,
87
- eventId: parsed.event_id,
67
+ eventId: envelope.event_id,
88
68
  });
89
- markSlackDlqRetried(hippoRoot, id);
90
- return { ok: true, status: r.status, memoryId: r.memoryId, retryCount: row.retryCount + 1 };
69
+ return { ok: true, status: r.status, memoryId: r.memoryId };
91
70
  }
92
71
  const result = await ingestMessage(replayCtx, {
93
- teamId: parsed.team_id,
72
+ teamId: envelope.team_id,
94
73
  channel: {
95
74
  id: inner.channel,
96
75
  is_private: inner.channel_type !== 'channel',
@@ -98,9 +77,8 @@ async function dispatchReplay(replayCtx, row, parsed) {
98
77
  is_mpim: inner.channel_type === 'mpim',
99
78
  },
100
79
  message: inner,
101
- eventId: parsed.event_id,
80
+ eventId: envelope.event_id,
102
81
  });
103
- markSlackDlqRetried(hippoRoot, id);
104
- return { ok: true, status: result.status, memoryId: result.memoryId, retryCount: row.retryCount + 1 };
82
+ return { ok: true, status: result.status, memoryId: result.memoryId };
105
83
  }
106
84
  //# sourceMappingURL=dlq.js.map
@@ -1,4 +1,5 @@
1
- import { type Context } from '../../api/index.js';
1
+ import type { Context } from '../../api/index.js';
2
+ import { type IngestResult } from '../ingest.js';
2
3
  import type { ChannelMeta } from './scope.js';
3
4
  import type { SlackMessageEvent } from './types.js';
4
5
  export interface IngestInput {
@@ -8,11 +9,6 @@ export interface IngestInput {
8
9
  /** Slack event_id for the envelope (or for backfill, a synthesized stable id). */
9
10
  eventId: string;
10
11
  }
11
- export type IngestStatus = 'ingested' | 'duplicate' | 'skipped' | 'skipped_duplicate';
12
- export interface IngestResult {
13
- status: IngestStatus;
14
- memoryId: string | null;
15
- }
16
12
  /** Ingests a Slack message as a kind='raw' memory once per event id (Slack redelivers within a minute); the pre-check is only the fast path,
17
13
  * the store logs the event in the memory's own transaction, which holds when two workers race. An empty body is logged with no memory. */
18
14
  export declare function ingestMessage(ctx: Context, input: IngestInput): Promise<IngestResult>;
@@ -1,6 +1,5 @@
1
- import { remember } from '../../api/index.js';
2
1
  import { requireGroup, storeFor } from '../../store/index.js';
3
- import { RejectedValueError } from '../../store/rejection.js';
2
+ import { rememberWithEventLog } from '../ingest.js';
4
3
  import { messageToRememberOpts } from './transform.js';
5
4
  /** Ingests a Slack message as a kind='raw' memory once per event id (Slack redelivers within a minute); the pre-check is only the fast path,
6
5
  * the store logs the event in the memory's own transaction, which holds when two workers race. An empty body is logged with no memory. */
@@ -15,30 +14,6 @@ export async function ingestMessage(ctx, input) {
15
14
  // they first returned; a non-NULL memory_id means a memory was ingested, so 'duplicate'.
16
15
  return { status: seen.memoryId === null ? 'skipped' : 'duplicate', memoryId: seen.memoryId };
17
16
  }
18
- const opts = messageToRememberOpts(input);
19
- if (!opts) {
20
- await events.markEventSeen(event);
21
- return { status: 'skipped', memoryId: null };
22
- }
23
- try {
24
- return await rememberWithEventLog(ctx, event, opts);
25
- }
26
- catch (e) {
27
- if (!(e instanceof RejectedValueError))
28
- throw e;
29
- // A tombstone hit is a PERMANENT skip: a DLQ retry would hit the same refusal forever, so mark the
30
- // event seen like the empty-body branch above and let a Slack retry ack as done, not error.
31
- await events.markEventSeen(event);
32
- return { status: 'skipped', memoryId: null };
33
- }
34
- }
35
- async function rememberWithEventLog(ctx, event, opts) {
36
- // No `|| 'connector:slack'` fallback: the caller always builds ctx with the connector subject, and with
37
- // an object-shaped Context.actor an OR-fallback would never fire anyway.
38
- const result = await remember(ctx, { ...opts, untrusted: true, event });
39
- // Another worker logged this event between the pre-check and the write: its memory stands and ours was not stored.
40
- if (result.duplicate)
41
- return { status: 'skipped_duplicate', memoryId: result.duplicate.memoryId };
42
- return { status: 'ingested', memoryId: result.id };
17
+ return rememberWithEventLog(ctx, event, messageToRememberOpts(input));
43
18
  }
44
19
  //# sourceMappingURL=ingest.js.map
@@ -1,6 +1,7 @@
1
1
  import type { HippoStore } from '../../store/index.js';
2
2
  import { type WebhookRequest } from '../../util/http-util.js';
3
- /** Slack Events API webhook: HMAC over the raw body with SLACK_SIGNING_SECRET, no Bearer (hence PUBLIC_ROUTES); malformed payloads go to slack_dlq, ACK 200.
3
+ /** Slack Events API webhook: HMAC over the raw body with SLACK_SIGNING_SECRET, no Bearer (its route row declares webhook-signature);
4
+ * malformed payloads go to slack_dlq, ACK 200.
4
5
  * 404 (not 503) when the secret is unset, so a probe cannot tell a gated-off route from a missing one. */
5
6
  export declare function handleSlackEventsWebhook(request: WebhookRequest, store?: HippoStore): Promise<void>;
6
7
  //# sourceMappingURL=webhook.d.ts.map
@@ -9,7 +9,8 @@ import { resolveTenantForSlackTeam } from './tenant-routing.js';
9
9
  import { resolveTenantId } from '../../store/tenant.js';
10
10
  import { HttpError, JSON_HEADERS, isHeaderString, closeIfBodyUnread, readWebhookBody, sendJson } from '../../util/http-util.js';
11
11
  import { isJsonObject } from '../../util/json.js';
12
- /** Slack Events API webhook: HMAC over the raw body with SLACK_SIGNING_SECRET, no Bearer (hence PUBLIC_ROUTES); malformed payloads go to slack_dlq, ACK 200.
12
+ /** Slack Events API webhook: HMAC over the raw body with SLACK_SIGNING_SECRET, no Bearer (its route row declares webhook-signature);
13
+ * malformed payloads go to slack_dlq, ACK 200.
13
14
  * 404 (not 503) when the secret is unset, so a probe cannot tell a gated-off route from a missing one. */
14
15
  export async function handleSlackEventsWebhook(request, store) {
15
16
  const { req, res, opts } = request;
@@ -1,7 +1,7 @@
1
1
  import { Layer, calculateStrength } from '../core/memory.js';
2
2
  import { jaccardMinShared, overlapPartners } from './overlap-index.js';
3
3
  import { isQuarantineScope } from '../trust/quarantine.js';
4
- import { isPersonalScope } from '../store/recall-scope.js';
4
+ import { isPersonalScope } from '../core/recall-scope.js';
5
5
  import { DECAY_THRESHOLD } from './decay.js';
6
6
  // Gate contradictions on content overlap, not shared tags (`feedback`/`policy` are too coarse). The Jaccard threshold on stopword-filtered tokens
7
7
  // only applies after a polarity signal, so it filters drive-by topic similarity, not semantic drift.
@@ -1,18 +1,24 @@
1
1
  import { createMemory, Layer } from '../core/memory.js';
2
2
  import { writeEntry, writeEntriesTogether } from '../store/entry-writes.js';
3
3
  import { loadAllDirtySummaries, loadChildrenOfSummary, applyRebuildResult, clearSummaryDirtyAfterBuild, } from '../store/summaries.js';
4
- import { RejectedValueError } from '../store/rejection.js';
4
+ import { RejectedValueError } from '../core/api-errors.js';
5
5
  import { generateDagSummary } from './dag-summary.js';
6
- import { derivationScope, derivationPartitionKey } from '../store/recall-scope.js';
6
+ import { overlapPartners } from './overlap-index.js';
7
+ import { derivationScope, derivationPartitionKey } from '../core/recall-scope.js';
7
8
  import { loadConfig } from '../core/config.js';
8
9
  import { neverAutoShareTags } from '../sharing/share.js';
9
10
  import { errorFields, errorMessage, log } from '../util/log.js';
10
11
  const SUMMARY_FALLBACK_CHARS = 40;
11
12
  const DEFAULT_REBUILD_CAP = 20;
13
+ // Facts join a cluster when their tag sets overlap at least this much.
14
+ const CLUSTER_JACCARD_MIN = 0.5;
12
15
  export function clusterFacts(facts) {
13
16
  if (facts.length === 0)
14
17
  return [];
15
18
  const entityTags = facts.map((f) => f.tags.filter((t) => t.startsWith('speaker:') || t.startsWith('topic:')));
19
+ const tagSets = entityTags.map((tags) => new Set(tags));
20
+ // Every tag indexed, not a prefix: a repeated tag counts twice in `shared`, so the prefix-filter bound would not hold.
21
+ const partnersOf = overlapPartners(tagSets, () => 1);
16
22
  const assigned = new Set();
17
23
  const clusters = [];
18
24
  for (let i = 0; i < facts.length; i++) {
@@ -20,19 +26,18 @@ export function clusterFacts(facts) {
20
26
  continue;
21
27
  const cluster = [i];
22
28
  assigned.add(i);
23
- for (let j = i + 1; j < facts.length; j++) {
29
+ for (const j of partnersOf(i)) {
24
30
  if (assigned.has(j))
25
31
  continue;
26
- const shared = entityTags[i].filter((t) => entityTags[j].includes(t));
27
- const union = new Set([...entityTags[i], ...entityTags[j]]);
28
- const jaccard = union.size > 0 ? shared.length / union.size : 0;
29
- if (jaccard >= 0.5) {
32
+ const shared = entityTags[i].filter((t) => tagSets[j].has(t)).length;
33
+ const inBoth = [...tagSets[i]].filter((t) => tagSets[j].has(t)).length;
34
+ if (shared / (tagSets[i].size + tagSets[j].size - inBoth) >= CLUSTER_JACCARD_MIN) {
30
35
  cluster.push(j);
31
36
  assigned.add(j);
32
37
  }
33
38
  }
34
39
  const members = cluster.map((idx) => facts[idx]);
35
- const sharedTags = entityTags[cluster[0]].filter((t) => cluster.every((idx) => entityTags[idx].includes(t)));
40
+ const sharedTags = entityTags[cluster[0]].filter((t) => cluster.every((idx) => tagSets[idx].has(t)));
36
41
  const label = sharedTags
37
42
  .map((t) => t.split(':')[1])
38
43
  .join(': ') || members[0].content.slice(0, SUMMARY_FALLBACK_CHARS);
@@ -179,7 +184,8 @@ function summaryLabel(summary) {
179
184
  * A concurrent sleep's second writer gets changed=false (UPDATE requires summary_dirty=1) and is skipped silently; a tombstone hit counts as `refused`. */
180
185
  export async function rebuildDirtySummaries(hippoRoot, opts) {
181
186
  const cap = opts.cap ?? DEFAULT_REBUILD_CAP;
182
- const dirty = loadAllDirtySummaries(hippoRoot);
187
+ // One past the cap tells whether more were waiting without reading the rest.
188
+ const dirty = loadAllDirtySummaries(hippoRoot, cap + 1);
183
189
  const capped = dirty.length > cap;
184
190
  const queue = dirty.slice(0, cap);
185
191
  const result = {
@@ -1,5 +1,6 @@
1
1
  /** Store-level dedup: same text apart from spacing, keeping the stronger copy; split out so `api.sleep` can dedupe without cli -> api imports.
2
2
  * Survivor order is total and tenant-scoped: strength bucket, retrieval_count, then compareEntryIdentity; raw and superseded rows are not candidates. */
3
+ import { type MemoryEntry } from '../core/memory.js';
3
4
  export interface DedupPair {
4
5
  kept: string;
5
6
  keptContent: string;
@@ -19,11 +20,17 @@ export interface DedupResult {
19
20
  /** Quantize strength to an integer bucket so ties are a true equivalence relation (equal iff same multiple of `STRENGTH_TIE_EPSILON`).
20
21
  * Non-finite input maps to 0: a NaN bucket would make the comparator return NaN and break the total order. */
21
22
  export declare function strengthBucket(strength: number | null | undefined): number;
23
+ /** Every row of the store, read once by a caller that also needs them, with the ids of rows backing an object. */
24
+ export interface LoadedStore {
25
+ readonly entries: readonly MemoryEntry[];
26
+ readonly backing: ReadonlySet<string>;
27
+ }
22
28
  /** Remove the weaker copy of same-text (apart from spacing) memories within one tenant; cross-tenant pairs are never duplicates (isolation boundary).
23
29
  * Higher strength wins, then more retrievals; `threshold` is accepted for old callers and ignored. */
24
30
  export declare function deduplicateStore(hippoRoot: string, options?: {
25
31
  threshold?: number;
26
32
  dryRun?: boolean;
27
33
  actor?: string;
34
+ loaded?: LoadedStore;
28
35
  }): DedupResult;
29
36
  //# sourceMappingURL=dedupe.d.ts.map
@@ -5,7 +5,7 @@ import { loadCurrentDistilledEntries } from '../store/entry-reads.js';
5
5
  import { deleteEntriesOneByOne, memoriesBackingObjects } from '../store/delete-and-batch.js';
6
6
  import { compareEntryIdentity } from '../core/compare.js';
7
7
  import { canAutoDelete } from '../core/memory.js';
8
- import { derivationPartitionKey } from '../store/recall-scope.js';
8
+ import { derivationPartitionKey } from '../core/recall-scope.js';
9
9
  import { duplicateKey } from '../util/same-text.js';
10
10
  /** Quantization step for strength-tie comparisons: the 0.01 epsilon applied by rounding instead of an abs-diff threshold, so the tiebreak is transitive. */
11
11
  const STRENGTH_TIE_EPSILON = 0.01;
@@ -73,18 +73,20 @@ function partitionPairs(tenantEntries, removed, backing) {
73
73
  }
74
74
  return [...groups.values()].flatMap((group) => group.pairs);
75
75
  }
76
+ /** The rows loadCurrentDistilledEntries selects, picked from a whole-store read. */
77
+ const isCurrentDistilled = (e) => (e.kind ?? 'distilled') === 'distilled' && !e.superseded_by;
76
78
  /** Remove the weaker copy of same-text (apart from spacing) memories within one tenant; cross-tenant pairs are never duplicates (isolation boundary).
77
79
  * Higher strength wins, then more retrievals; `threshold` is accepted for old callers and ignored. */
78
80
  export function deduplicateStore(hippoRoot, options = {}) {
79
81
  const dryRun = options.dryRun ?? false;
80
82
  // Only current distilled rows compete: raw rows are append-only (the delete
81
83
  // trigger would abort sleep mid-loop) and superseded rows are history.
82
- const entries = loadCurrentDistilledEntries(hippoRoot);
84
+ const entries = options.loaded?.entries.filter(isCurrentDistilled) ?? loadCurrentDistilledEntries(hippoRoot);
83
85
  const entriesByTenant = entriesByPartition(entries);
84
86
  // Shared across tenant groups: ids are globally unique UUIDs, so `removed` cannot collide across tenants and deleteEntry deletes by primary key alone.
85
87
  const removed = new Set();
86
88
  const pairs = [];
87
- const backing = memoriesBackingObjects(hippoRoot);
89
+ const backing = options.loaded?.backing ?? memoriesBackingObjects(hippoRoot);
88
90
  for (const tenantEntries of entriesByTenant.values()) {
89
91
  // The survivor total order (see the file-level docstring), scoped per
90
92
  // tenant group: the total order holds within a tenant only, matching the partition above.
@@ -1,8 +1,11 @@
1
1
  import { envAnthropicApiKey, envDagRebuildCap } from '../util/env.js';
2
2
  import { Layer } from '../core/memory.js';
3
- import { log } from '../util/log.js';
3
+ import { errorFields, errorMessage, log } from '../util/log.js';
4
4
  import { keptAsWritten } from './run.js';
5
5
  import { isReusable } from '../core/memory-quality.js';
6
+ import { isPermanentFailure } from '../util/anthropic-messages.js';
7
+ // Bounds the LLM calls one sleep spends on extraction.
8
+ const EXTRACTION_BATCH_LIMIT = 20;
6
9
  /** The key, model options and once-per-line error reporter every LLM phase shares. */
7
10
  function sleepLlm(run, fetcher) {
8
11
  const { config, result } = run;
@@ -28,21 +31,10 @@ export async function llmPasses(run, fetcher) {
28
31
  run.result.extractionCandidates = extractionCandidates.length;
29
32
  const llm = sleepLlm(run, fetcher);
30
33
  if (llm.apiKey && extractionCandidates.length > 0 && !run.dryRun) {
31
- const { extractFacts, storeExtractedFacts } = await import('../learn/extract.js');
32
- const batchLimit = 20;
33
- let extractedCount = 0;
34
- for (const candidate of extractionCandidates.slice(0, batchLimit)) {
35
- try {
36
- const facts = await extractFacts(candidate.content, { ...llm.llmOpts, onError: llm.llmError('extraction') });
37
- if (facts.length > 0) {
38
- extractedCount += storeExtractedFacts(run.hippoRoot, candidate, facts).length;
39
- }
40
- }
41
- catch (err) {
42
- llm.llmError('extraction')(String(err));
43
- }
44
- }
45
- run.result.extracted = extractedCount;
34
+ const stopped = await extractionPass(run, llm, extractionCandidates.slice(0, EXTRACTION_BATCH_LIMIT));
35
+ // The DAG passes call the same API with the same key and model, so they would fail the same way.
36
+ if (stopped)
37
+ return;
46
38
  }
47
39
  await dagBuildPass(run, llm);
48
40
  if (llm.apiKey && !run.dryRun) {
@@ -50,6 +42,37 @@ export async function llmPasses(run, fetcher) {
50
42
  await entityProfilePass(run, llm);
51
43
  }
52
44
  }
45
+ /** Extracts facts from each candidate; true when it stopped on a failure every later call would repeat (bad key, no credit, unknown model). */
46
+ async function extractionPass(run, llm, batch) {
47
+ const { extractFacts, storeExtractedFacts } = await import('../learn/extract.js');
48
+ const report = llm.llmError('extraction');
49
+ let extractedCount = 0;
50
+ let stopped = false;
51
+ const onError = (msg, failure) => {
52
+ report(msg);
53
+ if (failure && isPermanentFailure(failure))
54
+ stopped = true;
55
+ };
56
+ for (const [index, candidate] of batch.entries()) {
57
+ try {
58
+ const facts = await extractFacts(candidate.content, { ...llm.llmOpts, onError });
59
+ if (facts.length > 0) {
60
+ extractedCount += storeExtractedFacts(run.hippoRoot, candidate, facts).length;
61
+ }
62
+ }
63
+ catch (err) {
64
+ report(errorMessage(err));
65
+ log.warn('consolidate extraction threw', errorFields(err));
66
+ }
67
+ if (stopped) {
68
+ const skipped = batch.length - index - 1;
69
+ report(`stopped after a failure that will not clear on retry; ${skipped} of ${batch.length} candidates skipped, and the DAG passes with them`);
70
+ break;
71
+ }
72
+ }
73
+ run.result.extracted = extractedCount;
74
+ return stopped;
75
+ }
53
76
  // 1.7. DAG summarization — cluster extracted facts and generate summaries
54
77
  async function dagBuildPass(run, { apiKey, llmError, llmOpts }) {
55
78
  const extractedFacts = run.survivors.filter((e) => e.tags.includes('extracted') && e.dag_level === 1 && !e.superseded_by && isReusable(e));
@@ -65,7 +88,8 @@ async function dagBuildPass(run, { apiKey, llmError, llmOpts }) {
65
88
  }
66
89
  }
67
90
  catch (err) {
68
- llmError('dag')(String(err));
91
+ llmError('dag')(errorMessage(err));
92
+ log.warn('consolidate dag threw', errorFields(err));
69
93
  }
70
94
  }
71
95
  // 1.8. DAG summary rebuild: drain the summary_dirty queue set by child-write hooks; same apiKey/dryRun gate as buildDag.
@@ -99,7 +123,8 @@ async function dagRebuildPass(run, { llmError, llmOpts }) {
99
123
  }
100
124
  }
101
125
  catch (err) {
102
- llmError('dag rebuild')(String(err));
126
+ llmError('dag rebuild')(errorMessage(err));
127
+ log.warn('consolidate dag rebuild threw', errorFields(err));
103
128
  }
104
129
  }
105
130
  // 1.9. DAG entity profiles: cluster L2 summaries into per-entity L3 profiles; runs even when 1.7 was skipped (re-clusters existing L2s every sleep).
@@ -118,7 +143,8 @@ async function entityProfilePass(run, { llmError, llmOpts }) {
118
143
  }
119
144
  }
120
145
  catch (err) {
121
- llmError('dag profiles')(String(err));
146
+ llmError('dag profiles')(errorMessage(err));
147
+ log.warn('consolidate dag profiles threw', errorFields(err));
122
148
  }
123
149
  }
124
150
  //# sourceMappingURL=llm-passes.js.map