hippo-memory 1.60.0 → 1.61.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 (585) hide show
  1. package/README.md +1 -1
  2. package/dist/ablation.js +9 -27
  3. package/dist/agent-memories/apply.js +4 -1
  4. package/dist/agent-memories/claude-code.js +1 -1
  5. package/dist/agent-memories/codex.js +1 -1
  6. package/dist/agent-memories/gemini.js +1 -1
  7. package/dist/agent-memories/legacy.js +1 -1
  8. package/dist/agent-memories/source.js +1 -1
  9. package/dist/agent-memories/sync.js +8 -3
  10. package/dist/ambient-store.d.ts +14 -0
  11. package/dist/ambient-store.js +90 -0
  12. package/dist/ambient.d.ts +23 -0
  13. package/dist/ambient.js +72 -50
  14. package/dist/api/assemble.d.ts +93 -0
  15. package/dist/api/assemble.js +152 -0
  16. package/dist/api/audit.d.ts +17 -0
  17. package/dist/api/audit.js +23 -0
  18. package/dist/api/auth.d.ts +79 -0
  19. package/dist/api/auth.js +178 -0
  20. package/dist/api/context-types.d.ts +105 -0
  21. package/dist/api/context-types.js +3 -0
  22. package/dist/api/context.d.ts +31 -0
  23. package/dist/api/context.js +705 -0
  24. package/dist/api/dormant.d.ts +30 -0
  25. package/dist/api/dormant.js +140 -0
  26. package/dist/api/drill-down.d.ts +84 -0
  27. package/dist/api/drill-down.js +123 -0
  28. package/dist/api/forget.d.ts +57 -0
  29. package/dist/api/forget.js +87 -0
  30. package/dist/api/goals.d.ts +18 -0
  31. package/dist/api/goals.js +33 -0
  32. package/dist/api/learn.d.ts +31 -0
  33. package/dist/api/learn.js +88 -0
  34. package/dist/api/outcome.d.ts +61 -0
  35. package/dist/api/outcome.js +66 -0
  36. package/dist/api/promote.d.ts +54 -0
  37. package/dist/api/promote.js +203 -0
  38. package/dist/api/quarantine.d.ts +24 -0
  39. package/dist/api/quarantine.js +121 -0
  40. package/dist/api/recall-types.d.ts +390 -0
  41. package/dist/api/recall-types.js +3 -0
  42. package/dist/api/recall.d.ts +36 -0
  43. package/dist/api/recall.js +634 -0
  44. package/dist/api/remember.d.ts +35 -0
  45. package/dist/api/remember.js +43 -0
  46. package/dist/api/sleep.d.ts +136 -0
  47. package/dist/api/sleep.js +271 -0
  48. package/dist/api/tokens.d.ts +26 -0
  49. package/dist/api/tokens.js +60 -0
  50. package/dist/api/types.d.ts +56 -0
  51. package/dist/api/types.js +38 -0
  52. package/dist/api.d.ts +20 -1262
  53. package/dist/api.js +25 -2727
  54. package/dist/audit.d.ts +3 -0
  55. package/dist/audit.js +6 -3
  56. package/dist/auth.d.ts +45 -4
  57. package/dist/auth.js +125 -48
  58. package/dist/autolearn.js +2 -2
  59. package/dist/capture/command.d.ts +33 -0
  60. package/dist/capture/command.js +264 -0
  61. package/dist/capture/compact.d.ts +44 -0
  62. package/dist/capture/compact.js +354 -0
  63. package/dist/capture/extract.d.ts +21 -0
  64. package/dist/capture/extract.js +464 -0
  65. package/dist/capture/transcript.d.ts +40 -0
  66. package/dist/capture/transcript.js +193 -0
  67. package/dist/capture-error.js +2 -1
  68. package/dist/churn-git.js +4 -2
  69. package/dist/cli/audit.d.ts +3 -0
  70. package/dist/cli/audit.js +159 -0
  71. package/dist/cli/auth.d.ts +2 -0
  72. package/dist/cli/auth.js +171 -0
  73. package/dist/cli/briefs.d.ts +4 -0
  74. package/dist/cli/briefs.js +435 -0
  75. package/dist/cli/card.d.ts +3 -0
  76. package/dist/cli/card.js +333 -0
  77. package/dist/cli/context.d.ts +15 -0
  78. package/dist/cli/context.js +366 -0
  79. package/dist/cli/continuity.d.ts +6 -0
  80. package/dist/cli/continuity.js +445 -0
  81. package/dist/cli/curate.d.ts +19 -0
  82. package/dist/cli/curate.js +555 -0
  83. package/dist/cli/dag.d.ts +5 -0
  84. package/dist/cli/dag.js +177 -0
  85. package/dist/cli/decisions.d.ts +4 -0
  86. package/dist/cli/decisions.js +528 -0
  87. package/dist/cli/eval.d.ts +5 -0
  88. package/dist/cli/eval.js +213 -0
  89. package/dist/cli/explain.d.ts +4 -0
  90. package/dist/cli/explain.js +150 -0
  91. package/dist/cli/goals.d.ts +2 -0
  92. package/dist/cli/goals.js +196 -0
  93. package/dist/cli/hook-blocks.d.ts +18 -0
  94. package/dist/cli/hook-blocks.js +233 -0
  95. package/dist/cli/init.d.ts +2 -0
  96. package/dist/cli/init.js +305 -0
  97. package/dist/cli/maintenance.d.ts +4 -0
  98. package/dist/cli/maintenance.js +179 -0
  99. package/dist/cli/playbooks.d.ts +4 -0
  100. package/dist/cli/playbooks.js +556 -0
  101. package/dist/cli/projects.js +1 -1
  102. package/dist/cli/recall.d.ts +7 -0
  103. package/dist/cli/recall.js +597 -0
  104. package/dist/cli/remember.d.ts +5 -0
  105. package/dist/cli/remember.js +442 -0
  106. package/dist/cli/serve.d.ts +5 -0
  107. package/dist/cli/serve.js +40 -0
  108. package/dist/cli/session-hooks.d.ts +29 -0
  109. package/dist/cli/session-hooks.js +637 -0
  110. package/dist/cli/setup.d.ts +4 -0
  111. package/dist/cli/setup.js +376 -0
  112. package/dist/cli/shared.d.ts +15 -19
  113. package/dist/cli/shared.js +50 -341
  114. package/dist/cli/slack.d.ts +2 -0
  115. package/dist/cli/slack.js +171 -0
  116. package/dist/cli/status.d.ts +18 -0
  117. package/dist/cli/status.js +400 -0
  118. package/dist/cli/transfer.d.ts +10 -0
  119. package/dist/cli/transfer.js +438 -0
  120. package/dist/cli/usage.d.ts +85 -0
  121. package/dist/cli/usage.js +741 -0
  122. package/dist/cli.d.ts +71 -120
  123. package/dist/cli.js +170 -8469
  124. package/dist/client.js +15 -8
  125. package/dist/compaction-record.js +6 -4
  126. package/dist/connectors/github/backfill.js +94 -87
  127. package/dist/connectors/github/cli-impl.js +4 -3
  128. package/dist/connectors/github/dlq.js +67 -54
  129. package/dist/connectors/github/ingest.js +34 -35
  130. package/dist/connectors/github/tenant-routing.js +3 -2
  131. package/dist/connectors/github/webhook.js +135 -216
  132. package/dist/connectors/slack/dlq.js +49 -61
  133. package/dist/connectors/slack/ingest.js +55 -48
  134. package/dist/connectors/slack/tenant-routing.js +4 -3
  135. package/dist/connectors/slack/webhook.js +72 -74
  136. package/dist/consolidate/conflicts.d.ts +10 -0
  137. package/dist/consolidate/conflicts.js +178 -0
  138. package/dist/consolidate/decay.d.ts +12 -0
  139. package/dist/consolidate/decay.js +145 -0
  140. package/dist/consolidate/llm-passes.d.ts +3 -0
  141. package/dist/consolidate/llm-passes.js +141 -0
  142. package/dist/consolidate/merge.d.ts +7 -0
  143. package/dist/consolidate/merge.js +251 -0
  144. package/dist/consolidate/physics-pass.d.ts +3 -0
  145. package/dist/consolidate/physics-pass.js +60 -0
  146. package/dist/consolidate/run.d.ts +69 -0
  147. package/dist/consolidate/run.js +76 -0
  148. package/dist/consolidate/sleep.d.ts +18 -0
  149. package/dist/consolidate/sleep.js +209 -0
  150. package/dist/consolidate/traces.d.ts +4 -0
  151. package/dist/consolidate/traces.js +178 -0
  152. package/dist/context-auto.js +7 -11
  153. package/dist/context-render.d.ts +1 -1
  154. package/dist/context-render.js +1 -1
  155. package/dist/customer-notes.d.ts +3 -0
  156. package/dist/customer-notes.js +6 -4
  157. package/dist/dag.js +6 -5
  158. package/dist/dashboard-actions.d.ts +20 -0
  159. package/dist/dashboard-actions.js +88 -0
  160. package/dist/dashboard-params.d.ts +45 -0
  161. package/dist/dashboard-params.js +127 -0
  162. package/dist/dashboard-queries.d.ts +15 -0
  163. package/dist/dashboard-queries.js +355 -0
  164. package/dist/dashboard-snapshot.d.ts +138 -0
  165. package/dist/dashboard-snapshot.js +308 -0
  166. package/dist/dashboard-types.d.ts +163 -0
  167. package/dist/dashboard-types.js +3 -0
  168. package/dist/dashboard.d.ts +6 -7
  169. package/dist/dashboard.js +228 -202
  170. package/dist/db/busy.d.ts +5 -0
  171. package/dist/db/busy.js +24 -0
  172. package/dist/db/continuity.d.ts +5 -0
  173. package/dist/db/continuity.js +145 -0
  174. package/dist/db/meta.d.ts +8 -0
  175. package/dist/db/meta.js +35 -0
  176. package/dist/db/migrate.d.ts +9 -0
  177. package/dist/db/migrate.js +138 -0
  178. package/dist/db/migrations/index.d.ts +5 -0
  179. package/dist/db/migrations/index.js +109 -0
  180. package/dist/db/migrations/types.d.ts +14 -0
  181. package/dist/db/migrations/types.js +2 -0
  182. package/dist/db/migrations/v01.d.ts +3 -0
  183. package/dist/db/migrations/v01.js +38 -0
  184. package/dist/db/migrations/v02.d.ts +3 -0
  185. package/dist/db/migrations/v02.js +21 -0
  186. package/dist/db/migrations/v03.d.ts +3 -0
  187. package/dist/db/migrations/v03.js +22 -0
  188. package/dist/db/migrations/v04.d.ts +3 -0
  189. package/dist/db/migrations/v04.js +28 -0
  190. package/dist/db/migrations/v05.d.ts +3 -0
  191. package/dist/db/migrations/v05.js +21 -0
  192. package/dist/db/migrations/v06.d.ts +3 -0
  193. package/dist/db/migrations/v06.js +25 -0
  194. package/dist/db/migrations/v07.d.ts +3 -0
  195. package/dist/db/migrations/v07.js +13 -0
  196. package/dist/db/migrations/v08.d.ts +3 -0
  197. package/dist/db/migrations/v08.js +8 -0
  198. package/dist/db/migrations/v09.d.ts +3 -0
  199. package/dist/db/migrations/v09.js +13 -0
  200. package/dist/db/migrations/v10.d.ts +3 -0
  201. package/dist/db/migrations/v10.js +17 -0
  202. package/dist/db/migrations/v11.d.ts +3 -0
  203. package/dist/db/migrations/v11.js +15 -0
  204. package/dist/db/migrations/v12.d.ts +3 -0
  205. package/dist/db/migrations/v12.js +11 -0
  206. package/dist/db/migrations/v13.d.ts +3 -0
  207. package/dist/db/migrations/v13.js +15 -0
  208. package/dist/db/migrations/v14.d.ts +3 -0
  209. package/dist/db/migrations/v14.js +66 -0
  210. package/dist/db/migrations/v15.d.ts +3 -0
  211. package/dist/db/migrations/v15.js +42 -0
  212. package/dist/db/migrations/v16.d.ts +3 -0
  213. package/dist/db/migrations/v16.js +61 -0
  214. package/dist/db/migrations/v17.d.ts +3 -0
  215. package/dist/db/migrations/v17.js +46 -0
  216. package/dist/db/migrations/v18.d.ts +3 -0
  217. package/dist/db/migrations/v18.js +60 -0
  218. package/dist/db/migrations/v19.d.ts +3 -0
  219. package/dist/db/migrations/v19.js +28 -0
  220. package/dist/db/migrations/v20.d.ts +3 -0
  221. package/dist/db/migrations/v20.js +42 -0
  222. package/dist/db/migrations/v21.d.ts +3 -0
  223. package/dist/db/migrations/v21.js +16 -0
  224. package/dist/db/migrations/v22.d.ts +3 -0
  225. package/dist/db/migrations/v22.js +82 -0
  226. package/dist/db/migrations/v23.d.ts +3 -0
  227. package/dist/db/migrations/v23.js +48 -0
  228. package/dist/db/migrations/v24.d.ts +3 -0
  229. package/dist/db/migrations/v24.js +72 -0
  230. package/dist/db/migrations/v25.d.ts +3 -0
  231. package/dist/db/migrations/v25.js +46 -0
  232. package/dist/db/migrations/v26.d.ts +3 -0
  233. package/dist/db/migrations/v26.js +23 -0
  234. package/dist/db/migrations/v27.d.ts +3 -0
  235. package/dist/db/migrations/v27.js +57 -0
  236. package/dist/db/migrations/v28.d.ts +3 -0
  237. package/dist/db/migrations/v28.js +38 -0
  238. package/dist/db/migrations/v29.d.ts +3 -0
  239. package/dist/db/migrations/v29.js +78 -0
  240. package/dist/db/migrations/v30.d.ts +3 -0
  241. package/dist/db/migrations/v30.js +92 -0
  242. package/dist/db/migrations/v31.d.ts +3 -0
  243. package/dist/db/migrations/v31.js +74 -0
  244. package/dist/db/migrations/v32.d.ts +3 -0
  245. package/dist/db/migrations/v32.js +102 -0
  246. package/dist/db/migrations/v33.d.ts +3 -0
  247. package/dist/db/migrations/v33.js +104 -0
  248. package/dist/db/migrations/v34.d.ts +3 -0
  249. package/dist/db/migrations/v34.js +93 -0
  250. package/dist/db/migrations/v35.d.ts +3 -0
  251. package/dist/db/migrations/v35.js +98 -0
  252. package/dist/db/migrations/v36.d.ts +3 -0
  253. package/dist/db/migrations/v36.js +98 -0
  254. package/dist/db/migrations/v37.d.ts +3 -0
  255. package/dist/db/migrations/v37.js +219 -0
  256. package/dist/db/migrations/v38.d.ts +3 -0
  257. package/dist/db/migrations/v38.js +277 -0
  258. package/dist/db/migrations/v39.d.ts +3 -0
  259. package/dist/db/migrations/v39.js +59 -0
  260. package/dist/db/migrations/v40.d.ts +3 -0
  261. package/dist/db/migrations/v40.js +74 -0
  262. package/dist/db/migrations/v41.d.ts +3 -0
  263. package/dist/db/migrations/v41.js +43 -0
  264. package/dist/db/migrations/v42.d.ts +3 -0
  265. package/dist/db/migrations/v42.js +41 -0
  266. package/dist/db/migrations/v43.d.ts +3 -0
  267. package/dist/db/migrations/v43.js +67 -0
  268. package/dist/db/migrations/v44.d.ts +3 -0
  269. package/dist/db/migrations/v44.js +28 -0
  270. package/dist/db/migrations/v45.d.ts +3 -0
  271. package/dist/db/migrations/v45.js +30 -0
  272. package/dist/db/migrations/v46.d.ts +3 -0
  273. package/dist/db/migrations/v46.js +25 -0
  274. package/dist/db/migrations/v47.d.ts +3 -0
  275. package/dist/db/migrations/v47.js +17 -0
  276. package/dist/db/migrations/v48.d.ts +3 -0
  277. package/dist/db/migrations/v48.js +10 -0
  278. package/dist/db/migrations/v49.d.ts +3 -0
  279. package/dist/db/migrations/v49.js +31 -0
  280. package/dist/db/migrations/v50.d.ts +3 -0
  281. package/dist/db/migrations/v50.js +67 -0
  282. package/dist/db/migrations/v51.d.ts +3 -0
  283. package/dist/db/migrations/v51.js +14 -0
  284. package/dist/db/migrations/v52.d.ts +3 -0
  285. package/dist/db/migrations/v52.js +7 -0
  286. package/dist/db/open.d.ts +23 -0
  287. package/dist/db/open.js +146 -0
  288. package/dist/db/sqlite.d.ts +21 -0
  289. package/dist/db/sqlite.js +8 -0
  290. package/dist/db/tables.d.ts +7 -0
  291. package/dist/db/tables.js +38 -0
  292. package/dist/db.d.ts +6 -46
  293. package/dist/db.js +5 -3049
  294. package/dist/decisions.d.ts +4 -1
  295. package/dist/decisions.js +9 -7
  296. package/dist/dedupe.js +3 -2
  297. package/dist/delivery-recorder.js +4 -1
  298. package/dist/doctor.js +3 -3
  299. package/dist/embedding-provider.d.ts +1 -1
  300. package/dist/embedding-provider.js +4 -3
  301. package/dist/embeddings.d.ts +9 -52
  302. package/dist/embeddings.js +43 -297
  303. package/dist/env.d.ts +75 -0
  304. package/dist/env.js +119 -0
  305. package/dist/eval-suite.js +1 -1
  306. package/dist/eval.js +2 -2
  307. package/dist/extract.js +1 -1
  308. package/dist/gated-write.js +3 -1
  309. package/dist/goals.d.ts +3 -1
  310. package/dist/goals.js +18 -0
  311. package/dist/graph/read.d.ts +73 -0
  312. package/dist/graph/read.js +325 -0
  313. package/dist/graph/rows.d.ts +45 -0
  314. package/dist/graph/rows.js +51 -0
  315. package/dist/graph/types.d.ts +83 -0
  316. package/dist/graph/types.js +11 -0
  317. package/dist/graph/write.d.ts +93 -0
  318. package/dist/{graph.js → graph/write.js} +6 -384
  319. package/dist/graph-extract.js +2 -1
  320. package/dist/graph-recall.d.ts +1 -1
  321. package/dist/graph-recall.js +2 -2
  322. package/dist/graph-stream.js +1 -1
  323. package/dist/graph-view.d.ts +1 -1
  324. package/dist/graph-view.js +1 -1
  325. package/dist/half-life-migration.d.ts +1 -1
  326. package/dist/half-life-migration.js +2 -1
  327. package/dist/hooks/codex-session.d.ts +8 -0
  328. package/dist/hooks/codex-session.js +76 -0
  329. package/dist/hooks/codex-wrapper.d.ts +55 -0
  330. package/dist/hooks/codex-wrapper.js +288 -0
  331. package/dist/hooks/json-hooks.d.ts +63 -0
  332. package/dist/hooks/json-hooks.js +356 -0
  333. package/dist/hooks/opencode.d.ts +50 -0
  334. package/dist/hooks/opencode.js +202 -0
  335. package/dist/hooks/shared.d.ts +54 -0
  336. package/dist/hooks/shared.js +77 -0
  337. package/dist/http-retry.d.ts +2 -0
  338. package/dist/http-retry.js +4 -3
  339. package/dist/http-util.d.ts +3 -0
  340. package/dist/http-util.js +10 -0
  341. package/dist/{importers.d.ts → importers/core.d.ts} +13 -17
  342. package/dist/importers/core.js +141 -0
  343. package/dist/importers/markdown-parse.d.ts +41 -0
  344. package/dist/importers/markdown-parse.js +132 -0
  345. package/dist/importers/markdown.d.ts +3 -0
  346. package/dist/importers/markdown.js +92 -0
  347. package/dist/importers/sources.d.ts +7 -0
  348. package/dist/importers/sources.js +229 -0
  349. package/dist/importers/vault.d.ts +11 -0
  350. package/dist/importers/vault.js +352 -0
  351. package/dist/incidents.d.ts +3 -0
  352. package/dist/incidents.js +7 -5
  353. package/dist/index.d.ts +25 -6
  354. package/dist/index.js +23 -6
  355. package/dist/invalidation.js +2 -1
  356. package/dist/judgment.js +2 -1
  357. package/dist/keyset.d.ts +13 -0
  358. package/dist/keyset.js +8 -0
  359. package/dist/local-embedding.d.ts +13 -0
  360. package/dist/local-embedding.js +165 -0
  361. package/dist/log.d.ts +7 -0
  362. package/dist/log.js +19 -1
  363. package/dist/mcp/admin-tools.d.ts +8 -0
  364. package/dist/mcp/admin-tools.js +116 -0
  365. package/dist/mcp/format.d.ts +27 -0
  366. package/dist/mcp/format.js +135 -0
  367. package/dist/mcp/memory-tools.d.ts +5 -0
  368. package/dist/mcp/memory-tools.js +83 -0
  369. package/dist/mcp/protocol.d.ts +83 -0
  370. package/dist/mcp/protocol.js +55 -0
  371. package/dist/mcp/recall-tools.d.ts +6 -0
  372. package/dist/mcp/recall-tools.js +320 -0
  373. package/dist/mcp/request.d.ts +10 -0
  374. package/dist/mcp/request.js +163 -0
  375. package/dist/mcp/server.d.ts +4 -75
  376. package/dist/mcp/server.js +7 -1190
  377. package/dist/mcp/session-state.d.ts +11 -0
  378. package/dist/mcp/session-state.js +28 -0
  379. package/dist/mcp/stdio.d.ts +8 -0
  380. package/dist/mcp/stdio.js +79 -0
  381. package/dist/mcp/tools.d.ts +11 -0
  382. package/dist/mcp/tools.js +247 -0
  383. package/dist/memory.d.ts +3 -0
  384. package/dist/memory.js +27 -1
  385. package/dist/multihop.d.ts +1 -1
  386. package/dist/multihop.js +2 -1
  387. package/dist/owner-validation.js +2 -1
  388. package/dist/physics-state.js +10 -7
  389. package/dist/policies.d.ts +3 -0
  390. package/dist/policies.js +8 -6
  391. package/dist/postinstall.js +3 -2
  392. package/dist/predictions/planning-fallacy.d.ts +100 -0
  393. package/dist/predictions/planning-fallacy.js +190 -0
  394. package/dist/{predictions.d.ts → predictions/store.d.ts} +7 -102
  395. package/dist/predictions/store.js +434 -0
  396. package/dist/processes.d.ts +3 -0
  397. package/dist/processes.js +7 -5
  398. package/dist/project-briefs.d.ts +3 -0
  399. package/dist/project-briefs.js +7 -5
  400. package/dist/project-identity.js +3 -2
  401. package/dist/project-merge.js +3 -1
  402. package/dist/quarantine.d.ts +2 -1
  403. package/dist/quarantine.js +8 -5
  404. package/dist/raw-archive.js +1 -1
  405. package/dist/recall-history.js +3 -2
  406. package/dist/recall-pipeline.d.ts +2 -2
  407. package/dist/recall-pipeline.js +16 -8
  408. package/dist/recall-scope.js +1 -1
  409. package/dist/recall-trace.d.ts +1 -1
  410. package/dist/refine-llm.js +2 -1
  411. package/dist/reject-flow.js +6 -1
  412. package/dist/rerankers/clef.js +6 -5
  413. package/dist/rerankers/jev.d.ts +1 -1
  414. package/dist/rerankers/jev.js +4 -4
  415. package/dist/rerankers/llm.d.ts +4 -2
  416. package/dist/rerankers/llm.js +58 -38
  417. package/dist/rerankers/types.d.ts +1 -1
  418. package/dist/salience.js +1 -1
  419. package/dist/scheduler.d.ts +1 -0
  420. package/dist/scheduler.js +26 -4
  421. package/dist/scope.js +4 -3
  422. package/dist/search/as-of.d.ts +10 -0
  423. package/dist/search/as-of.js +22 -0
  424. package/dist/search/bm25-search.d.ts +14 -0
  425. package/dist/search/bm25-search.js +43 -0
  426. package/dist/search/bm25.d.ts +15 -0
  427. package/dist/search/bm25.js +54 -0
  428. package/dist/search/boosts.d.ts +54 -0
  429. package/dist/search/boosts.js +94 -0
  430. package/dist/search/breakdown.d.ts +7 -0
  431. package/dist/search/breakdown.js +20 -0
  432. package/dist/search/explain.d.ts +25 -0
  433. package/dist/search/explain.js +31 -0
  434. package/dist/search/finalize.d.ts +8 -0
  435. package/dist/search/finalize.js +52 -0
  436. package/dist/search/fusion.d.ts +27 -0
  437. package/dist/search/fusion.js +42 -0
  438. package/dist/search/hybrid-score.d.ts +20 -0
  439. package/dist/search/hybrid-score.js +73 -0
  440. package/dist/search/hybrid.d.ts +46 -0
  441. package/dist/search/hybrid.js +64 -0
  442. package/dist/search/physics-search.d.ts +29 -0
  443. package/dist/search/physics-search.js +162 -0
  444. package/dist/search/rerank.d.ts +10 -0
  445. package/dist/search/rerank.js +72 -0
  446. package/dist/search/temporal.d.ts +15 -0
  447. package/dist/search/temporal.js +45 -0
  448. package/dist/search/types.d.ts +91 -0
  449. package/dist/search/types.js +2 -0
  450. package/dist/search/vector.d.ts +30 -0
  451. package/dist/search/vector.js +71 -0
  452. package/dist/secret-detect.d.ts +2 -0
  453. package/dist/secret-detect.js +2 -1
  454. package/dist/server/auth.d.ts +52 -0
  455. package/dist/server/auth.js +221 -0
  456. package/dist/server/client-ip.d.ts +25 -0
  457. package/dist/server/client-ip.js +92 -0
  458. package/dist/server/cursor.d.ts +23 -0
  459. package/dist/server/cursor.js +58 -0
  460. package/dist/server/lifecycle.d.ts +7 -0
  461. package/dist/server/lifecycle.js +29 -0
  462. package/dist/server/mcp-http.d.ts +5 -0
  463. package/dist/server/mcp-http.js +199 -0
  464. package/dist/server/request.d.ts +33 -0
  465. package/dist/server/request.js +103 -0
  466. package/dist/server/routes/admin.d.ts +9 -0
  467. package/dist/server/routes/admin.js +158 -0
  468. package/dist/server/routes/customer-notes.d.ts +7 -0
  469. package/dist/server/routes/customer-notes.js +112 -0
  470. package/dist/server/routes/decisions.d.ts +7 -0
  471. package/dist/server/routes/decisions.js +133 -0
  472. package/dist/server/routes/incidents.d.ts +7 -0
  473. package/dist/server/routes/incidents.js +126 -0
  474. package/dist/server/routes/memories.d.ts +10 -0
  475. package/dist/server/routes/memories.js +179 -0
  476. package/dist/server/routes/policies.d.ts +8 -0
  477. package/dist/server/routes/policies.js +151 -0
  478. package/dist/server/routes/predictions.d.ts +7 -0
  479. package/dist/server/routes/predictions.js +164 -0
  480. package/dist/server/routes/processes.d.ts +7 -0
  481. package/dist/server/routes/processes.js +161 -0
  482. package/dist/server/routes/project-briefs.d.ts +8 -0
  483. package/dist/server/routes/project-briefs.js +136 -0
  484. package/dist/server/routes/recall.d.ts +8 -0
  485. package/dist/server/routes/recall.js +340 -0
  486. package/dist/server/routes/skills.d.ts +8 -0
  487. package/dist/server/routes/skills.js +150 -0
  488. package/dist/server/types.d.ts +56 -0
  489. package/dist/server/types.js +2 -0
  490. package/dist/server/validation.d.ts +14 -0
  491. package/dist/server/validation.js +91 -0
  492. package/dist/server.d.ts +7 -61
  493. package/dist/server.js +69 -2362
  494. package/dist/session-digest.d.ts +1 -1
  495. package/dist/session-digest.js +9 -2
  496. package/dist/shared.d.ts +3 -1
  497. package/dist/shared.js +26 -22
  498. package/dist/skills.d.ts +3 -0
  499. package/dist/skills.js +7 -5
  500. package/dist/stdin.js +2 -2
  501. package/dist/store/audit-event.d.ts +19 -0
  502. package/dist/store/audit-event.js +33 -0
  503. package/dist/store/candidates.d.ts +42 -0
  504. package/dist/store/candidates.js +152 -0
  505. package/dist/store/conflicts.d.ts +44 -0
  506. package/dist/store/conflicts.js +444 -0
  507. package/dist/store/delete-and-batch.d.ts +63 -0
  508. package/dist/store/delete-and-batch.js +310 -0
  509. package/dist/store/entry-reads.d.ts +92 -0
  510. package/dist/store/entry-reads.js +255 -0
  511. package/dist/store/entry-row.d.ts +67 -0
  512. package/dist/store/entry-row.js +208 -0
  513. package/dist/store/entry-writes.d.ts +46 -0
  514. package/dist/store/entry-writes.js +148 -0
  515. package/dist/store/handoffs.d.ts +26 -0
  516. package/dist/store/handoffs.js +184 -0
  517. package/dist/store/index-and-stats.d.ts +52 -0
  518. package/dist/store/index-and-stats.js +214 -0
  519. package/dist/store/markdown.d.ts +10 -0
  520. package/dist/store/markdown.js +108 -0
  521. package/dist/store/mirrors.d.ts +49 -0
  522. package/dist/store/mirrors.js +312 -0
  523. package/dist/store/open.d.ts +15 -0
  524. package/dist/store/open.js +223 -0
  525. package/dist/store/rows.d.ts +178 -0
  526. package/dist/store/rows.js +158 -0
  527. package/dist/store/search-rows.d.ts +86 -0
  528. package/dist/store/search-rows.js +254 -0
  529. package/dist/store/sessions.d.ts +83 -0
  530. package/dist/store/sessions.js +272 -0
  531. package/dist/store/summaries.d.ts +94 -0
  532. package/dist/store/summaries.js +377 -0
  533. package/dist/store/tenant-lookup.d.ts +12 -0
  534. package/dist/store/tenant-lookup.js +16 -0
  535. package/dist/store-cards.js +2 -1
  536. package/dist/summary-dirty.d.ts +4 -0
  537. package/dist/summary-dirty.js +32 -0
  538. package/dist/support-bundle.js +4 -3
  539. package/dist/tenant.js +2 -2
  540. package/dist/tokenize.d.ts +2 -0
  541. package/dist/tokenize.js +16 -0
  542. package/dist/transcript-tail.d.ts +7 -0
  543. package/dist/transcript-tail.js +48 -0
  544. package/dist/vector-store.d.ts +27 -0
  545. package/dist/vector-store.js +210 -0
  546. package/dist/version.d.ts +2 -2
  547. package/dist/version.js +2 -2
  548. package/dist/working-memory.js +1 -1
  549. package/dist/yaml.js +36 -11
  550. package/dist-ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  551. package/dist-ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  552. package/dist-ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  553. package/dist-ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  554. package/dist-ui/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
  555. package/dist-ui/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
  556. package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  557. package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  558. package/dist-ui/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  559. package/dist-ui/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  560. package/dist-ui/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  561. package/dist-ui/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  562. package/dist-ui/assets/index-DPN7cP19.js +33 -0
  563. package/dist-ui/assets/index-dFloKRVr.css +1 -0
  564. package/dist-ui/index.html +3 -25
  565. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  566. package/extensions/openclaw-plugin/package.json +1 -1
  567. package/openclaw.plugin.json +1 -1
  568. package/package.json +1 -1
  569. package/dist/capture.d.ts +0 -155
  570. package/dist/capture.js +0 -1295
  571. package/dist/consolidate.d.ts +0 -58
  572. package/dist/consolidate.js +0 -1124
  573. package/dist/graph.d.ts +0 -245
  574. package/dist/hooks.d.ts +0 -208
  575. package/dist/hooks.js +0 -1076
  576. package/dist/importers.js +0 -900
  577. package/dist/predictions.js +0 -620
  578. package/dist/search.d.ts +0 -320
  579. package/dist/search.js +0 -970
  580. package/dist/store.d.ts +0 -776
  581. package/dist/store.js +0 -3473
  582. package/dist-ui/assets/d3-BiWEKnn4.js +0 -1
  583. package/dist-ui/assets/index-BhT8RvO6.js +0 -61
  584. package/dist-ui/assets/index-RoXXJ5dq.css +0 -1
  585. package/dist-ui/assets/three-BDgTxR1l.js +0 -4112
