hippo-memory 1.60.0 → 1.62.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 (593) hide show
  1. package/README.md +29 -54
  2. package/dist/ablation.js +9 -27
  3. package/dist/agent-memories/apply.d.ts +1 -1
  4. package/dist/agent-memories/apply.js +4 -1
  5. package/dist/agent-memories/claude-code.d.ts +3 -1
  6. package/dist/agent-memories/claude-code.js +54 -10
  7. package/dist/agent-memories/codex.js +1 -1
  8. package/dist/agent-memories/gemini.js +1 -1
  9. package/dist/agent-memories/legacy.js +1 -1
  10. package/dist/agent-memories/source.js +1 -1
  11. package/dist/agent-memories/sync.d.ts +3 -3
  12. package/dist/agent-memories/sync.js +22 -9
  13. package/dist/agent-memories/types.d.ts +0 -2
  14. package/dist/ambient-store.d.ts +14 -0
  15. package/dist/ambient-store.js +90 -0
  16. package/dist/ambient.d.ts +23 -0
  17. package/dist/ambient.js +72 -50
  18. package/dist/api/assemble.d.ts +93 -0
  19. package/dist/api/assemble.js +153 -0
  20. package/dist/api/audit.d.ts +17 -0
  21. package/dist/api/audit.js +23 -0
  22. package/dist/api/auth.d.ts +79 -0
  23. package/dist/api/auth.js +178 -0
  24. package/dist/api/context-select.d.ts +49 -0
  25. package/dist/api/context-select.js +344 -0
  26. package/dist/api/context-types.d.ts +105 -0
  27. package/dist/api/context-types.js +3 -0
  28. package/dist/api/context.d.ts +31 -0
  29. package/dist/api/context.js +378 -0
  30. package/dist/api/dormant.d.ts +30 -0
  31. package/dist/api/dormant.js +140 -0
  32. package/dist/api/drill-down.d.ts +84 -0
  33. package/dist/api/drill-down.js +126 -0
  34. package/dist/api/forget.d.ts +57 -0
  35. package/dist/api/forget.js +87 -0
  36. package/dist/api/goals.d.ts +18 -0
  37. package/dist/api/goals.js +33 -0
  38. package/dist/api/learn.d.ts +31 -0
  39. package/dist/api/learn.js +88 -0
  40. package/dist/api/outcome.d.ts +61 -0
  41. package/dist/api/outcome.js +66 -0
  42. package/dist/api/promote.d.ts +54 -0
  43. package/dist/api/promote.js +192 -0
  44. package/dist/api/quarantine.d.ts +24 -0
  45. package/dist/api/quarantine.js +121 -0
  46. package/dist/api/recall-types.d.ts +390 -0
  47. package/dist/api/recall-types.js +3 -0
  48. package/dist/api/recall.d.ts +36 -0
  49. package/dist/api/recall.js +499 -0
  50. package/dist/api/remember.d.ts +35 -0
  51. package/dist/api/remember.js +43 -0
  52. package/dist/api/sleep.d.ts +136 -0
  53. package/dist/api/sleep.js +256 -0
  54. package/dist/api/tokens.d.ts +26 -0
  55. package/dist/api/tokens.js +60 -0
  56. package/dist/api/types.d.ts +56 -0
  57. package/dist/api/types.js +38 -0
  58. package/dist/api.d.ts +20 -1262
  59. package/dist/api.js +25 -2727
  60. package/dist/audit.d.ts +3 -0
  61. package/dist/audit.js +6 -3
  62. package/dist/auth.d.ts +45 -4
  63. package/dist/auth.js +125 -48
  64. package/dist/autolearn.js +2 -2
  65. package/dist/capture/command.d.ts +33 -0
  66. package/dist/capture/command.js +264 -0
  67. package/dist/capture/compact.d.ts +44 -0
  68. package/dist/capture/compact.js +354 -0
  69. package/dist/capture/extract.d.ts +21 -0
  70. package/dist/capture/extract.js +464 -0
  71. package/dist/capture/transcript.d.ts +40 -0
  72. package/dist/capture/transcript.js +193 -0
  73. package/dist/capture-error.js +2 -1
  74. package/dist/churn-git.js +4 -2
  75. package/dist/cli/audit.d.ts +3 -0
  76. package/dist/cli/audit.js +159 -0
  77. package/dist/cli/auth.d.ts +2 -0
  78. package/dist/cli/auth.js +171 -0
  79. package/dist/cli/briefs.d.ts +4 -0
  80. package/dist/cli/briefs.js +453 -0
  81. package/dist/cli/card.d.ts +3 -0
  82. package/dist/cli/card.js +333 -0
  83. package/dist/cli/context.d.ts +15 -0
  84. package/dist/cli/context.js +376 -0
  85. package/dist/cli/continuity.d.ts +6 -0
  86. package/dist/cli/continuity.js +457 -0
  87. package/dist/cli/curate.d.ts +19 -0
  88. package/dist/cli/curate.js +556 -0
  89. package/dist/cli/dag.d.ts +5 -0
  90. package/dist/cli/dag.js +177 -0
  91. package/dist/cli/decisions.d.ts +4 -0
  92. package/dist/cli/decisions.js +528 -0
  93. package/dist/cli/eval.d.ts +5 -0
  94. package/dist/cli/eval.js +213 -0
  95. package/dist/cli/explain.d.ts +4 -0
  96. package/dist/cli/explain.js +156 -0
  97. package/dist/cli/goals.d.ts +2 -0
  98. package/dist/cli/goals.js +196 -0
  99. package/dist/cli/hook-blocks.d.ts +18 -0
  100. package/dist/cli/hook-blocks.js +233 -0
  101. package/dist/cli/init.d.ts +2 -0
  102. package/dist/cli/init.js +305 -0
  103. package/dist/cli/maintenance.d.ts +4 -0
  104. package/dist/cli/maintenance.js +190 -0
  105. package/dist/cli/playbooks.d.ts +4 -0
  106. package/dist/cli/playbooks.js +573 -0
  107. package/dist/cli/projects.js +9 -6
  108. package/dist/cli/recall.d.ts +7 -0
  109. package/dist/cli/recall.js +582 -0
  110. package/dist/cli/remember.d.ts +5 -0
  111. package/dist/cli/remember.js +485 -0
  112. package/dist/cli/serve.d.ts +5 -0
  113. package/dist/cli/serve.js +40 -0
  114. package/dist/cli/session-hooks.d.ts +29 -0
  115. package/dist/cli/session-hooks.js +647 -0
  116. package/dist/cli/setup.d.ts +4 -0
  117. package/dist/cli/setup.js +397 -0
  118. package/dist/cli/shared.d.ts +15 -19
  119. package/dist/cli/shared.js +50 -341
  120. package/dist/cli/slack.d.ts +2 -0
  121. package/dist/cli/slack.js +171 -0
  122. package/dist/cli/status.d.ts +18 -0
  123. package/dist/cli/status.js +409 -0
  124. package/dist/cli/transfer.d.ts +10 -0
  125. package/dist/cli/transfer.js +424 -0
  126. package/dist/cli/usage.d.ts +85 -0
  127. package/dist/cli/usage.js +741 -0
  128. package/dist/cli.d.ts +71 -120
  129. package/dist/cli.js +170 -8469
  130. package/dist/client.js +15 -8
  131. package/dist/compaction-record.d.ts +0 -2
  132. package/dist/compaction-record.js +7 -5
  133. package/dist/connectors/github/backfill.js +94 -87
  134. package/dist/connectors/github/cli-impl.js +4 -3
  135. package/dist/connectors/github/dlq.js +67 -54
  136. package/dist/connectors/github/ingest.js +34 -35
  137. package/dist/connectors/github/tenant-routing.js +3 -2
  138. package/dist/connectors/github/webhook.js +135 -216
  139. package/dist/connectors/slack/dlq.js +49 -61
  140. package/dist/connectors/slack/ingest.js +55 -48
  141. package/dist/connectors/slack/tenant-routing.js +4 -3
  142. package/dist/connectors/slack/webhook.js +72 -74
  143. package/dist/consolidate/conflicts.d.ts +10 -0
  144. package/dist/consolidate/conflicts.js +178 -0
  145. package/dist/consolidate/decay.d.ts +12 -0
  146. package/dist/consolidate/decay.js +145 -0
  147. package/dist/consolidate/llm-passes.d.ts +3 -0
  148. package/dist/consolidate/llm-passes.js +141 -0
  149. package/dist/consolidate/merge.d.ts +7 -0
  150. package/dist/consolidate/merge.js +251 -0
  151. package/dist/consolidate/physics-pass.d.ts +3 -0
  152. package/dist/consolidate/physics-pass.js +60 -0
  153. package/dist/consolidate/run.d.ts +69 -0
  154. package/dist/consolidate/run.js +76 -0
  155. package/dist/consolidate/sleep.d.ts +18 -0
  156. package/dist/consolidate/sleep.js +209 -0
  157. package/dist/consolidate/traces.d.ts +4 -0
  158. package/dist/consolidate/traces.js +178 -0
  159. package/dist/context-auto.js +7 -11
  160. package/dist/context-render.d.ts +1 -1
  161. package/dist/context-render.js +1 -1
  162. package/dist/customer-notes.d.ts +3 -0
  163. package/dist/customer-notes.js +83 -72
  164. package/dist/dag.js +225 -188
  165. package/dist/dashboard-actions.d.ts +20 -0
  166. package/dist/dashboard-actions.js +88 -0
  167. package/dist/dashboard-params.d.ts +45 -0
  168. package/dist/dashboard-params.js +127 -0
  169. package/dist/dashboard-queries.d.ts +15 -0
  170. package/dist/dashboard-queries.js +355 -0
  171. package/dist/dashboard-snapshot.d.ts +138 -0
  172. package/dist/dashboard-snapshot.js +308 -0
  173. package/dist/dashboard-types.d.ts +163 -0
  174. package/dist/dashboard-types.js +3 -0
  175. package/dist/dashboard.d.ts +6 -7
  176. package/dist/dashboard.js +228 -202
  177. package/dist/db/busy.d.ts +5 -0
  178. package/dist/db/busy.js +24 -0
  179. package/dist/db/continuity.d.ts +5 -0
  180. package/dist/db/continuity.js +145 -0
  181. package/dist/db/meta.d.ts +8 -0
  182. package/dist/db/meta.js +35 -0
  183. package/dist/db/migrate.d.ts +9 -0
  184. package/dist/db/migrate.js +138 -0
  185. package/dist/db/migrations/index.d.ts +5 -0
  186. package/dist/db/migrations/index.js +109 -0
  187. package/dist/db/migrations/types.d.ts +14 -0
  188. package/dist/db/migrations/types.js +2 -0
  189. package/dist/db/migrations/v01.d.ts +3 -0
  190. package/dist/db/migrations/v01.js +38 -0
  191. package/dist/db/migrations/v02.d.ts +3 -0
  192. package/dist/db/migrations/v02.js +21 -0
  193. package/dist/db/migrations/v03.d.ts +3 -0
  194. package/dist/db/migrations/v03.js +22 -0
  195. package/dist/db/migrations/v04.d.ts +3 -0
  196. package/dist/db/migrations/v04.js +28 -0
  197. package/dist/db/migrations/v05.d.ts +3 -0
  198. package/dist/db/migrations/v05.js +21 -0
  199. package/dist/db/migrations/v06.d.ts +3 -0
  200. package/dist/db/migrations/v06.js +25 -0
  201. package/dist/db/migrations/v07.d.ts +3 -0
  202. package/dist/db/migrations/v07.js +13 -0
  203. package/dist/db/migrations/v08.d.ts +3 -0
  204. package/dist/db/migrations/v08.js +8 -0
  205. package/dist/db/migrations/v09.d.ts +3 -0
  206. package/dist/db/migrations/v09.js +13 -0
  207. package/dist/db/migrations/v10.d.ts +3 -0
  208. package/dist/db/migrations/v10.js +17 -0
  209. package/dist/db/migrations/v11.d.ts +3 -0
  210. package/dist/db/migrations/v11.js +15 -0
  211. package/dist/db/migrations/v12.d.ts +3 -0
  212. package/dist/db/migrations/v12.js +11 -0
  213. package/dist/db/migrations/v13.d.ts +3 -0
  214. package/dist/db/migrations/v13.js +15 -0
  215. package/dist/db/migrations/v14.d.ts +3 -0
  216. package/dist/db/migrations/v14.js +66 -0
  217. package/dist/db/migrations/v15.d.ts +3 -0
  218. package/dist/db/migrations/v15.js +42 -0
  219. package/dist/db/migrations/v16.d.ts +3 -0
  220. package/dist/db/migrations/v16.js +61 -0
  221. package/dist/db/migrations/v17.d.ts +3 -0
  222. package/dist/db/migrations/v17.js +46 -0
  223. package/dist/db/migrations/v18.d.ts +3 -0
  224. package/dist/db/migrations/v18.js +60 -0
  225. package/dist/db/migrations/v19.d.ts +3 -0
  226. package/dist/db/migrations/v19.js +28 -0
  227. package/dist/db/migrations/v20.d.ts +3 -0
  228. package/dist/db/migrations/v20.js +42 -0
  229. package/dist/db/migrations/v21.d.ts +3 -0
  230. package/dist/db/migrations/v21.js +16 -0
  231. package/dist/db/migrations/v22.d.ts +3 -0
  232. package/dist/db/migrations/v22.js +82 -0
  233. package/dist/db/migrations/v23.d.ts +3 -0
  234. package/dist/db/migrations/v23.js +48 -0
  235. package/dist/db/migrations/v24.d.ts +3 -0
  236. package/dist/db/migrations/v24.js +72 -0
  237. package/dist/db/migrations/v25.d.ts +3 -0
  238. package/dist/db/migrations/v25.js +46 -0
  239. package/dist/db/migrations/v26.d.ts +3 -0
  240. package/dist/db/migrations/v26.js +23 -0
  241. package/dist/db/migrations/v27.d.ts +3 -0
  242. package/dist/db/migrations/v27.js +57 -0
  243. package/dist/db/migrations/v28.d.ts +3 -0
  244. package/dist/db/migrations/v28.js +38 -0
  245. package/dist/db/migrations/v29.d.ts +3 -0
  246. package/dist/db/migrations/v29.js +78 -0
  247. package/dist/db/migrations/v30.d.ts +3 -0
  248. package/dist/db/migrations/v30.js +92 -0
  249. package/dist/db/migrations/v31.d.ts +3 -0
  250. package/dist/db/migrations/v31.js +74 -0
  251. package/dist/db/migrations/v32.d.ts +3 -0
  252. package/dist/db/migrations/v32.js +102 -0
  253. package/dist/db/migrations/v33.d.ts +3 -0
  254. package/dist/db/migrations/v33.js +104 -0
  255. package/dist/db/migrations/v34.d.ts +3 -0
  256. package/dist/db/migrations/v34.js +93 -0
  257. package/dist/db/migrations/v35.d.ts +3 -0
  258. package/dist/db/migrations/v35.js +98 -0
  259. package/dist/db/migrations/v36.d.ts +3 -0
  260. package/dist/db/migrations/v36.js +98 -0
  261. package/dist/db/migrations/v37.d.ts +3 -0
  262. package/dist/db/migrations/v37.js +219 -0
  263. package/dist/db/migrations/v38.d.ts +3 -0
  264. package/dist/db/migrations/v38.js +277 -0
  265. package/dist/db/migrations/v39.d.ts +3 -0
  266. package/dist/db/migrations/v39.js +59 -0
  267. package/dist/db/migrations/v40.d.ts +3 -0
  268. package/dist/db/migrations/v40.js +74 -0
  269. package/dist/db/migrations/v41.d.ts +3 -0
  270. package/dist/db/migrations/v41.js +43 -0
  271. package/dist/db/migrations/v42.d.ts +3 -0
  272. package/dist/db/migrations/v42.js +41 -0
  273. package/dist/db/migrations/v43.d.ts +3 -0
  274. package/dist/db/migrations/v43.js +67 -0
  275. package/dist/db/migrations/v44.d.ts +3 -0
  276. package/dist/db/migrations/v44.js +28 -0
  277. package/dist/db/migrations/v45.d.ts +3 -0
  278. package/dist/db/migrations/v45.js +30 -0
  279. package/dist/db/migrations/v46.d.ts +3 -0
  280. package/dist/db/migrations/v46.js +25 -0
  281. package/dist/db/migrations/v47.d.ts +3 -0
  282. package/dist/db/migrations/v47.js +17 -0
  283. package/dist/db/migrations/v48.d.ts +3 -0
  284. package/dist/db/migrations/v48.js +10 -0
  285. package/dist/db/migrations/v49.d.ts +3 -0
  286. package/dist/db/migrations/v49.js +31 -0
  287. package/dist/db/migrations/v50.d.ts +3 -0
  288. package/dist/db/migrations/v50.js +67 -0
  289. package/dist/db/migrations/v51.d.ts +3 -0
  290. package/dist/db/migrations/v51.js +14 -0
  291. package/dist/db/migrations/v52.d.ts +3 -0
  292. package/dist/db/migrations/v52.js +7 -0
  293. package/dist/db/open.d.ts +23 -0
  294. package/dist/db/open.js +146 -0
  295. package/dist/db/sqlite.d.ts +21 -0
  296. package/dist/db/sqlite.js +8 -0
  297. package/dist/db/tables.d.ts +7 -0
  298. package/dist/db/tables.js +38 -0
  299. package/dist/db.d.ts +6 -46
  300. package/dist/db.js +5 -3049
  301. package/dist/decisions.d.ts +4 -1
  302. package/dist/decisions.js +102 -83
  303. package/dist/dedupe.js +3 -2
  304. package/dist/delivery-recorder.js +4 -1
  305. package/dist/doctor.js +14 -10
  306. package/dist/embedding-provider.d.ts +1 -1
  307. package/dist/embedding-provider.js +4 -3
  308. package/dist/embeddings.d.ts +9 -52
  309. package/dist/embeddings.js +43 -297
  310. package/dist/env.d.ts +75 -0
  311. package/dist/env.js +119 -0
  312. package/dist/eval-suite.js +1 -1
  313. package/dist/eval.js +2 -2
  314. package/dist/extract.js +1 -1
  315. package/dist/gated-write.js +3 -1
  316. package/dist/goals.d.ts +3 -1
  317. package/dist/goals.js +117 -86
  318. package/dist/graph/read.d.ts +73 -0
  319. package/dist/graph/read.js +325 -0
  320. package/dist/graph/rows.d.ts +45 -0
  321. package/dist/graph/rows.js +51 -0
  322. package/dist/graph/types.d.ts +83 -0
  323. package/dist/graph/types.js +11 -0
  324. package/dist/graph/write.d.ts +93 -0
  325. package/dist/{graph.js → graph/write.js} +6 -384
  326. package/dist/graph-extract.js +2 -1
  327. package/dist/graph-recall.d.ts +1 -1
  328. package/dist/graph-recall.js +2 -2
  329. package/dist/graph-stream.js +1 -1
  330. package/dist/graph-view.d.ts +1 -1
  331. package/dist/graph-view.js +1 -1
  332. package/dist/half-life-migration.d.ts +1 -1
  333. package/dist/half-life-migration.js +2 -1
  334. package/dist/hooks/codex-session.d.ts +8 -0
  335. package/dist/hooks/codex-session.js +76 -0
  336. package/dist/hooks/codex-wrapper.d.ts +55 -0
  337. package/dist/hooks/codex-wrapper.js +288 -0
  338. package/dist/hooks/json-hooks.d.ts +63 -0
  339. package/dist/hooks/json-hooks.js +356 -0
  340. package/dist/hooks/opencode.d.ts +50 -0
  341. package/dist/hooks/opencode.js +202 -0
  342. package/dist/hooks/shared.d.ts +54 -0
  343. package/dist/hooks/shared.js +77 -0
  344. package/dist/http-retry.d.ts +2 -0
  345. package/dist/http-retry.js +4 -3
  346. package/dist/http-util.d.ts +3 -0
  347. package/dist/http-util.js +10 -0
  348. package/dist/{importers.d.ts → importers/core.d.ts} +13 -17
  349. package/dist/importers/core.js +141 -0
  350. package/dist/importers/markdown-parse.d.ts +41 -0
  351. package/dist/importers/markdown-parse.js +132 -0
  352. package/dist/importers/markdown.d.ts +3 -0
  353. package/dist/importers/markdown.js +92 -0
  354. package/dist/importers/sources.d.ts +7 -0
  355. package/dist/importers/sources.js +229 -0
  356. package/dist/importers/vault.d.ts +11 -0
  357. package/dist/importers/vault.js +352 -0
  358. package/dist/incidents.d.ts +3 -0
  359. package/dist/incidents.js +52 -43
  360. package/dist/index.d.ts +25 -6
  361. package/dist/index.js +23 -6
  362. package/dist/invalidation.js +2 -1
  363. package/dist/judgment.js +2 -1
  364. package/dist/keyset.d.ts +13 -0
  365. package/dist/keyset.js +8 -0
  366. package/dist/local-embedding.d.ts +13 -0
  367. package/dist/local-embedding.js +165 -0
  368. package/dist/log.d.ts +7 -0
  369. package/dist/log.js +19 -1
  370. package/dist/mcp/admin-tools.d.ts +8 -0
  371. package/dist/mcp/admin-tools.js +116 -0
  372. package/dist/mcp/format.d.ts +27 -0
  373. package/dist/mcp/format.js +135 -0
  374. package/dist/mcp/memory-tools.d.ts +5 -0
  375. package/dist/mcp/memory-tools.js +83 -0
  376. package/dist/mcp/protocol.d.ts +83 -0
  377. package/dist/mcp/protocol.js +55 -0
  378. package/dist/mcp/recall-tools.d.ts +6 -0
  379. package/dist/mcp/recall-tools.js +320 -0
  380. package/dist/mcp/request.d.ts +10 -0
  381. package/dist/mcp/request.js +163 -0
  382. package/dist/mcp/server.d.ts +4 -75
  383. package/dist/mcp/server.js +7 -1190
  384. package/dist/mcp/session-state.d.ts +11 -0
  385. package/dist/mcp/session-state.js +28 -0
  386. package/dist/mcp/stdio.d.ts +8 -0
  387. package/dist/mcp/stdio.js +79 -0
  388. package/dist/mcp/tools.d.ts +11 -0
  389. package/dist/mcp/tools.js +247 -0
  390. package/dist/memory.d.ts +3 -0
  391. package/dist/memory.js +27 -1
  392. package/dist/multihop.d.ts +1 -1
  393. package/dist/multihop.js +2 -1
  394. package/dist/owner-validation.js +2 -1
  395. package/dist/physics-state.js +10 -7
  396. package/dist/policies.d.ts +3 -0
  397. package/dist/policies.js +93 -74
  398. package/dist/postinstall.js +3 -2
  399. package/dist/predictions/planning-fallacy.d.ts +100 -0
  400. package/dist/predictions/planning-fallacy.js +190 -0
  401. package/dist/{predictions.d.ts → predictions/store.d.ts} +7 -102
  402. package/dist/predictions/store.js +434 -0
  403. package/dist/processes.d.ts +3 -0
  404. package/dist/processes.js +94 -76
  405. package/dist/project-briefs.d.ts +3 -0
  406. package/dist/project-briefs.js +142 -113
  407. package/dist/project-identity.js +3 -2
  408. package/dist/project-merge.d.ts +12 -4
  409. package/dist/project-merge.js +132 -45
  410. package/dist/quarantine.d.ts +2 -1
  411. package/dist/quarantine.js +8 -5
  412. package/dist/raw-archive.js +1 -1
  413. package/dist/recall-history.js +3 -2
  414. package/dist/recall-pipeline.d.ts +2 -2
  415. package/dist/recall-pipeline.js +16 -8
  416. package/dist/recall-scope.js +1 -1
  417. package/dist/recall-trace.d.ts +1 -1
  418. package/dist/refine-llm.js +2 -1
  419. package/dist/reject-flow.js +6 -1
  420. package/dist/rerankers/clef.js +6 -5
  421. package/dist/rerankers/jev.d.ts +1 -1
  422. package/dist/rerankers/jev.js +4 -4
  423. package/dist/rerankers/llm.d.ts +4 -2
  424. package/dist/rerankers/llm.js +58 -38
  425. package/dist/rerankers/types.d.ts +1 -1
  426. package/dist/salience.js +1 -1
  427. package/dist/scheduler.d.ts +1 -0
  428. package/dist/scheduler.js +26 -4
  429. package/dist/scope.js +4 -3
  430. package/dist/search/as-of.d.ts +10 -0
  431. package/dist/search/as-of.js +22 -0
  432. package/dist/search/bm25-search.d.ts +14 -0
  433. package/dist/search/bm25-search.js +43 -0
  434. package/dist/search/bm25.d.ts +15 -0
  435. package/dist/search/bm25.js +54 -0
  436. package/dist/search/boosts.d.ts +54 -0
  437. package/dist/search/boosts.js +94 -0
  438. package/dist/search/breakdown.d.ts +7 -0
  439. package/dist/search/breakdown.js +20 -0
  440. package/dist/search/explain.d.ts +25 -0
  441. package/dist/search/explain.js +31 -0
  442. package/dist/search/finalize.d.ts +8 -0
  443. package/dist/search/finalize.js +52 -0
  444. package/dist/search/fusion.d.ts +27 -0
  445. package/dist/search/fusion.js +42 -0
  446. package/dist/search/hybrid-score.d.ts +20 -0
  447. package/dist/search/hybrid-score.js +73 -0
  448. package/dist/search/hybrid.d.ts +46 -0
  449. package/dist/search/hybrid.js +64 -0
  450. package/dist/search/physics-search.d.ts +29 -0
  451. package/dist/search/physics-search.js +162 -0
  452. package/dist/search/rerank.d.ts +10 -0
  453. package/dist/search/rerank.js +72 -0
  454. package/dist/search/temporal.d.ts +15 -0
  455. package/dist/search/temporal.js +45 -0
  456. package/dist/search/types.d.ts +91 -0
  457. package/dist/search/types.js +2 -0
  458. package/dist/search/vector.d.ts +30 -0
  459. package/dist/search/vector.js +71 -0
  460. package/dist/secret-detect.d.ts +2 -0
  461. package/dist/secret-detect.js +2 -1
  462. package/dist/server/auth.d.ts +52 -0
  463. package/dist/server/auth.js +221 -0
  464. package/dist/server/client-ip.d.ts +25 -0
  465. package/dist/server/client-ip.js +92 -0
  466. package/dist/server/cursor.d.ts +23 -0
  467. package/dist/server/cursor.js +58 -0
  468. package/dist/server/lifecycle.d.ts +7 -0
  469. package/dist/server/lifecycle.js +29 -0
  470. package/dist/server/mcp-http.d.ts +5 -0
  471. package/dist/server/mcp-http.js +199 -0
  472. package/dist/server/request.d.ts +33 -0
  473. package/dist/server/request.js +103 -0
  474. package/dist/server/routes/admin.d.ts +9 -0
  475. package/dist/server/routes/admin.js +158 -0
  476. package/dist/server/routes/customer-notes.d.ts +7 -0
  477. package/dist/server/routes/customer-notes.js +112 -0
  478. package/dist/server/routes/decisions.d.ts +7 -0
  479. package/dist/server/routes/decisions.js +133 -0
  480. package/dist/server/routes/incidents.d.ts +7 -0
  481. package/dist/server/routes/incidents.js +126 -0
  482. package/dist/server/routes/memories.d.ts +10 -0
  483. package/dist/server/routes/memories.js +179 -0
  484. package/dist/server/routes/policies.d.ts +8 -0
  485. package/dist/server/routes/policies.js +151 -0
  486. package/dist/server/routes/predictions.d.ts +7 -0
  487. package/dist/server/routes/predictions.js +164 -0
  488. package/dist/server/routes/processes.d.ts +7 -0
  489. package/dist/server/routes/processes.js +161 -0
  490. package/dist/server/routes/project-briefs.d.ts +8 -0
  491. package/dist/server/routes/project-briefs.js +136 -0
  492. package/dist/server/routes/recall.d.ts +8 -0
  493. package/dist/server/routes/recall.js +340 -0
  494. package/dist/server/routes/skills.d.ts +8 -0
  495. package/dist/server/routes/skills.js +150 -0
  496. package/dist/server/types.d.ts +56 -0
  497. package/dist/server/types.js +2 -0
  498. package/dist/server/validation.d.ts +14 -0
  499. package/dist/server/validation.js +91 -0
  500. package/dist/server.d.ts +7 -61
  501. package/dist/server.js +69 -2362
  502. package/dist/session-digest.d.ts +1 -1
  503. package/dist/session-digest.js +9 -2
  504. package/dist/shared.d.ts +12 -1
  505. package/dist/shared.js +33 -27
  506. package/dist/skills.d.ts +3 -0
  507. package/dist/skills.js +88 -70
  508. package/dist/stdin.js +2 -2
  509. package/dist/store/audit-event.d.ts +19 -0
  510. package/dist/store/audit-event.js +33 -0
  511. package/dist/store/candidates.d.ts +42 -0
  512. package/dist/store/candidates.js +152 -0
  513. package/dist/store/conflicts.d.ts +44 -0
  514. package/dist/store/conflicts.js +444 -0
  515. package/dist/store/delete-and-batch.d.ts +63 -0
  516. package/dist/store/delete-and-batch.js +310 -0
  517. package/dist/store/entry-reads.d.ts +92 -0
  518. package/dist/store/entry-reads.js +255 -0
  519. package/dist/store/entry-row.d.ts +67 -0
  520. package/dist/store/entry-row.js +208 -0
  521. package/dist/store/entry-writes.d.ts +46 -0
  522. package/dist/store/entry-writes.js +148 -0
  523. package/dist/store/handoffs.d.ts +26 -0
  524. package/dist/store/handoffs.js +184 -0
  525. package/dist/store/index-and-stats.d.ts +52 -0
  526. package/dist/store/index-and-stats.js +214 -0
  527. package/dist/store/markdown.d.ts +10 -0
  528. package/dist/store/markdown.js +108 -0
  529. package/dist/store/mirrors.d.ts +49 -0
  530. package/dist/store/mirrors.js +312 -0
  531. package/dist/store/open.d.ts +15 -0
  532. package/dist/store/open.js +223 -0
  533. package/dist/store/rows.d.ts +178 -0
  534. package/dist/store/rows.js +158 -0
  535. package/dist/store/search-rows.d.ts +86 -0
  536. package/dist/store/search-rows.js +260 -0
  537. package/dist/store/sessions.d.ts +83 -0
  538. package/dist/store/sessions.js +272 -0
  539. package/dist/store/summaries.d.ts +94 -0
  540. package/dist/store/summaries.js +377 -0
  541. package/dist/store/tenant-lookup.d.ts +12 -0
  542. package/dist/store/tenant-lookup.js +16 -0
  543. package/dist/store-cards.js +2 -1
  544. package/dist/summary-dirty.d.ts +4 -0
  545. package/dist/summary-dirty.js +32 -0
  546. package/dist/support-bundle.js +4 -3
  547. package/dist/tenant.js +2 -2
  548. package/dist/tokenize.d.ts +2 -0
  549. package/dist/tokenize.js +16 -0
  550. package/dist/transcript-tail.d.ts +7 -0
  551. package/dist/transcript-tail.js +48 -0
  552. package/dist/vector-store.d.ts +27 -0
  553. package/dist/vector-store.js +210 -0
  554. package/dist/version.d.ts +2 -2
  555. package/dist/version.js +2 -2
  556. package/dist/working-memory.js +1 -1
  557. package/dist/yaml.js +36 -11
  558. package/dist-ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  559. package/dist-ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  560. package/dist-ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  561. package/dist-ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  562. package/dist-ui/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
  563. package/dist-ui/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
  564. package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  565. package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  566. package/dist-ui/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  567. package/dist-ui/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  568. package/dist-ui/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  569. package/dist-ui/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  570. package/dist-ui/assets/index-DPN7cP19.js +33 -0
  571. package/dist-ui/assets/index-dFloKRVr.css +1 -0
  572. package/dist-ui/index.html +3 -25
  573. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  574. package/extensions/openclaw-plugin/package.json +1 -1
  575. package/openclaw.plugin.json +1 -1
  576. package/package.json +1 -1
  577. package/dist/capture.d.ts +0 -155
  578. package/dist/capture.js +0 -1295
  579. package/dist/consolidate.d.ts +0 -58
  580. package/dist/consolidate.js +0 -1124
  581. package/dist/graph.d.ts +0 -245
  582. package/dist/hooks.d.ts +0 -208
  583. package/dist/hooks.js +0 -1076
  584. package/dist/importers.js +0 -900
  585. package/dist/predictions.js +0 -620
  586. package/dist/search.d.ts +0 -320
  587. package/dist/search.js +0 -970
  588. package/dist/store.d.ts +0 -776
  589. package/dist/store.js +0 -3473
  590. package/dist-ui/assets/d3-BiWEKnn4.js +0 -1
  591. package/dist-ui/assets/index-BhT8RvO6.js +0 -61
  592. package/dist-ui/assets/index-RoXXJ5dq.css +0 -1
  593. package/dist-ui/assets/three-BDgTxR1l.js +0 -4112