package/dist/dashboard.js CHANGED
@@ -1,124 +1,19 @@
1
- /**
2
- * Hippo Dashboard — local web UI for memory health visualization.
3
- * Serves a single-page app with memory stats, decay curves, and conflict status.
4
- *
5
- * Usage: hippo dashboard [--port 3333]
6
- */
1
+ /** Hippo dashboard server: the React bundle plus the JSON API behind the Health view and the Board (`hippo dashboard --port`). */
7
2
  import * as http from 'http';
8
- import * as os from 'os';
9
3
  import * as path from 'path';
10
4
  import * as fs from 'fs';
11
- import { extractPathTags } from './path-context.js';
12
- import { loadAllEntries, listMemoryConflicts, readEntry, writeEntry } from './store.js';
5
+ import { randomBytes, timingSafeEqual } from 'crypto';
6
+ import { evalNow } from './ablation.js';
7
+ import { readEntry } from './store/entry-reads.js';
13
8
  import { listCards } from './store-cards.js';
14
- import { calculateStrength, confidenceFacets } from './memory.js';
15
- import { loadConfig } from './config.js';
16
- import { listPeers } from './shared.js';
17
- import { loadEmbeddingIndex } from './embeddings.js';
18
9
  import { resolveTenantId } from './tenant.js';
19
10
  import { loadCardDetail } from './card-detail.js';
20
- import { isCrossSite, LOOPBACK_HOST_HEADER } from './server.js';
11
+ import { isCrossSite, LOOPBACK_HOST_HEADER } from './http-util.js';
21
12
  import { log } from './log.js';
22
- function buildDashboardData(hippoRoot) {
23
- // A5: scope dashboard to active tenant. Without this, the UI surfaces every
24
- // tenant's memories on a multi-tenant deployment.
25
- const tenantId = resolveTenantId({});
26
- const entries = loadAllEntries(hippoRoot, tenantId);
27
- const now = new Date();
28
- const config = loadConfig(hippoRoot);
29
- // v0.28 — fetch ALL conflict statuses (open + resolved) so the UI can
30
- // render resolved conflicts as faded historical context, not just open
31
- // conflicts. The open_conflicts stat below still counts only 'open' rows
32
- // to preserve the existing badge meaning.
33
- const conflicts = listMemoryConflicts(hippoRoot, '*', tenantId);
34
- // D4 v1.12.10: tenant-scope peer discovery in the dashboard (matches the
35
- // tenantId already used for loadAllEntries on line 72).
36
- const peers = listPeers(undefined, tenantId);
37
- const embeddingIndex = loadEmbeddingIndex(hippoRoot);
38
- const embeddedCount = Object.keys(embeddingIndex).length;
39
- let totalStrength = 0;
40
- let totalHalfLife = 0;
41
- let atRisk = 0;
42
- let agedOut = 0;
43
- const byLayer = {};
44
- const byConfidence = {};
45
- const memories = entries.map((e) => {
46
- const strength = calculateStrength(e, now);
47
- const facets = confidenceFacets(e, now);
48
- const confidence = facets.tier;
49
- const ageDays = (now.getTime() - new Date(e.created).getTime()) / (1000 * 60 * 60 * 24);
50
- totalStrength += strength;
51
- totalHalfLife += e.half_life_days;
52
- if (strength < 0.1 && !e.pinned)
53
- atRisk++;
54
- byLayer[e.layer] = (byLayer[e.layer] ?? 0) + 1;
55
- byConfidence[confidence] = (byConfidence[confidence] ?? 0) + 1;
56
- if (facets.agedOut)
57
- agedOut++;
58
- // Project strength at +7d and +30d
59
- const future7 = { ...e, last_retrieved: e.last_retrieved };
60
- const future30 = { ...e, last_retrieved: e.last_retrieved };
61
- const in7d = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
62
- const in30d = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
63
- return {
64
- id: e.id,
65
- content: e.content,
66
- tags: e.tags,
67
- layer: e.layer,
68
- strength,
69
- half_life_days: e.half_life_days,
70
- retrieval_count: e.retrieval_count,
71
- schema_fit: e.schema_fit,
72
- emotional_valence: e.emotional_valence,
73
- confidence,
74
- aged_out: facets.agedOut,
75
- pinned: e.pinned,
76
- created: e.created,
77
- last_retrieved: e.last_retrieved,
78
- age_days: Math.round(ageDays * 10) / 10,
79
- projected_strength_7d: calculateStrength(future7, in7d),
80
- projected_strength_30d: calculateStrength(future30, in30d),
81
- parents: e.parents ?? [],
82
- starred: Boolean(e.starred),
83
- conflicts_with: e.conflicts_with ?? [],
84
- };
85
- });
86
- return {
87
- memories,
88
- conflicts: conflicts.map((c) => ({
89
- id: c.id,
90
- memory_a_id: c.memory_a_id,
91
- memory_b_id: c.memory_b_id,
92
- reason: c.reason,
93
- score: c.score,
94
- status: c.status,
95
- })),
96
- stats: {
97
- total: entries.length,
98
- pinned: entries.filter((e) => e.pinned).length,
99
- errors: entries.filter((e) => e.tags.includes('error')).length,
100
- at_risk: atRisk,
101
- avg_strength: entries.length > 0 ? totalStrength / entries.length : 0,
102
- avg_half_life: entries.length > 0 ? totalHalfLife / entries.length : 0,
103
- by_layer: byLayer,
104
- by_confidence: byConfidence,
105
- aged_out: agedOut,
106
- embedding_coverage: entries.length > 0 ? embeddedCount / entries.length : 0,
107
- // v0.28 — open_conflicts must count only 'open' rows now that
108
- // `conflicts` includes all statuses. Preserves the existing badge
109
- // meaning (plan-eng-critic R1 must-fix #2).
110
- open_conflicts: conflicts.filter((c) => c.status === 'open').length,
111
- },
112
- peers,
113
- config: {
114
- defaultHalfLifeDays: config.defaultHalfLifeDays,
115
- defaultBudget: config.defaultBudget,
116
- embeddingsEnabled: config.embeddings.enabled,
117
- // Every memory captured in the home folder carries these, so they name no project.
118
- homePathTags: extractPathTags(os.homedir()),
119
- },
120
- };
121
- }
13
+ import { createSnapshotService, isLiveMemory } from './dashboard-snapshot.js';
14
+ import { buildMemoryDetail, buildMemoryPage, buildOverview, buildProjectDetail, buildSearch, } from './dashboard-queries.js';
15
+ import { forgetMemory, markWrong, pinMemory, resolveOpenConflict, } from './dashboard-actions.js';
16
+ import { ParamError, parseActionBody, parseConflictId, parseMemoryId, parseMemoryQuery, parseSearchText, } from './dashboard-params.js';
122
17
  const MIME_TYPES = {
123
18
  '.html': 'text/html; charset=utf-8',
124
19
  '.js': 'application/javascript; charset=utf-8',
@@ -127,8 +22,13 @@ const MIME_TYPES = {
127
22
  '.png': 'image/png',
128
23
  '.svg': 'image/svg+xml',
129
24
  '.ico': 'image/x-icon',
25
+ '.woff': 'font/woff',
130
26
  '.woff2': 'font/woff2',
131
27
  };
28
+ const BODY_MAX_BYTES = 4096;
29
+ const BODY_DRAIN_MAX_BYTES = 64 * 1024;
30
+ const JSON_CONTENT_TYPE = /^application\/json\s*(;|$)/i;
31
+ const CARD_ID = /^[A-Za-z0-9_-]+$/;
132
32
  function isStaticFileExtension(ext) {
133
33
  return Object.hasOwn(MIME_TYPES, ext);
134
34
  }
@@ -139,6 +39,13 @@ function jsonResponse(res, data, status = 200) {
139
39
  });