package/dist/audit.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type MemoryEntry } from './memory.js';
2
2
  import type { DatabaseSyncLike } from './db.js';
3
3
  import type { JsonObject } from './working-memory.js';
4
+ import { type KeysetPosition } from './keyset.js';
4
5
  export type AuditSeverity = 'warning' | 'error';
5
6
  export interface AuditIssue {
6
7
  memoryId: string;
@@ -42,6 +43,8 @@ export interface QueryAuditOpts {
42
43
  op?: AuditOp;
43
44
  since?: string;
44
45
  limit?: number;
46
+ /** Resume after this row: the (ts, id) position the previous page ended on. */
47
+ after?: KeysetPosition;
45
48
  }
46
49
  export interface AuditEvent {
47
50
  id: number;
package/dist/audit.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { canAutoDelete } from './memory.js';
3
3
  import { log } from './log.js';
4
+ import { keysetAfter } from './keyset.js';
4
5
  export const STOP_WORDS = new Set([
5
6
  'the', 'a', 'an', 'is', 'was', 'are', 'were', 'be', 'been', 'being',
6
7
  'to', 'of', 'in', 'for', 'on', 'with', 'at', 'by', 'from', 'it',
@@ -274,11 +275,13 @@ export function queryAuditEvents(db, opts) {
274
275
  where.push('ts >= ?');
275
276
  params.push(opts.since);
276
277
  }
277
- const limit = Math.max(1, Math.min(opts.limit ?? 100, 10000));
278
+ const after = keysetAfter('ts', 'id', opts.after);
279
+ // One past the route's 10000 cap: GET /v1/audit reads a row ahead to tell whether another page exists.
280
+ const limit = Math.max(1, Math.min(opts.limit ?? 100, 10001));
278
281
  // SAFETY: AUDIT_COLUMNS names exactly the AuditRow columns, in this order.
279
282
  const rows = db
280
- .prepare(`SELECT ${AUDIT_COLUMNS} FROM audit_log WHERE ${where.join(' AND ')} ORDER BY ts DESC, id DESC LIMIT ?`)
281
- .all(...params, limit);
283
+ .prepare(`SELECT ${AUDIT_COLUMNS} FROM audit_log WHERE ${where.join(' AND ')}${after.sql} ORDER BY ts DESC, id DESC LIMIT ?`)
284
+ .all(...params, ...after.params, limit);
282
285
  return rows.map(rowToAuditEvent);
283
286
  }
284
287
  /**
package/dist/auth.d.ts CHANGED
@@ -1,7 +1,13 @@
1
- import type { DatabaseSyncLike } from './db.js';
1
+ import { type DatabaseSyncLike } from './db.js';
2
+ import { type KeysetPosition } from './keyset.js';
2
3
  /** Every minted API key starts with this, so the server can route a bearer token by shape. */
3
4
  export declare const API_KEY_PREFIX = "hk_";
4
- export declare function _dummyHashForTests(): string;
5
+ /** Counts since process start, so a flood of bad tokens shows as scrypt work, not just as 401s. */
6
+ export interface ApiKeyVerifyStats {
7
+ readonly storeLookups: number;
8
+ readonly scryptRuns: number;
9
+ }
10
+ export declare function apiKeyVerifyStats(): ApiKeyVerifyStats;
5
11
  export interface CreateApiKeyOpts {
6
12
  tenantId: string;
7
13
  label?: string;
@@ -22,7 +28,29 @@ export interface ValidateResult {
22
28
  /** EI2: scope grants for this key. Present only when valid=true. */
23
29
  scopes?: string[];
24
30
  }
31
+ /** The identity a verified API key carries. */
32
+ export interface VerifiedApiKey {
33
+ tenantId: string;
34
+ keyId: string;
35
+ role: 'admin' | 'member';
36
+ scopes: string[];
37
+ }
25
38
  export declare function validateApiKey(db: DatabaseSyncLike, plaintext: string): ValidateResult;
39
+ /** How long a verified key is trusted without a store read; also the ceiling on a revoke made by another process. */
40
+ export declare const VERIFIED_KEY_TTL_MS = 60000;
41
+ /** LRU of verified keys with an absolute TTL. Holds a SHA-256 of the token, never the token. */
42
+ export declare class VerifiedKeyCache {
43
+ private readonly capacity;
44
+ private readonly ttlMs;
45
+ private readonly entries;
46
+ constructor(capacity: number, ttlMs: number);
47
+ get size(): number;
48
+ get(hippoRoot: string, keyId: string, plaintext: string, now: number): VerifiedApiKey | undefined;
49
+ set(hippoRoot: string, keyId: string, plaintext: string, key: VerifiedApiKey, now: number): void;
50
+ delete(keyId: string): void;
51
+ }
52
+ /** Verify a bearer API key for `hippoRoot`; a cache hit skips both scrypt and the DB open. Null when invalid. */
53
+ export declare function verifyApiKeyCached(hippoRoot: string, plaintext: string): VerifiedApiKey | null;
26
54
  export declare function revokeApiKey(db: DatabaseSyncLike, keyId: string): void;
27
55
  /** EI2: grant `keyId` read access to one restricted `scope`. Idempotent. */
28
56
  export declare function grantScope(db: DatabaseSyncLike, keyId: string, scope: string): void;
@@ -45,7 +73,20 @@ export interface ApiKeyListItem {
45
73
  /** EI2: restricted scopes this key may read. */
46
74
  scopes: string[];
47
75
  }
48
- export declare function listApiKeys(db: DatabaseSyncLike, opts: {
76
+ export interface ListApiKeysOpts {
49
77
  active: boolean;
50
- }): ApiKeyListItem[];
78
+ /** Only this tenant's keys; omit for every tenant (the CLI's single-tenant view). */
79
+ tenantId?: string;
80
+ /** Resume after this row: the position the previous page ended on (key and id are both the row id). */
81
+ after?: KeysetPosition;
82
+ limit?: number;
83
+ }
84
+ /** A listed key plus its row id, the paging key the list item itself does not expose. */
85
+ export interface ApiKeyListRow {
86
+ rowId: number;
87
+ key: ApiKeyListItem;
88
+ }
89
+ /** Keys newest first, with their row ids, filtered and limited in SQL. */
90
+ export declare function listApiKeyRows(db: DatabaseSyncLike, opts: ListApiKeysOpts): ApiKeyListRow[];
91
+ export declare function listApiKeys(db: DatabaseSyncLike, opts: ListApiKeysOpts): ApiKeyListItem[];
51
92
  //# sourceMappingURL=auth.d.ts.map
package/dist/auth.js CHANGED
@@ -1,10 +1,13 @@
1
- import { randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
1
+ import { createHash, randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
2
+ import { openHippoDb, closeHippoDb } from './db.js';
3
+ import { keysetAfter } from './keyset.js';
2
4
  /** Every minted API key starts with this, so the server can route a bearer token by shape. */
3
5
  export const API_KEY_PREFIX = 'hk_';
4
6
  const ID_LEN = 24; // base32 chars after prefix
5
7
  const SECRET_LEN = 32; // base32 chars after dot
6
8
  const SCRYPT_KEYLEN = 32;
7
9
  const BASE32 = 'abcdefghijklmnopqrstuvwxyz234567';
10
+ const MINTED_KEY_PATTERN = new RegExp(`^${API_KEY_PREFIX}[a-z2-7]{${ID_LEN}}\\.[a-z2-7]{${SECRET_LEN}}$`);
8
11
  function randBase32(n) {
9
12
  const bytes = randomBytes(n);
10
13
  let out = '';
@@ -18,18 +21,9 @@ function hashKey(plaintext) {
18
21
  const hash = scryptSync(plaintext, salt, SCRYPT_KEYLEN);
19
22
  return `scrypt$${salt.toString('hex')}$${hash.toString('hex')}`;
20
23
  }
21
- // Constant dummy hash precomputed once at module load. Used by validateApiKey
22
- // to pay the scrypt cost on the miss path (unknown / revoked / malformed key)
23
- // so the timing signal between hit and miss is reduced. The DB lookup branch
24
- // itself can still leak via cache effects — v0.40 follow-up: request-level
25
- // rate limit on /v1/* to bound key-id enumeration. Stored format identical to
26
- // real hashes: scrypt$saltHex$hashHex.
27
- const DUMMY_PLAINTEXT = 'hk_dummy_constant_padding_for_timing.dummy_secret_padding_for_timing_x';
28
- // Precomputed `hashKey(DUMMY_PLAINTEXT)`: computing it at module load put one scrypt on every CLI start.
29
- const DUMMY_HASH = 'scrypt$5b2117156f1ad78738fd8b6a5bace454$92e098fa28003b7c38ba8f1451bd8618260a4a3c5a64b87cc2b471567c0960e7';
30
- // Not a secret (it's padding, not a real key hash): exposed only so a test can pin its shape.
31
- export function _dummyHashForTests() {
32
- return DUMMY_HASH;
24
+ const verifyStats = { storeLookups: 0, scryptRuns: 0 };
25
+ export function apiKeyVerifyStats() {
26
+ return { ...verifyStats };
33
27
  }
34
28
  function verifyKey(plaintext, stored) {
35
29
  const parts = stored.split('$');
@@ -37,6 +31,7 @@ function verifyKey(plaintext, stored) {
37
31
  return false;
38
32
  const salt = Buffer.from(parts[1], 'hex');
39
33
  const expected = Buffer.from(parts[2], 'hex');
34
+ verifyStats.scryptRuns++;
40
35
  const actual = scryptSync(plaintext, salt, expected.length);
41
36
  return expected.length === actual.length && timingSafeEqual(expected, actual);
42
37
  }
@@ -52,49 +47,115 @@ export function createApiKey(db, opts) {
52
47
  db.prepare(`INSERT INTO api_keys (key_id, key_hash, tenant_id, label, created_at, role) VALUES (?, ?, ?, ?, ?, ?)`).run(keyId, hash, opts.tenantId, opts.label ?? null, new Date().toISOString(), opts.role ?? 'admin');
53
48
  return { keyId, plaintext };
54
49
  }
55
- export function validateApiKey(db, plaintext) {
56
- const dot = plaintext.indexOf('.');
57
- if (dot < 0) {
58
- // Malformed input still pays the scrypt cost so caller cannot distinguish
59
- // "no dot" from "unknown key_id" by timing.
60
- verifyKey(DUMMY_PLAINTEXT, DUMMY_HASH);
61
- return { valid: false };
62
- }
63
- const keyId = plaintext.slice(0, dot);
50
+ /** One full check against the store: shape, row, revocation, then scrypt. Null for any failure. */
51
+ function lookupApiKey(db, plaintext) {
52
+ if (!MINTED_KEY_PATTERN.test(plaintext))
53
+ return null;
54
+ const keyId = plaintext.slice(0, API_KEY_PREFIX.length + ID_LEN);
64
55
  // SAFETY: row comes from the SELECT above, which projects exactly
65
56
  // key_hash, tenant_id, revoked_at, role; `.get` returns undefined when no
66
57
  // row matches key_id.
67
58
  const row = db
68
59
  .prepare(`SELECT key_hash, tenant_id, revoked_at, role FROM api_keys WHERE key_id = ?`)
69
60
  .get(keyId);
70
- // Always run verifyKey — on miss/revoked, against DUMMY_HASH so scrypt cost
71
- // is paid. This reduces timing signal between hit and miss, but the DB
72
- // lookup itself can still leak via cache effects. v0.40 follow-up: add
73
- // request-level rate limit on /v1/* to bound enumeration.
74
- const target = (row && !row.revoked_at) ? row.key_hash : DUMMY_HASH;
75
- const matches = verifyKey(plaintext, target);
76
- if (!row || row.revoked_at || !matches)
77
- return { valid: false };
78
- // v1.12.0: fail-safe to least privilege — only 'admin' is admitted as admin,
79
- // anything else (including future schema drift, manual DB tampering inserting
80
- // 'superuser', or a NULL slipped past the NOT NULL constraint) downgrades to
81
- // 'member'. The migration constrains to 'admin' DEFAULT, but defense-in-depth.
61
+ // Ids are 120 random bits and not secret, so padding the miss path with scrypt hid nothing and let junk tokens burn CPU.
62
+ if (!row || row.revoked_at || !verifyKey(plaintext, row.key_hash))
63
+ return null;
64
+ // Fail-safe to least privilege: any role value but 'admin' reads as 'member'.
82
65
  const role = row.role === 'admin' ? 'admin' : 'member';
83
- const scopes = listScopeGrants(db, keyId);
84
- return { valid: true, tenantId: row.tenant_id, keyId, role, scopes };
66
+ return { tenantId: row.tenant_id, keyId, role, scopes: listScopeGrants(db, keyId) };
67
+ }
68
+ export function validateApiKey(db, plaintext) {
69
+ const key = lookupApiKey(db, plaintext);
70
+ return key ? { valid: true, ...key } : { valid: false };
71
+ }
72
+ /** How long a verified key is trusted without a store read; also the ceiling on a revoke made by another process. */
73
+ export const VERIFIED_KEY_TTL_MS = 60_000;
74
+ const VERIFIED_KEY_CACHE_CAP = 1_000;
75
+ function secretDigest(plaintext) {
76
+ return createHash('sha256').update(plaintext).digest();
77
+ }
78
+ /** LRU of verified keys with an absolute TTL. Holds a SHA-256 of the token, never the token. */
79
+ export class VerifiedKeyCache {
80
+ capacity;
81
+ ttlMs;
82
+ // Map iterates in insertion order, so re-inserting on a hit makes the first key the least recent.
83
+ entries = new Map();
84
+ constructor(capacity, ttlMs) {
85
+ this.capacity = capacity;
86
+ this.ttlMs = ttlMs;
87
+ }
88
+ get size() {
89
+ return this.entries.size;
90
+ }
91
+ get(hippoRoot, keyId, plaintext, now) {
92
+ const entry = this.entries.get(keyId);
93
+ if (!entry || entry.hippoRoot !== hippoRoot)
94
+ return undefined;
95
+ if (now >= entry.expiresAt) {
96
+ this.entries.delete(keyId);
97
+ return undefined;
98
+ }
99
+ // A wrong secret misses but leaves the entry, so a flood of bad guesses cannot evict a good key.
100
+ if (!timingSafeEqual(entry.digest, secretDigest(plaintext)))
101
+ return undefined;
102
+ this.entries.delete(keyId);
103
+ this.entries.set(keyId, entry);
104
+ return { ...entry.key, scopes: [...entry.key.scopes] };
105
+ }
106
+ set(hippoRoot, keyId, plaintext, key, now) {
107
+ this.entries.delete(keyId);
108
+ if (this.entries.size >= this.capacity) {
109
+ const oldest = this.entries.keys().next();
110
+ if (!oldest.done)
111
+ this.entries.delete(oldest.value);
112
+ }
113
+ const frozen = Object.freeze({ ...key, scopes: [...key.scopes] });
114
+ this.entries.set(keyId, { hippoRoot, digest: secretDigest(plaintext), key: frozen, expiresAt: now + this.ttlMs });
115
+ }
116
+ delete(keyId) {
117
+ this.entries.delete(keyId);
118
+ }
119
+ }
120
+ // SHORTCUT: per-process cache, so a revoke or scope change made by another process (the CLI) lands within VERIFIED_KEY_TTL_MS; a shared revocation epoch in the store if that is too slow.
121
+ const verifiedKeys = new VerifiedKeyCache(VERIFIED_KEY_CACHE_CAP, VERIFIED_KEY_TTL_MS);
122
+ /** Verify a bearer API key for `hippoRoot`; a cache hit skips both scrypt and the DB open. Null when invalid. */
123
+ export function verifyApiKeyCached(hippoRoot, plaintext) {
124
+ if (!MINTED_KEY_PATTERN.test(plaintext))
125
+ return null;
126
+ const keyId = plaintext.slice(0, API_KEY_PREFIX.length + ID_LEN);
127
+ const hit = verifiedKeys.get(hippoRoot, keyId, plaintext, Date.now());
128
+ if (hit)
129
+ return hit;
130
+ verifyStats.storeLookups++;
131
+ const db = openHippoDb(hippoRoot);
132
+ let key;
133
+ try {
134
+ key = lookupApiKey(db, plaintext);
135
+ }
136
+ finally {
137
+ closeHippoDb(db);
138
+ }
139
+ // Only successes are cached: caching misses would let junk tokens fill the cache and evict real keys.
140
+ if (key)
141
+ verifiedKeys.set(hippoRoot, keyId, plaintext, key, Date.now());
142
+ return key;
85
143
  }
86
144
  export function revokeApiKey(db, keyId) {
87
145
  db.prepare(`UPDATE api_keys SET revoked_at = ? WHERE key_id = ? AND revoked_at IS NULL`)
88
146
  .run(new Date().toISOString(), keyId);
147
+ verifiedKeys.delete(keyId);
89
148
  }
90
149
  /** EI2: grant `keyId` read access to one restricted `scope`. Idempotent. */
91
150
  export function grantScope(db, keyId, scope) {
92
151
  db.prepare(`INSERT INTO api_key_scope_grants (key_id, scope, granted_at) VALUES (?, ?, ?)
93
152
  ON CONFLICT(key_id, scope) DO NOTHING`).run(keyId, scope, new Date().toISOString());
153
+ verifiedKeys.delete(keyId);
94
154
  }
95
155
  /** EI2: revoke `keyId`'s grant on `scope`. Not an error when no such grant exists. */
96
156
  export function ungrantScope(db, keyId, scope) {
97
157
  db.prepare(`DELETE FROM api_key_scope_grants WHERE key_id = ? AND scope = ?`).run(keyId, scope);
158
+ verifiedKeys.delete(keyId);
98
159
  }
99
160
  /** EI2: every restricted scope `keyId` may read. */
100
161
  export function listScopeGrants(db, keyId) {
@@ -104,19 +165,35 @@ export function listScopeGrants(db, keyId) {
104
165
  .all(keyId);
105
166
  return rows.map((r) => r.scope);
106
167
  }
107
- export function listApiKeys(db, opts) {
108
- const sql = opts.active
109
- ? `SELECT key_id, tenant_id, label, created_at, revoked_at, role FROM api_keys WHERE revoked_at IS NULL ORDER BY id DESC`
110
- : `SELECT key_id, tenant_id, label, created_at, revoked_at, role FROM api_keys ORDER BY id DESC`;
111
- // SAFETY: rows come from one of the two SELECTs above, both of which
112
- // project the same 6 columns (key_id, tenant_id, label, created_at,
113
- // revoked_at, role) in the same order.
114
- const rows = db.prepare(sql).all();
168
+ /** Keys newest first, with their row ids, filtered and limited in SQL. */
169
+ export function listApiKeyRows(db, opts) {
170
+ const where = ['1 = 1'];
171
+ const params = [];
172
+ if (opts.active)
173
+ where.push('revoked_at IS NULL');
174
+ if (opts.tenantId !== undefined) {
175
+ where.push('tenant_id = ?');
176
+ params.push(opts.tenantId);
177
+ }
178
+ const after = keysetAfter('id', 'id', opts.after);
179
+ params.push(...after.params);
180
+ const limitSql = opts.limit === undefined ? '' : ' LIMIT ?';
181
+ if (opts.limit !== undefined)
182
+ params.push(opts.limit);
183
+ const sql = `SELECT id, key_id, tenant_id, label, created_at, revoked_at, role FROM api_keys WHERE ${where.join(' AND ')}${after.sql} ORDER BY id DESC${limitSql}`;
184
+ // SAFETY: the SELECT above projects exactly these 7 columns, in this order.
185
+ const rows = db.prepare(sql).all(...params);
115
186
  return rows.map(r => ({
116
- keyId: r.key_id, tenantId: r.tenant_id, label: r.label,
117
- createdAt: r.created_at, revokedAt: r.revoked_at,
118
- role: r.role === 'admin' ? 'admin' : 'member',
119
- scopes: listScopeGrants(db, r.key_id),
187
+ rowId: r.id,
188
+ key: {
189
+ keyId: r.key_id, tenantId: r.tenant_id, label: r.label,
190
+ createdAt: r.created_at, revokedAt: r.revoked_at,
191
+ role: r.role === 'admin' ? 'admin' : 'member',
192
+ scopes: listScopeGrants(db, r.key_id),
193
+ },
120
194
  }));
121
195
  }
196
+ export function listApiKeys(db, opts) {
197
+ return listApiKeyRows(db, opts).map(r => r.key);
198
+ }
122
199
  //# sourceMappingURL=auth.js.map
package/dist/autolearn.js CHANGED
@@ -4,8 +4,8 @@
4
4
  */
5
5
  import { execSync, execFileSync, spawn } from 'child_process';
6
6
  import { createMemory, Layer, DEFAULT_HALF_LIFE_DAYS } from './memory.js';
7
- import { loadAllEntries } from './store.js';
8
- import { textOverlap } from './search.js';
7
+ import { loadAllEntries } from './store/entry-reads.js';
8
+ import { textOverlap } from './tokenize.js';
9
9
  import { isContentWorthStoring } from './audit.js';
10
10
  import { redactSecretsStrict } from './secret-detect.js';
11
11
  import { log } from './log.js';
@@ -0,0 +1,33 @@
1
+ import { type SessionTurn } from './transcript.js';
2
+ export interface CaptureOptions {
3
+ source: 'stdin' | 'file' | 'last-session';
4
+ filePath?: string;
5
+ /** Explicit transcript path for `--last-session`. Without one, `stdinText`
6
+ * is used, then auto-discovery under `~/.claude/projects/` on a manual run. */
7
+ transcriptPath?: string;
8
+ /** Read from stdin by the caller (cli.ts), which owns the bounded wait.
9
+ * `stdinTimedOut` marks an empty read "unknown", not "no payload". */
10
+ stdinText?: string;
11
+ stdinTimedOut?: boolean;
12
+ sessionTurns?: readonly SessionTurn[];
13
+ /**
14
+ * Tee stdout/stderr to this log file while capture runs. Mirrors the
15
+ * pattern used by `hippo sleep --log-file` so the SessionEnd hook output
16
+ * (invisible during TUI teardown) can be surfaced via `hippo last-sleep`
17
+ * on the next session start. Appends rather than truncates — `hippo sleep`
18
+ * writes the same file first in the SessionEnd sequence.
19
+ */
20
+ logFile?: string;
21
+ dryRun: boolean;
22
+ global: boolean;
23
+ /**
24
+ * L9: tenant scope for the dedup read in `cmdCaptureCore`. When provided
25
+ * AND `global` is false, the dedup check only considers this tenant's
26
+ * existing memories. Undefined preserves pre-1.12.1 host-wide dedup
27
+ * behaviour. Ignored when `global: true` (global captures are host-wide).
28
+ */
29
+ tenantId?: string;
30
+ originProject?: string;
31
+ }
32
+ export declare function cmdCapture(hippoRoot: string, options: CaptureOptions): void;
33
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1,264 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { createMemory, Layer } from '../memory.js';
4
+ import { duplicateKey, storedTextKeys } from '../same-text.js';
5
+ import { stampOriginProject } from '../store/entry-row.js';
6
+ import { isInitialized, openStore } from '../store/open.js';
7
+ import { writeEntryMirrors } from '../store/entry-writes.js';
8
+ import { loadAllEntries } from '../store/entry-reads.js';
9
+ import { updateStats } from '../store/index-and-stats.js';
10
+ import { gatedWrite } from '../gated-write.js';
11
+ import { getGlobalRoot, initGlobal } from '../shared.js';
12
+ import { embedMemory } from '../embeddings.js';
13
+ import { maskEmails, redactSecretsStrict } from '../secret-detect.js';
14
+ import { RejectedValueError, checkRejectionGuard } from '../rejection.js';
15
+ import { openHippoDb, closeHippoDb } from '../db.js';
16
+ import { loadConfig } from '../config.js';
17
+ import { classifyOriginProject } from '../project-identity.js';
18
+ import { isStringValue } from '../capture-contract.js';
19
+ import { errorMessage, log } from '../log.js';
20
+ import { extractFromText } from './extract.js';
21
+ import { collectSessionTurns, summariseSessionTurns, resolveLastSessionTranscript } from './transcript.js';
22
+ export function cmdCapture(hippoRoot, options) {
23
+ // Tee stdout/stderr to a log file when --log-file is set. Used by the
24
+ // SessionEnd hook so output (otherwise swallowed by TUI teardown) surfaces
25
+ // on the next session start via `hippo last-sleep`. Runs second in the
26
+ // SessionEnd sequence after `hippo sleep`, so we APPEND rather than
27
+ // truncate — sleep already wrote its own header + body to this file.
28
+ const restoreStdio = options.logFile ? beginLogTee(options.logFile) : null;
29
+ try {
30
+ cmdCaptureCore(hippoRoot, options);
31
+ if (options.logFile)
32
+ console.log('[hippo] capture complete');
33
+ }
34
+ catch (err) {
35
+ if (options.logFile)
36
+ console.log(`[hippo] capture failed: ${errorMessage(err)}`);
37
+ throw err;
38
+ }
39
+ finally {
40
+ if (restoreStdio)
41
+ restoreStdio();
42
+ }
43
+ }
44
+ /**
45
+ * Append-mode tee: writes a banner line then mirrors every stdout/stderr
46
+ * chunk to `logFile` until the returned restore function is called.
47
+ * Failures to write the log are non-fatal; the real streams still get
48
+ * the data.
49
+ */
50
+ function beginLogTee(logFile) {
51
+ try {
52
+ fs.mkdirSync(path.dirname(logFile), { recursive: true });
53
+ fs.appendFileSync(logFile, `[hippo] ${new Date().toISOString()} capturing session...\n`, 'utf8');
54
+ }
55
+ catch (err) {
56
+ log.warn(`could not open log file ${logFile}: ${errorMessage(err)}`);
57
+ return () => { };
58
+ }
59
+ const origStdoutWrite = process.stdout.write.bind(process.stdout);
60
+ const origStderrWrite = process.stderr.write.bind(process.stderr);
61
+ const tee = (chunk) => {
62
+ try {
63
+ const buf = isStringValue(chunk) ? chunk : Buffer.from(chunk).toString('utf8');
64
+ fs.appendFileSync(logFile, buf, 'utf8');
65
+ }
66
+ catch {
67
+ // log failures are non-fatal
68
+ }
69
+ };
70
+ const wrapWrite = (origWrite) => {
71
+ const wrapped = (...args) => {
72
+ tee(args[0]);
73
+ // SAFETY: forwarding the exact arguments Node's real overloaded
74
+ // `write` received is safe regardless of which overload the call
75
+ // site used — Node dispatches on the actual argument shapes at
76
+ // runtime, and `StreamWriteArgs` is the union of both overloads'
77
+ // parameter lists.
78
+ return origWrite(...args);
79
+ };
80
+ // SAFETY: `wrapped` matches both real `write` overload shapes it's
81
+ // assigned to; TS can't verify a single implementation covers an
82
+ // overloaded type, but this one forwards to the real stream method.
83
+ return wrapped;
84
+ };
85
+ process.stdout.write = wrapWrite(origStdoutWrite);
86
+ process.stderr.write = wrapWrite(origStderrWrite);
87
+ return () => {
88
+ process.stdout.write = origStdoutWrite;
89
+ process.stderr.write = origStderrWrite;
90
+ };
91
+ }
92
+ // Console lines here are the `hippo capture` command's printed result, so they stay off the logger.
93
+ function cmdCaptureCore(hippoRoot, options) {
94
+ const useGlobal = options.global;
95
+ const targetRoot = useGlobal ? getGlobalRoot() : hippoRoot;
96
+ if (useGlobal) {
97
+ initGlobal();
98
+ }
99
+ else {
100
+ if (!isInitialized(hippoRoot)) {
101
+ console.error(`No hippo store at ${hippoRoot} (searched ${process.cwd()} and its parents up to your home directory). Run \`hippo init\` first.`);
102
+ process.exit(1);
103
+ }
104
+ }
105
+ // Read input text
106
+ const text = readCaptureText(options);
107
+ if (text === null)
108
+ return;
109
+ if (!text || text.trim().length === 0) {
110
+ console.log('No text to capture from.');
111
+ return;
112
+ }
113
+ // Scrub once here, as the snapshot fields are: every source can carry a pasted token (AGENTS.md: no secrets in memories).
114
+ const extracted = extractFromText(maskEmails(redactSecretsStrict(text)));
115
+ if (extracted.length === 0) {
116
+ console.log('No actionable items found in the input.');
117
+ return;
118
+ }
119
+ // Dedup only against rows this capture's reader sees: another tenant's rows (L9), or another
120
+ // project's, are hidden from it, so they must not stop its own copy.
121
+ const stored = loadAllEntries(targetRoot, useGlobal ? undefined : options.tenantId);
122
+ const origin = options.originProject;
123
+ const keys = storedTextKeys(origin === undefined
124
+ ? stored
125
+ : stored.filter((e) => classifyOriginProject(e.origin_project, origin) !== 'cross-project'));
126
+ const { captured, skipped, rejected } = captureExtractedItems(targetRoot, options, extracted, keys);
127
+ const prefix = options.dryRun ? '[dry-run] ' : '';
128
+ const globalPrefix = useGlobal ? '[global] ' : '';
129
+ console.log(`\n${prefix}${globalPrefix}Captured ${captured} items (${skipped} skipped as duplicates` +
130
+ (rejected > 0 ? `, ${rejected} rejected` : '') +
131
+ ')');
132
+ }
133
+ /** The raw text for the chosen source; null after printing why a last-session capture has nothing. */
134
+ function readCaptureText(options) {
135
+ switch (options.source) {
136
+ case 'stdin': {
137
+ try {
138
+ return fs.readFileSync(0, 'utf8');
139
+ }
140
+ catch {
141
+ console.error('No input on stdin. Pipe text in or use --file <path>.');
142
+ process.exit(1);
143
+ }
144
+ }
145
+ case 'file': {
146
+ if (!options.filePath) {
147
+ console.error('Missing file path. Usage: hippo capture --file <path>');
148
+ process.exit(1);
149
+ }
150
+ if (!fs.existsSync(options.filePath)) {
151
+ console.error(`File not found: ${options.filePath}`);
152
+ process.exit(1);
153
+ }
154
+ return fs.readFileSync(options.filePath, 'utf8');
155
+ }
156
+ case 'last-session': {
157
+ let turns = options.sessionTurns;
158
+ if (!turns) {
159
+ const resolved = resolveLastSessionTranscript(options.transcriptPath, options.stdinText, { mayScan: !options.stdinTimedOut });
160
+ if (!resolved) {
161
+ console.log('No transcript found. Pass --transcript <path> or run from a SessionEnd hook.');
162
+ return null;
163
+ }
164
+ turns = collectSessionTurns(fs.readFileSync(resolved, 'utf8'));
165
+ }
166
+ const text = summariseSessionTurns(turns);
167
+ if (!text) {
168
+ console.log('Transcript had no user/assistant messages to summarise.');
169
+ return null;
170
+ }
171
+ return text;
172
+ }
173
+ }
174
+ }
175
+ function captureExtractedItems(targetRoot, options, extracted, keys) {
176
+ const tally = { captured: 0, skipped: 0, rejected: 0 };
177
+ const baseHalfLifeDays = loadConfig(targetRoot).defaultHalfLifeDays;
178
+ // AT1 P2 fix (dry-run parity, docs/plans/2026-08-15-at1-rejected-value-tombstone.md):
179
+ // dry-run used to skip the guarded write branch ENTIRELY, so a tombstoned
180
+ // extraction printed as `[capture]` and counted toward `captured` — the
181
+ // preview lied about what a real run would do. Mirrors importers.ts's
182
+ // importEntries dry-run probe (commit 6146e82): open a read-only handle
183
+ // once, run the same checkRejectionGuard the real write path uses via
184
+ // writeEntry, never write anything.
185
+ const dryRunDb = options.dryRun ? openHippoDb(targetRoot) : null;
186
+ const writeDb = options.dryRun ? null : openStore(targetRoot);
187
+ try {
188
+ for (const item of extracted) {
189
+ if (keys.has(duplicateKey(item.content))) {
190
+ tally.skipped++;
191
+ if (options.dryRun) {
192
+ console.log(` [skip] (${item.category}) ${item.content.slice(0, 80)}`);
193
+ }
194
+ continue;
195
+ }
196
+ const entry = captureEntry(item, options, baseHalfLifeDays);
197
+ tally[captureOne({ targetRoot, options, dryRunDb, writeDb, keys }, item, entry)]++;
198
+ }
199
+ }
200
+ finally {
201
+ if (dryRunDb)
202
+ closeHippoDb(dryRunDb);
203
+ if (writeDb)
204
+ closeHippoDb(writeDb);
205
+ }
206
+ return tally;
207
+ }
208
+ function captureEntry(item, options, baseHalfLifeDays) {
209
+ const useGlobal = options.global;
210
+ // A3: kind defaults to 'distilled'. capture.ts extracts curated items from
211
+ // session output (not raw transcript chunks), so distilled is correct. If a
212
+ // future variant captures full raw session text, it MUST set kind: 'raw'
213
+ // and route deletions through archiveRawMemory(). See MEMORY_ENVELOPE.md.
214
+ // L9: the dedup read above is scoped by options.tenantId — the WRITE
215
+ // must match, or scoped-dedup-passes-then-default-tenant-write breaks
216
+ // the per-tenant contract. Mirror the dedup-read guard: when
217
+ // global: true, the global store is host-wide and tenant is irrelevant
218
+ // (createMemory's default 'default' applies). When global: false,
219
+ // options.tenantId scopes the write to the same tenant as the dedup.
220
+ const created = createMemory(item.content, {
221
+ layer: Layer.Episodic,
222
+ tags: item.tags,
223
+ source: 'capture',
224
+ confidence: 'observed',
225
+ tenantId: useGlobal ? undefined : options.tenantId,
226
+ baseHalfLifeDays,
227
+ });
228
+ return options.originProject === undefined ? created : { ...created, origin_project: options.originProject };
229
+ }
230
+ /** Previews or writes one non-duplicate item and names the tally it counts toward. */
231
+ function captureOne(ctx, item, entry) {
232
+ const { targetRoot, options, dryRunDb, writeDb, keys } = ctx;
233
+ if (options.dryRun) {
234
+ if (dryRunDb) {
235
+ try {
236
+ checkRejectionGuard(dryRunDb, entry.tenantId ?? 'default', entry.id, entry.content);
237
+ }
238
+ catch (err) {
239
+ if (err instanceof RejectedValueError) {
240
+ console.log(` [reject] (${item.category}) ${item.content.slice(0, 80)} - matches a rejected value`);
241
+ return 'rejected';
242
+ }
243
+ throw err;
244
+ }
245
+ }
246
+ console.log(` [capture] (${item.category}) ${item.content}`);
247
+ }
248
+ else if (writeDb !== null) {
249
+ // AT1 (plan §3 containment): one rejected item must not abort the
250
+ // rest of this capture's items.
251
+ const stamped = stampOriginProject(targetRoot, entry);
252
+ const outcome = gatedWrite(writeDb, targetRoot, stamped);
253
+ if (outcome === 'skipped:rejected')
254
+ return 'rejected';
255
+ if (outcome !== 'written')
256
+ return 'skipped';
257
+ writeEntryMirrors(targetRoot, stamped);
258
+ updateStats(targetRoot, { remembered: 1 });
259
+ keys.add(duplicateKey(item.content)); // within-batch dedup
260
+ void embedMemory(targetRoot, entry);
261
+ }
262
+ return 'captured';
263
+ }
264
+ //# sourceMappingURL=command.js.map