140
40
  res.end(body);
141
41
  }
42
+ function forbidden(res) {
43
+ res.writeHead(403, { 'Content-Type': 'text/plain' });
44
+ res.end('Forbidden');
45
+ }
46
+ function notFoundJson(res) {
47
+ jsonResponse(res, { error: 'Not found' }, 404);
48
+ }
142
49
  function serveStaticFile(res, filePath) {
143
50
  const ext = path.extname(filePath).toLowerCase();
144
51
  if (!isStaticFileExtension(ext))
@@ -156,87 +63,129 @@ function serveStaticFile(res, filePath) {
156
63
  return false; // an unreadable asset falls through to the next route, which answers 404 or the fallback page
157
64
  }
158
65
  }
159
- export function serveDashboard(hippoRoot, port = 3333) {
160
- const distUiDir = path.resolve(import.meta.dirname, '..', 'dist-ui');
161
- const hasDistUi = fs.existsSync(path.join(distUiDir, 'index.html'));
162
- const handleRequest = (req, res) => {
163
- const host = req.headers.host;
164
- if (host !== undefined && !LOOPBACK_HOST_HEADER.test(host)) {
165
- res.writeHead(403, { 'Content-Type': 'text/plain' });
166
- res.end('Forbidden');
167
- return;
168
- }
169
- const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
170
- const pathname = url.pathname;
171
- // --- API routes ---
172
- if (pathname.startsWith('/api/')) {
173
- // POST /api/star/:id - toggle starred on a memory
174
- const starMatch = pathname.match(/^\/api\/star\/([A-Za-z0-9_\-]+)$/);
175
- if (starMatch && req.method === 'POST') {
176
- if (isCrossSite(req)) {
177
- res.writeHead(403, { 'Content-Type': 'text/plain' });
178
- res.end('Forbidden');
179
- return;
180
- }
181
- const id = starMatch[1];
182
- const entry = readEntry(hippoRoot, id, resolveTenantId({}));
183
- if (!entry)
184
- return jsonResponse(res, { error: 'Not found' }, 404);
185
- entry.starred = !entry.starred;
186
- writeEntry(hippoRoot, entry);
187
- return jsonResponse(res, { id, starred: entry.starred });
188
- }
189
- if (pathname === '/api/cards' && req.method === 'GET') {
190
- return jsonResponse(res, { cards: listCards(hippoRoot, resolveTenantId({})) });
191
- }
192
- const cardMatch = pathname.match(/^\/api\/cards\/([A-Za-z0-9_-]+)$/);
193
- if (cardMatch && req.method === 'GET') {
194
- const detail = loadCardDetail(hippoRoot, resolveTenantId({}), cardMatch[1]);
195
- return detail ? jsonResponse(res, detail) : jsonResponse(res, { error: 'Not found' }, 404);
196
- }
197
- const data = buildDashboardData(hippoRoot);
198
- if (pathname === '/api/memories')
199
- return jsonResponse(res, data.memories);
200
- if (pathname === '/api/stats')
201
- return jsonResponse(res, data.stats);
202
- if (pathname === '/api/conflicts')
203
- return jsonResponse(res, data.conflicts);
204
- if (pathname === '/api/peers')
205
- return jsonResponse(res, data.peers);
206
- if (pathname === '/api/config')
207
- return jsonResponse(res, data.config);
208
- if (pathname === '/api/embeddings') {
209
- const index = loadEmbeddingIndex(hippoRoot);
210
- return jsonResponse(res, index);
66
+ /** Raised once a body passes the drain ceiling; the caller answers 400 and drops the socket. */
67
+ class BodyDrainExceeded extends ParamError {
68
+ }
69
+ // Past the cap it keeps draining so the socket stays usable, up to a ceiling, then refuses.
70
+ function readActionBody(req) {
71
+ return new Promise((resolve, reject) => {
72
+ const chunks = [];
73
+ let size = 0;
74
+ let aborted = false;
75
+ req.on('data', (chunk) => {
76
+ size += chunk.length;
77
+ if (size <= BODY_MAX_BYTES)
78
+ chunks.push(chunk);
79
+ if (size > BODY_DRAIN_MAX_BYTES && !aborted) {
80
+ aborted = true;
81
+ reject(new BodyDrainExceeded(`Body must be at most ${BODY_DRAIN_MAX_BYTES} bytes`));
211
82
  }
212
- return jsonResponse(res, { error: 'Not found' }, 404);
213
- }
214
- // --- Static file serving from dist-ui/ ---
215
- if (hasDistUi) {
216
- // Normalize and prevent path traversal
217
- const safePath = path.normalize(pathname).replace(/^(\.\.[/\\])+/, '');
218
- const filePath = path.join(distUiDir, safePath);
219
- // Block traversal outside dist-ui
220
- if (!filePath.startsWith(distUiDir)) {
221
- res.writeHead(403, { 'Content-Type': 'text/plain' });
222
- res.end('Forbidden');
223
- return;
83
+ });
84
+ req.on('error', reject);
85
+ req.on('end', () => {
86
+ if (size > BODY_MAX_BYTES)
87
+ return reject(new ParamError(`Body must be at most ${BODY_MAX_BYTES} bytes`));
88
+ try {
89
+ resolve(parseActionBody(Buffer.concat(chunks).toString('utf8').trim()));
224
90
  }
225
- // Try exact file
226
- if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
227
- if (serveStaticFile(res, filePath))
228
- return;
91
+ catch (err) {
92
+ reject(err);
229
93
  }
230
- // SPA fallback: serve index.html for non-file routes
231
- const indexPath = path.join(distUiDir, 'index.html');
232
- if (serveStaticFile(res, indexPath))
233
- return;
234
- }
235
- // --- SPA not built: prompt to build (the dashboardHTML SSR fallback
236
- // was removed in the hybrid-v4 revamp E1 since dist-ui/ is now the
237
- // sole UI surface). ---
238
- res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });
239
- res.end(`<!doctype html>
94
+ });
95
+ });
96
+ }
97
+ function sendOrNotFound(res, body) {
98
+ if (body === null)
99
+ notFoundJson(res);
100
+ else
101
+ jsonResponse(res, body);
102
+ return true;
103
+ }
104
+ function handleRead(ctx, segments) {
105
+ const { hippoRoot, snapshots, req, res, url } = ctx;
106
+ const tenantId = resolveTenantId({});
107
+ // A cross-site <img src=...?fresh=1> must not force a full rebuild.
108
+ const fresh = url.searchParams.get('fresh') === '1' && !isCrossSite(req);
109
+ const [, head, a, b] = segments;
110
+ if (head === 'overview' && segments.length === 2) {
111
+ return sendOrNotFound(res, buildOverview(snapshots.get(tenantId, fresh)));
112
+ }
113
+ if (head === 'projects' && a !== undefined && segments.length === 4 && b === 'memories') {
114
+ const query = parseMemoryQuery(url.searchParams);
115
+ return sendOrNotFound(res, buildMemoryPage(snapshots.get(tenantId, fresh), a, query));
116
+ }
117
+ if (head === 'projects' && a !== undefined && segments.length === 3) {
118
+ return sendOrNotFound(res, buildProjectDetail(snapshots.get(tenantId, fresh), a));
119
+ }
120
+ if (head === 'memory' && a !== undefined && segments.length === 3) {
121
+ const entry = readEntry(hippoRoot, parseMemoryId(a), tenantId);
122
+ if (entry === null || !isLiveMemory(entry))
123
+ return sendOrNotFound(res, null);
124
+ const snapshotId = snapshots.get(tenantId, fresh).id;
125
+ const embedded = snapshots.embeddedIds()?.has(entry.id) ?? false;
126
+ return sendOrNotFound(res, buildMemoryDetail(hippoRoot, tenantId, entry, snapshotId, ctx.now(), embedded));
127
+ }
128
+ if (head === 'search' && segments.length === 2) {
129
+ return sendOrNotFound(res, buildSearch(snapshots.get(tenantId, fresh), parseSearchText(url.searchParams)));
130
+ }
131
+ if (head === 'cards' && segments.length === 2) {
132
+ return sendOrNotFound(res, { cards: listCards(hippoRoot, tenantId) });
133
+ }
134
+ if (head === 'cards' && a !== undefined && segments.length === 3) {
135
+ // The Board has always answered 404, not 400, for an id that does not fit the pattern.
136
+ return sendOrNotFound(res, CARD_ID.test(a) ? loadCardDetail(hippoRoot, tenantId, a) ?? null : null);
137
+ }
138
+ return false;
139
+ }
140
+ // Names the action only; nothing runs until the cross-site, content-type and body guards pass.
141
+ function matchAction(segments) {
142
+ const [, head, id, verb] = segments;
143
+ if (segments.length !== 4 || id === undefined)
144
+ return null;
145
+ if (head === 'memory' && verb === 'pin')
146
+ return (root, tenant, body) => pinMemory(root, tenant, id, body);
147
+ if (head === 'memory' && verb === 'wrong')
148
+ return (root, tenant) => markWrong(root, tenant, id);
149
+ if (head === 'memory' && verb === 'forget')
150
+ return (root, tenant) => forgetMemory(root, tenant, id);
151
+ if (head === 'conflicts' && verb === 'resolve') {
152
+ return (root, tenant, body) => resolveOpenConflict(root, tenant, parseConflictId(id), body);
153
+ }
154
+ return null;
155
+ }
156
+ async function handlePost(ctx, segments) {
157
+ const action = matchAction(segments);
158
+ if (action === null)
159
+ return false;
160
+ const { req, res, hippoRoot, snapshots } = ctx;
161
+ if (isCrossSite(req)) {
162
+ forbidden(res);
163
+ return true;
164
+ }
165
+ if (!JSON_CONTENT_TYPE.test(req.headers['content-type'] ?? '')) {
166
+ jsonResponse(res, { error: 'Content-Type must be application/json' }, 415);
167
+ req.resume();
168
+ return true;
169
+ }
170
+ const body = await readActionBody(req);
171
+ const tenantId = resolveTenantId({});
172
+ const result = action(hippoRoot, tenantId, body);
173
+ if (result.changed)
174
+ snapshots.invalidate();
175
+ if (result.entry) {
176
+ // The write is committed, so a rebuild failure must not turn it into a 500: the next read rebuilds.
177
+ const embedded = snapshots.embeddedIds()?.has(result.entry.id) ?? false;
178
+ const detail = buildMemoryDetail(hippoRoot, tenantId, result.entry, snapshots.currentId(), ctx.now(), embedded);
179
+ jsonResponse(res, detail, result.status);
180
+ }
181
+ else {
182
+ jsonResponse(res, result.body, result.status);
183
+ }
184
+ return true;
185
+ }
186
+ function notBuilt(res) {
187
+ res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });
188
+ res.end(`<!doctype html>
240
189
  <html lang="en"><head><title>Hippo Dashboard</title><meta charset="utf-8"></head>
241
190
  <body style="font-family:Georgia,'Palatino Linotype',serif;max-width:640px;margin:60px auto;padding:24px;line-height:1.6;background:#f4efe6;color:#3a3228">
242
191
  <h1 style="color:#c45c3c">Hippo Dashboard</h1>
@@ -244,22 +193,99 @@ export function serveDashboard(hippoRoot, port = 3333) {
244
193
  <pre style="background:#faf7f2;padding:16px;border:1px solid #c4b9a8;border-radius:3px;font-family:Consolas,monospace">cd ui && npm install && npm run build</pre>
245
194
  <p>Then refresh this page. The dashboard server will serve <code>dist-ui/index.html</code> automatically once present.</p>
246
195
  </body></html>`);
196
+ }
197
+ function serveSpa(res, distUiDir, pathname) {
198
+ const safePath = path.normalize(pathname).replace(/^(\.\.[/\\])+/, '');
199
+ const filePath = path.join(distUiDir, safePath);
200
+ if (!filePath.startsWith(distUiDir))
201
+ return forbidden(res);
202
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile() && serveStaticFile(res, filePath))
203
+ return;
204
+ // SPA fallback: non-file routes get index.html.
205
+ if (serveStaticFile(res, path.join(distUiDir, 'index.html')))
206
+ return;
207
+ notBuilt(res);
208
+ }
209
+ function sameToken(given, token) {
210
+ if (given === undefined)
211
+ return false;
212
+ const a = Buffer.from(given);
213
+ const b = Buffer.from(token);
214
+ return a.length === b.length && timingSafeEqual(a, b);
215
+ }
216
+ function cookieValue(header, name) {
217
+ for (const part of (header ?? '').split(';')) {
218
+ const eq = part.indexOf('=');
219
+ if (eq > 0 && part.slice(0, eq).trim() === name)
220
+ return part.slice(eq + 1).trim();
221
+ }
222
+ return undefined;
223
+ }
224
+ /** Serves the dashboard on 127.0.0.1 behind a per-start `token` (tests pass one), since loopback alone lets any local process read every memory; `opts` sets the projection and cache clocks. */
225
+ export function serveDashboard(hippoRoot, port = 3333, token = randomBytes(32).toString('base64url'), opts) {
226
+ const distUiDir = path.resolve(import.meta.dirname, '..', 'dist-ui');
227
+ const hasDistUi = fs.existsSync(path.join(distUiDir, 'index.html'));
228
+ const now = opts?.now ?? (() => evalNow().getTime());
229
+ const snapshots = createSnapshotService(hippoRoot, now, opts?.cacheClock);
230
+ const handleRequest = async (req, res) => {
231
+ const host = req.headers.host;
232
+ if (host !== undefined && !LOOPBACK_HOST_HEADER.test(host))
233
+ return forbidden(res);
234
+ const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
235
+ // Cookies ignore the port, so the name carries it: two dashboards on one host keep separate tokens.
236
+ const cookieName = `hippo_dashboard_${req.socket.localPort ?? port}`;
237
+ if (sameToken(url.searchParams.get('token') ?? undefined, token)) {
238
+ res.setHeader('Set-Cookie', `${cookieName}=${token}; HttpOnly; SameSite=Strict; Path=/`);
239
+ }
240
+ else if (!sameToken(cookieValue(req.headers.cookie, cookieName), token)) {
241
+ res.writeHead(401, { 'Content-Type': 'text/plain' });
242
+ res.end('Unauthorized: open the dashboard with the URL `hippo dashboard` printed; it carries the access token.');
243
+ return;
244
+ }
245
+ if (url.pathname.startsWith('/api/')) {
246
+ // A malformed % sequence throws URIError here; the caller answers 400.
247
+ const segments = url.pathname.split('/').filter((s) => s !== '').map((s) => decodeURIComponent(s));
248
+ const ctx = { hippoRoot, snapshots, now, req, res, url };
249
+ const handled = req.method === 'GET' ? handleRead(ctx, segments)
250
+ : req.method === 'POST' ? await handlePost(ctx, segments)
251
+ : false;
252
+ if (!handled)
253
+ notFoundJson(res);
254
+ return;
255
+ }
256
+ if (hasDistUi)
257
+ return serveSpa(res, distUiDir, url.pathname);
258
+ notBuilt(res);
247
259
  };
248
260
  const server = http.createServer((req, res) => {
249
- try {
250
- handleRequest(req, res);
251
- }
252
- catch (err) {
253
- log.error(`dashboard request failed: ${err instanceof Error ? err.message : String(err)}`);
254
- if (res.headersSent)
261
+ handleRequest(req, res).catch((err) => {
262
+ const clientFault = err instanceof ParamError || err instanceof URIError;
263
+ // A cut-short response is logged even for a client fault; a bare 400 is not.
264
+ if (res.headersSent || !clientFault) {
265
+ log.error('dashboard request failed', { error: err instanceof Error ? err.message : String(err), path: req.url });
266
+ }
267
+ if (res.headersSent) {
255
268
  res.end();
256
- else
257
- jsonResponse(res, { error: 'Internal error' }, 500);
258
- }
269
+ return;
270
+ }
271
+ if (err instanceof BodyDrainExceeded) {
272
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8', Connection: 'close' });
273
+ res.end(JSON.stringify({ error: err.message }), () => req.destroy());
274
+ return;
275
+ }
276
+ if (err instanceof ParamError)
277
+ return jsonResponse(res, { error: err.message }, 400);
278
+ if (err instanceof URIError)
279
+ return jsonResponse(res, { error: 'Malformed URL path' }, 400);
280
+ jsonResponse(res, { error: 'Internal error' }, 500);
281
+ });
259
282
  });
283
+ server.on('close', () => snapshots.close());
260
284
  server.listen(port, '127.0.0.1', () => {
285
+ // SAFETY: listen() was given a TCP port, so address() is AddressInfo, never a pipe name.
286
+ const boundPort = server.address().port;
261
287
  // The banner is the `hippo dashboard` command's printed result, so it stays on stdout.
262
- console.log(`Hippo Dashboard running at http://localhost:${port}`);
288
+ console.log(`Hippo Dashboard running at http://localhost:${boundPort}/?token=${token}`);
263
289
  if (hasDistUi)
264
290
  console.log(`Serving React UI from ${distUiDir}`);
265
291
  console.log('Press Ctrl+C to stop.');
@@ -0,0 +1,5 @@
1
+ import type { DatabaseSyncLike } from './sqlite.js';
2
+ export declare function isSqliteBusy(error: unknown): boolean;
3
+ export declare const STORE_BUSY_MESSAGE = "store busy (another hippo process holds the write lock); retry shortly";
4
+ export declare function execWithBusyRetry(db: DatabaseSyncLike, sql: string, timeoutMs?: number): void;
5
+ //# sourceMappingURL=busy.d.ts.map
@@ -0,0 +1,24 @@
1
+ export function isSqliteBusy(error) {
2
+ const code = error?.errcode;
3
+ return code === 5 || code === 6 || code === 517;
4
+ }
5
+ export const STORE_BUSY_MESSAGE = 'store busy (another hippo process holds the write lock); retry shortly';
6
+ // busy_timeout covers neither of this file's two contended statements: SQLite
7
+ // skips the busy handler for `PRAGMA journal_mode` and for a write that upgrades
8
+ // a deferred read snapshot. Both need an explicit wait instead.
9
+ export function execWithBusyRetry(db, sql, timeoutMs = 30000) {
10
+ const deadline = Date.now() + timeoutMs;
11
+ const idle = new Int32Array(new SharedArrayBuffer(4));
12
+ for (;;) {
13
+ try {
14
+ db.exec(sql);
15
+ return;
16
+ }
17
+ catch (error) {
18
+ if (!isSqliteBusy(error) || Date.now() >= deadline)
19
+ throw error;
20
+ Atomics.wait(idle, 0, 0, 10 + Math.floor(Math.random() * 20));
21
+ }
22
+ }
23
+ }
24
+ //# sourceMappingURL=busy.js.map
@@ -0,0 +1,5 @@
1
+ import type { DatabaseSyncLike } from './sqlite.js';
2
+ export declare const MEMORY_QUARANTINE_DDL = "\n CREATE TABLE IF NOT EXISTS memory_quarantine (\n tenant_id TEXT NOT NULL DEFAULT 'default',\n memory_id TEXT NOT NULL,\n original_scope TEXT,\n reason TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','approved','rejected')),\n quarantined_at TEXT NOT NULL,\n decided_at TEXT,\n decided_by TEXT,\n PRIMARY KEY (tenant_id, memory_id)\n ) WITHOUT ROWID;\n CREATE INDEX IF NOT EXISTS idx_memory_quarantine_status\n ON memory_quarantine(tenant_id, status, quarantined_at DESC);\n";
3
+ export declare function ensureContinuityTables(db: DatabaseSyncLike): void;
4
+ export declare function ensureContinuityIndexes(db: DatabaseSyncLike): void;
5
+ //# sourceMappingURL=continuity.d.ts.map
@@ -0,0 +1,145 @@
1
+ // Shared by migration v48 and the stamped-store self-heal below.
2
+ export const MEMORY_QUARANTINE_DDL = `
3
+ CREATE TABLE IF NOT EXISTS memory_quarantine (
4
+ tenant_id TEXT NOT NULL DEFAULT 'default',
5
+ memory_id TEXT NOT NULL,
6
+ original_scope TEXT,
7
+ reason TEXT NOT NULL,
8
+ status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','approved','rejected')),
9
+ quarantined_at TEXT NOT NULL,
10
+ decided_at TEXT,
11
+ decided_by TEXT,
12
+ PRIMARY KEY (tenant_id, memory_id)
13
+ ) WITHOUT ROWID;
14
+ CREATE INDEX IF NOT EXISTS idx_memory_quarantine_status
15
+ ON memory_quarantine(tenant_id, status, quarantined_at DESC);
16
+ `;
17
+ const TASK_SNAPSHOTS_DDL = `
18
+ CREATE TABLE IF NOT EXISTS task_snapshots (
19
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
20
+ task TEXT NOT NULL,
21
+ summary TEXT NOT NULL,
22
+ next_step TEXT NOT NULL,
23
+ status TEXT NOT NULL,
24
+ source TEXT NOT NULL,
25
+ created_at TEXT NOT NULL,
26
+ updated_at TEXT NOT NULL,
27
+ session_id TEXT,
28
+ tenant_id TEXT NOT NULL DEFAULT 'default',
29
+ scope TEXT
30
+ )
31
+ `;
32
+ const SESSION_EVENTS_DDL = `
33
+ CREATE TABLE IF NOT EXISTS session_events (
34
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
35
+ session_id TEXT NOT NULL,
36
+ task TEXT,
37
+ event_type TEXT NOT NULL,
38
+ content TEXT NOT NULL,
39
+ source TEXT NOT NULL,
40
+ metadata_json TEXT NOT NULL,
41
+ created_at TEXT NOT NULL,
42
+ tenant_id TEXT NOT NULL DEFAULT 'default',
43
+ scope TEXT
44
+ )
45
+ `;
46
+ const SESSION_HANDOFFS_DDL = `
47
+ CREATE TABLE IF NOT EXISTS session_handoffs (
48
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
49
+ session_id TEXT NOT NULL,
50
+ repo_root TEXT,
51
+ task_id TEXT,
52
+ summary TEXT NOT NULL,
53
+ next_action TEXT,
54
+ artifacts_json TEXT NOT NULL DEFAULT '[]',
55
+ created_at TEXT NOT NULL,
56
+ tenant_id TEXT NOT NULL DEFAULT 'default',
57
+ scope TEXT,
58
+ constraints_json TEXT,
59
+ evidence_json TEXT,
60
+ outcome TEXT,
61
+ target_runtime TEXT,
62
+ card_id TEXT
63
+ )
64
+ `;
65
+ const CARDS_DDL = `
66
+ CREATE TABLE IF NOT EXISTS cards (
67
+ id TEXT PRIMARY KEY,
68
+ title TEXT NOT NULL,
69
+ status TEXT NOT NULL DEFAULT 'backlog',
70
+ assignee_runtime TEXT,
71
+ repo TEXT,
72
+ contract TEXT,
73
+ budget INTEGER,
74
+ lease_until TEXT,
75
+ heartbeat_at TEXT,
76
+ created_at TEXT NOT NULL,
77
+ updated_at TEXT NOT NULL,
78
+ tenant_id TEXT NOT NULL DEFAULT 'default',
79
+ scope TEXT
80
+ ) WITHOUT ROWID
81
+ `;
82
+ const CARD_DEPS_DDL = `
83
+ CREATE TABLE IF NOT EXISTS card_deps (
84
+ parent TEXT NOT NULL REFERENCES cards(id),
85
+ child TEXT NOT NULL REFERENCES cards(id),
86
+ tenant_id TEXT NOT NULL DEFAULT 'default',
87
+ created_at TEXT NOT NULL,
88
+ PRIMARY KEY (parent, child)
89
+ ) WITHOUT ROWID
90
+ `;
91
+ const CARD_RUNS_DDL = `
92
+ CREATE TABLE IF NOT EXISTS card_runs (
93
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
94
+ card TEXT NOT NULL REFERENCES cards(id),
95
+ runtime TEXT NOT NULL,
96
+ session_id TEXT,
97
+ started TEXT NOT NULL,
98
+ ended TEXT,
99
+ outcome TEXT,
100
+ created_at TEXT NOT NULL,
101
+ updated_at TEXT NOT NULL,
102
+ tenant_id TEXT NOT NULL DEFAULT 'default'
103
+ )
104
+ `;
105
+ const CARD_COMMENTS_DDL = `
106
+ CREATE TABLE IF NOT EXISTS card_comments (
107
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
108
+ card_id TEXT NOT NULL REFERENCES cards(id),
109
+ author TEXT NOT NULL,
110
+ body TEXT NOT NULL,
111
+ created_at TEXT NOT NULL,
112
+ tenant_id TEXT NOT NULL DEFAULT 'default'
113
+ )
114
+ `;
115
+ // Before the loop on stamped stores: a table lost after its migration stamped (2026-08-15
116
+ // incident) is never re-migrated, and v4/v16/v22 ALTER or read it. Fresh stores use the chain.
117
+ export function ensureContinuityTables(db) {
118
+ db.exec(TASK_SNAPSHOTS_DDL);
119
+ db.exec(SESSION_EVENTS_DDL);
120
+ db.exec(SESSION_HANDOFFS_DDL);
121
+ db.exec(CARDS_DDL);
122
+ db.exec(CARD_DEPS_DDL);
123
+ db.exec(CARD_RUNS_DDL);
124
+ db.exec(CARD_COMMENTS_DDL);
125
+ db.exec(MEMORY_QUARANTINE_DDL);
126
+ }
127
+ // After the loop: tenant_id (v16) and scope (v23) do not exist yet on a genuine old store.
128
+ export function ensureContinuityIndexes(db) {
129
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_task_snapshots_status_updated ON task_snapshots(status, updated_at DESC, id DESC)`);
130
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_task_snapshots_tenant_status ON task_snapshots(tenant_id, status, updated_at DESC)`);
131
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_task_snapshots_tenant_scope ON task_snapshots(tenant_id, scope, status)`);
132
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_session_events_session_created ON session_events(session_id, created_at DESC, id DESC)`);
133
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_session_events_task_created ON session_events(task, created_at DESC, id DESC)`);
134
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_session_events_tenant_session ON session_events(tenant_id, session_id, created_at DESC, id DESC)`);
135
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_session_handoffs_session ON session_handoffs(session_id, created_at DESC)`);
136
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_session_handoffs_tenant_session ON session_handoffs(tenant_id, session_id, created_at DESC)`);
137
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_session_handoffs_tenant_outcome ON session_handoffs(tenant_id, outcome, created_at DESC)`);
138
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_cards_tenant_status ON cards(tenant_id, status, updated_at DESC)`);
139
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_card_deps_tenant_child ON card_deps(tenant_id, child)`);
140
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_card_deps_tenant_parent ON card_deps(tenant_id, parent)`);
141
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_card_runs_tenant_card ON card_runs(tenant_id, card, started DESC)`);
142
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_card_comments_tenant_card ON card_comments(tenant_id, card_id, created_at DESC)`);
143
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_session_handoffs_tenant_card ON session_handoffs(tenant_id, card_id, created_at DESC)`);
144
+ }
145
+ //# sourceMappingURL=continuity.js.map