hippo-memory 1.71.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (625) hide show
  1. package/README.md +7 -5
  2. package/dist/agent-memories/apply.d.ts +0 -11
  3. package/dist/agent-memories/apply.js +11 -26
  4. package/dist/agent-memories/claude-code.js +1 -1
  5. package/dist/agent-memories/codex.js +1 -1
  6. package/dist/agent-memories/copilot.d.ts +1 -3
  7. package/dist/agent-memories/copilot.js +1 -17
  8. package/dist/agent-memories/project-folds.d.ts +10 -0
  9. package/dist/agent-memories/project-folds.js +34 -0
  10. package/dist/agent-memories/sync.js +8 -6
  11. package/dist/api/assemble.js +3 -2
  12. package/dist/api/audit.d.ts +30 -2
  13. package/dist/api/audit.js +48 -7
  14. package/dist/api/auth.d.ts +11 -9
  15. package/dist/api/auth.js +39 -40
  16. package/dist/api/auto-sleep.d.ts +5 -2
  17. package/dist/api/auto-sleep.js +7 -3
  18. package/dist/api/availability.js +2 -3
  19. package/dist/api/cards.d.ts +42 -0
  20. package/dist/api/cards.js +52 -0
  21. package/dist/api/conflicts.d.ts +28 -0
  22. package/dist/api/conflicts.js +61 -0
  23. package/dist/api/context-auto.js +4 -2
  24. package/dist/api/context-render.js +4 -3
  25. package/dist/api/context-select.js +15 -29
  26. package/dist/api/context.js +29 -18
  27. package/dist/api/continuity.d.ts +47 -0
  28. package/dist/api/continuity.js +66 -0
  29. package/dist/api/dormant.js +4 -2
  30. package/dist/api/drill-down.js +3 -2
  31. package/dist/api/embeddings.d.ts +17 -0
  32. package/dist/api/embeddings.js +28 -0
  33. package/dist/api/error-reply.d.ts +8 -0
  34. package/dist/api/error-reply.js +21 -0
  35. package/dist/api/forget.d.ts +7 -3
  36. package/dist/api/forget.js +24 -14
  37. package/dist/api/goals.d.ts +1 -1
  38. package/dist/api/goals.js +5 -9
  39. package/dist/api/graph.js +2 -1
  40. package/dist/api/index.d.ts +4 -2
  41. package/dist/api/index.js +4 -2
  42. package/dist/api/instruction-detect.js +5 -1
  43. package/dist/api/learn.d.ts +5 -1
  44. package/dist/api/learn.js +71 -44
  45. package/dist/api/ledger-db.d.ts +2 -6
  46. package/dist/api/ledger-db.js +4 -36
  47. package/dist/api/memories.d.ts +21 -1
  48. package/dist/api/memories.js +51 -1
  49. package/dist/api/objects.d.ts +1 -1
  50. package/dist/api/objects.js +2 -1
  51. package/dist/api/outcome.d.ts +5 -5
  52. package/dist/api/outcome.js +17 -16
  53. package/dist/api/pilot-arm.d.ts +1 -11
  54. package/dist/api/pilot-arm.js +5 -45
  55. package/dist/api/predictions.d.ts +3 -1
  56. package/dist/api/predictions.js +8 -1
  57. package/dist/api/projects.d.ts +21 -0
  58. package/dist/api/projects.js +24 -0
  59. package/dist/api/promote.d.ts +3 -3
  60. package/dist/api/promote.js +26 -28
  61. package/dist/api/prompt-hook.d.ts +17 -2
  62. package/dist/api/prompt-hook.js +45 -4
  63. package/dist/api/quarantine.js +2 -1
  64. package/dist/api/recall-cli.d.ts +3 -0
  65. package/dist/api/recall-cli.js +13 -4
  66. package/dist/api/recall-core.js +10 -5
  67. package/dist/api/recall-pipeline.js +23 -24
  68. package/dist/api/recall-request.js +1 -1
  69. package/dist/api/recall-types.d.ts +3 -1
  70. package/dist/api/recall.d.ts +0 -3
  71. package/dist/api/recall.js +51 -73
  72. package/dist/api/remember-local.d.ts +5 -2
  73. package/dist/api/remember-local.js +15 -7
  74. package/dist/api/remember.d.ts +7 -3
  75. package/dist/api/remember.js +19 -16
  76. package/dist/api/session-end.d.ts +46 -0
  77. package/dist/api/session-end.js +131 -0
  78. package/dist/api/sleep-run.d.ts +1 -1
  79. package/dist/api/sleep-run.js +16 -11
  80. package/dist/api/sleep.js +4 -3
  81. package/dist/api/status.d.ts +24 -0
  82. package/dist/api/status.js +40 -0
  83. package/dist/api/store-init.d.ts +4 -0
  84. package/dist/api/store-init.js +9 -0
  85. package/dist/api/tokens.js +2 -2
  86. package/dist/api/types.d.ts +3 -7
  87. package/dist/api/types.js +6 -1
  88. package/dist/capture/caller-session.d.ts +0 -3
  89. package/dist/capture/caller-session.js +1 -12
  90. package/dist/capture/capture-error.d.ts +2 -2
  91. package/dist/capture/capture-error.js +23 -18
  92. package/dist/capture/codex-patch.js +3 -1
  93. package/dist/capture/command.js +36 -95
  94. package/dist/capture/compact.js +10 -5
  95. package/dist/capture/compaction-items-caller.js +11 -20
  96. package/dist/capture/compaction-record.d.ts +7 -93
  97. package/dist/capture/compaction-record.js +29 -426
  98. package/dist/capture/compaction-spool.d.ts +7 -7
  99. package/dist/capture/compaction-spool.js +60 -51
  100. package/dist/capture/copilot-transcript.d.ts +2 -1
  101. package/dist/capture/copilot-transcript.js +2 -1
  102. package/dist/capture/extract.d.ts +5 -2
  103. package/dist/capture/extract.js +4 -3
  104. package/dist/capture/failure-caller.d.ts +1 -1
  105. package/dist/capture/failure-caller.js +21 -16
  106. package/dist/capture/failure-reading.d.ts +2 -1
  107. package/dist/capture/failure-reading.js +7 -9
  108. package/dist/capture/pre-compact-caller.js +5 -4
  109. package/dist/capture/session-digest.js +4 -3
  110. package/dist/capture/session-end-caller.js +3 -2
  111. package/dist/capture/session-worker.js +22 -10
  112. package/dist/capture/transcript.d.ts +2 -1
  113. package/dist/capture/transcript.js +6 -3
  114. package/dist/capture/working-state.d.ts +4 -2
  115. package/dist/capture/working-state.js +4 -2
  116. package/dist/cli/audit-prune.d.ts +0 -25
  117. package/dist/cli/audit-prune.js +1 -27
  118. package/dist/cli/audit.js +20 -20
  119. package/dist/cli/auth.d.ts +1 -1
  120. package/dist/cli/auth.js +16 -17
  121. package/dist/cli/briefs.d.ts +1 -4
  122. package/dist/cli/briefs.js +37 -292
  123. package/dist/cli/card.js +41 -33
  124. package/dist/cli/client.d.ts +6 -2
  125. package/dist/cli/client.js +37 -21
  126. package/dist/cli/compact-resume-payload.js +1 -1
  127. package/dist/cli/context.d.ts +1 -1
  128. package/dist/cli/context.js +13 -13
  129. package/dist/cli/continuity.d.ts +1 -1
  130. package/dist/cli/continuity.js +36 -39
  131. package/dist/cli/curate.d.ts +2 -2
  132. package/dist/cli/curate.js +32 -36
  133. package/dist/cli/dag.d.ts +1 -1
  134. package/dist/cli/dag.js +23 -8
  135. package/dist/cli/decisions.d.ts +0 -2
  136. package/dist/cli/decisions.js +7 -285
  137. package/dist/cli/eval.d.ts +1 -1
  138. package/dist/cli/eval.js +12 -13
  139. package/dist/cli/explain.d.ts +1 -1
  140. package/dist/cli/explain.js +21 -25
  141. package/dist/cli/flag-values.js +16 -6
  142. package/dist/cli/flags.d.ts +0 -289
  143. package/dist/cli/flags.js +3 -138
  144. package/dist/cli/graph.d.ts +3 -0
  145. package/dist/cli/graph.js +83 -0
  146. package/dist/cli/hook-runtime.d.ts +6 -6
  147. package/dist/cli/hook-runtime.js +9 -36
  148. package/dist/cli/incidents.d.ts +3 -0
  149. package/dist/cli/incidents.js +112 -0
  150. package/dist/cli/init.d.ts +1 -1
  151. package/dist/cli/init.js +28 -19
  152. package/dist/cli/install-steps.d.ts +2 -1
  153. package/dist/cli/install-steps.js +11 -3
  154. package/dist/cli/last-sleep.js +1 -1
  155. package/dist/cli/maintenance.d.ts +3 -3
  156. package/dist/cli/maintenance.js +29 -30
  157. package/dist/cli/notes.d.ts +2 -0
  158. package/dist/cli/notes.js +41 -0
  159. package/dist/cli/object-verbs.d.ts +2 -2
  160. package/dist/cli/object-verbs.js +4 -4
  161. package/dist/cli/open-browser.d.ts +14 -0
  162. package/dist/cli/open-browser.js +26 -0
  163. package/dist/cli/playbooks.d.ts +3 -4
  164. package/dist/cli/playbooks.js +126 -290
  165. package/dist/cli/predictions.d.ts +3 -0
  166. package/dist/cli/predictions.js +174 -0
  167. package/dist/cli/print.js +2 -1
  168. package/dist/cli/projects.js +30 -22
  169. package/dist/cli/quality-repair-once.d.ts +1 -1
  170. package/dist/cli/quality-repair-once.js +6 -5
  171. package/dist/cli/recall.js +13 -7
  172. package/dist/cli/refine-llm.js +18 -17
  173. package/dist/cli/remember.js +17 -17
  174. package/dist/cli/scheduler.js +2 -1
  175. package/dist/cli/serve.js +11 -6
  176. package/dist/cli/session-hooks.d.ts +1 -5
  177. package/dist/cli/session-hooks.js +89 -168
  178. package/dist/cli/setup.d.ts +1 -1
  179. package/dist/cli/setup.js +17 -10
  180. package/dist/cli/shared.d.ts +2 -6
  181. package/dist/cli/shared.js +9 -29
  182. package/dist/cli/sleep-render.js +6 -3
  183. package/dist/cli/sleep.js +6 -37
  184. package/dist/cli/status.d.ts +3 -3
  185. package/dist/cli/status.js +32 -35
  186. package/dist/cli/transfer.d.ts +3 -3
  187. package/dist/cli/transfer.js +64 -43
  188. package/dist/cli/usage.d.ts +1 -80
  189. package/dist/cli/usage.js +2 -696
  190. package/dist/cli/verb-row.d.ts +34 -0
  191. package/dist/cli/verb-row.js +6 -0
  192. package/dist/cli/verbs/health.d.ts +27 -0
  193. package/dist/cli/verbs/health.js +234 -0
  194. package/dist/cli/verbs/memory.d.ts +16 -0
  195. package/dist/cli/verbs/memory.js +236 -0
  196. package/dist/cli/verbs/objects.d.ts +20 -0
  197. package/dist/cli/verbs/objects.js +257 -0
  198. package/dist/cli/verbs/sessions.d.ts +31 -0
  199. package/dist/cli/verbs/sessions.js +314 -0
  200. package/dist/cli/verbs.d.ts +81 -0
  201. package/dist/cli/verbs.js +7 -0
  202. package/dist/cli/versioned-verbs.d.ts +51 -0
  203. package/dist/cli/versioned-verbs.js +79 -0
  204. package/dist/cli.d.ts +1 -272
  205. package/dist/cli.js +24 -315
  206. package/dist/connectors/dlq.d.ts +39 -3
  207. package/dist/connectors/dlq.js +39 -4
  208. package/dist/connectors/github/backfill.js +12 -3
  209. package/dist/connectors/github/deletion.js +2 -1
  210. package/dist/connectors/github/dlq.js +31 -73
  211. package/dist/connectors/github/ingest.d.ts +2 -6
  212. package/dist/connectors/github/ingest.js +5 -29
  213. package/dist/connectors/github/tenant-routing.js +2 -1
  214. package/dist/connectors/github/webhook.d.ts +2 -1
  215. package/dist/connectors/github/webhook.js +35 -2
  216. package/dist/connectors/ingest.d.ts +10 -0
  217. package/dist/connectors/ingest.js +29 -0
  218. package/dist/connectors/slack/deletion.js +2 -1
  219. package/dist/connectors/slack/dlq.js +49 -71
  220. package/dist/connectors/slack/ingest.d.ts +2 -6
  221. package/dist/connectors/slack/ingest.js +4 -28
  222. package/dist/connectors/slack/tenant-routing.js +2 -1
  223. package/dist/connectors/slack/webhook.d.ts +2 -1
  224. package/dist/connectors/slack/webhook.js +2 -1
  225. package/dist/consolidate/conflicts.js +2 -2
  226. package/dist/consolidate/dag.js +29 -18
  227. package/dist/consolidate/dedupe.d.ts +7 -0
  228. package/dist/consolidate/dedupe.js +6 -4
  229. package/dist/consolidate/llm-passes.js +45 -19
  230. package/dist/consolidate/memory-value.js +3 -2
  231. package/dist/consolidate/merge.js +8 -8
  232. package/dist/consolidate/physics-pass.d.ts +6 -1
  233. package/dist/consolidate/physics-pass.js +26 -6
  234. package/dist/consolidate/replay.js +11 -4
  235. package/dist/consolidate/sleep.js +9 -6
  236. package/dist/consolidate/traces.js +5 -5
  237. package/dist/core/ablation.d.ts +10 -0
  238. package/dist/core/ablation.js +12 -0
  239. package/dist/{sharing/scope.d.ts → core/active-scope.d.ts} +1 -1
  240. package/dist/{sharing/scope.js → core/active-scope.js} +1 -1
  241. package/dist/core/ambient.d.ts +4 -0
  242. package/dist/core/ambient.js +39 -18
  243. package/dist/core/api-errors.d.ts +16 -0
  244. package/dist/core/api-errors.js +20 -0
  245. package/dist/core/card.js +3 -1
  246. package/dist/core/compaction-timing.d.ts +7 -0
  247. package/dist/core/compaction-timing.js +9 -0
  248. package/dist/core/config.d.ts +1 -1
  249. package/dist/core/config.js +22 -18
  250. package/dist/core/cosine.d.ts +3 -0
  251. package/dist/core/cosine.js +19 -0
  252. package/dist/{store → core}/markdown.d.ts +1 -1
  253. package/dist/{store → core}/markdown.js +4 -4
  254. package/dist/core/memory-audit.d.ts +17 -0
  255. package/dist/core/memory-audit.js +54 -0
  256. package/dist/core/memory.d.ts +14 -8
  257. package/dist/core/memory.js +35 -23
  258. package/dist/{consolidate → core}/merged-row.d.ts +1 -1
  259. package/dist/{consolidate → core}/merged-row.js +1 -1
  260. package/dist/core/physics.d.ts +3 -1
  261. package/dist/core/physics.js +23 -18
  262. package/dist/core/prediction.d.ts +3 -0
  263. package/dist/core/prediction.js +6 -0
  264. package/dist/core/project-identity.d.ts +3 -0
  265. package/dist/core/project-identity.js +12 -2
  266. package/dist/{store → core}/recall-scope.d.ts +1 -10
  267. package/dist/{store → core}/recall-scope.js +2 -19
  268. package/dist/core/search-types.d.ts +1 -1
  269. package/dist/dashboard/dashboard-actions.d.ts +2 -2
  270. package/dist/dashboard/dashboard-actions.js +5 -5
  271. package/dist/dashboard/dashboard-queries.js +2 -7
  272. package/dist/dashboard/dashboard-snapshot.js +1 -3
  273. package/dist/dashboard/dashboard.js +16 -5
  274. package/dist/db/busy.d.ts +2 -7
  275. package/dist/db/busy.js +21 -26
  276. package/dist/db/connect.d.ts +2 -2
  277. package/dist/db/connect.js +7 -7
  278. package/dist/db/index.d.ts +3 -3
  279. package/dist/db/index.js +3 -3
  280. package/dist/db/migrate.js +14 -7
  281. package/dist/db/migrations/index.d.ts +1 -1
  282. package/dist/db/migrations/index.js +3 -1
  283. package/dist/db/migrations/v11.js +1 -0
  284. package/dist/db/migrations/v14.js +1 -0
  285. package/dist/db/migrations/v55.d.ts +3 -0
  286. package/dist/db/migrations/v55.js +17 -0
  287. package/dist/db/open.d.ts +0 -2
  288. package/dist/db/open.js +6 -10
  289. package/dist/db/physics-state.d.ts +0 -4
  290. package/dist/db/physics-state.js +7 -33
  291. package/dist/db/request-stores.d.ts +1 -0
  292. package/dist/db/request-stores.js +2 -1
  293. package/dist/db/tables.d.ts +2 -0
  294. package/dist/db/tables.js +6 -2
  295. package/dist/db/vector-store.js +19 -5
  296. package/dist/db/wal-checkpointer.js +2 -1
  297. package/dist/doctor.d.ts +3 -0
  298. package/dist/doctor.js +18 -15
  299. package/dist/embeddings/local.d.ts +62 -0
  300. package/dist/embeddings/local.js +146 -0
  301. package/dist/{store/embeddings → embeddings}/provider.d.ts +5 -2
  302. package/dist/{store/embeddings → embeddings}/provider.js +19 -25
  303. package/dist/embeddings/transformers.d.ts +26 -0
  304. package/dist/embeddings/transformers.js +89 -0
  305. package/dist/entry/session-text.d.ts +1 -1
  306. package/dist/entry/session-text.js +1 -1
  307. package/dist/entry/store-kit.d.ts +37 -0
  308. package/dist/entry/store-kit.js +25 -0
  309. package/dist/entry/testing.d.ts +6 -0
  310. package/dist/entry/testing.js +7 -0
  311. package/dist/eval/eval-suite.js +17 -11
  312. package/dist/eval/eval.js +1 -1
  313. package/dist/eval/judgment.js +12 -5
  314. package/dist/graph/extract.js +3 -3
  315. package/dist/graph/recall.js +1 -1
  316. package/dist/graph/view.js +13 -5
  317. package/dist/graph/write.d.ts +1 -1
  318. package/dist/graph/write.js +9 -9
  319. package/dist/hooks/codex-session.js +3 -2
  320. package/dist/hooks/codex-wrapper.js +2 -1
  321. package/dist/hooks/json-hooks.js +10 -4
  322. package/dist/hooks/opencode.js +8 -5
  323. package/dist/hooks/shared.d.ts +0 -6
  324. package/dist/hooks/shared.js +4 -15
  325. package/dist/importers/core.js +4 -4
  326. package/dist/importers/sources.js +5 -4
  327. package/dist/importers/vault.d.ts +1 -1
  328. package/dist/importers/vault.js +25 -23
  329. package/dist/index.d.ts +5 -4
  330. package/dist/index.js +4 -3
  331. package/dist/learn/autolearn.d.ts +15 -3
  332. package/dist/learn/autolearn.js +38 -16
  333. package/dist/learn/churn-git.js +13 -4
  334. package/dist/learn/extract.d.ts +3 -2
  335. package/dist/learn/extract.js +7 -5
  336. package/dist/learn/forward-claim-detector.js +4 -2
  337. package/dist/learn/invalidation.d.ts +2 -1
  338. package/dist/learn/invalidation.js +18 -9
  339. package/dist/mcp/admin-tools.d.ts +2 -2
  340. package/dist/mcp/admin-tools.js +27 -48
  341. package/dist/mcp/format.js +3 -2
  342. package/dist/mcp/framing.d.ts +1 -0
  343. package/dist/mcp/framing.js +1 -1
  344. package/dist/mcp/memory-tools.d.ts +1 -1
  345. package/dist/mcp/memory-tools.js +8 -9
  346. package/dist/mcp/protocol.d.ts +11 -7
  347. package/dist/mcp/protocol.js +25 -12
  348. package/dist/mcp/recall-tools.js +12 -9
  349. package/dist/mcp/request.js +22 -20
  350. package/dist/mcp/stdio.js +37 -16
  351. package/dist/mcp/tool-args.js +2 -1
  352. package/dist/mcp/tools.js +44 -21
  353. package/dist/objects/customer-notes.d.ts +2 -2
  354. package/dist/objects/decisions.d.ts +2 -2
  355. package/dist/objects/descriptor.d.ts +1 -1
  356. package/dist/objects/incidents.d.ts +2 -2
  357. package/dist/objects/lifecycle.d.ts +1 -1
  358. package/dist/objects/lifecycle.js +2 -1
  359. package/dist/objects/policies.d.ts +2 -2
  360. package/dist/objects/policies.js +2 -1
  361. package/dist/objects/processes.d.ts +2 -2
  362. package/dist/objects/project-briefs.d.ts +2 -2
  363. package/dist/objects/project-briefs.js +2 -3
  364. package/dist/objects/skills.d.ts +2 -2
  365. package/dist/postinstall.js +1 -1
  366. package/dist/rerankers/clef.js +3 -6
  367. package/dist/rerankers/cross-encoder.d.ts +31 -2
  368. package/dist/rerankers/cross-encoder.js +71 -98
  369. package/dist/rerankers/index.js +5 -4
  370. package/dist/rerankers/jev.js +2 -4
  371. package/dist/rerankers/outage-warning.d.ts +3 -1
  372. package/dist/rerankers/outage-warning.js +5 -3
  373. package/dist/search/bm25-search.js +1 -1
  374. package/dist/search/boosts.js +1 -1
  375. package/dist/search/embed-memories.d.ts +12 -0
  376. package/dist/search/embed-memories.js +389 -0
  377. package/dist/search/engine.d.ts +8 -0
  378. package/dist/search/engine.js +10 -0
  379. package/dist/search/goal-boost.d.ts +1 -1
  380. package/dist/search/goal-boost.js +6 -3
  381. package/dist/search/hybrid.d.ts +9 -2
  382. package/dist/search/hybrid.js +9 -4
  383. package/dist/search/physics-search.d.ts +6 -3
  384. package/dist/search/physics-search.js +22 -15
  385. package/dist/search/rerank.js +1 -1
  386. package/dist/search/vector.d.ts +8 -6
  387. package/dist/search/vector.js +14 -17
  388. package/dist/server/auth.d.ts +3 -1
  389. package/dist/server/auth.js +24 -15
  390. package/dist/server/boot.d.ts +2 -1
  391. package/dist/server/boot.js +34 -84
  392. package/dist/server/client-ip.d.ts +9 -6
  393. package/dist/server/client-ip.js +36 -17
  394. package/dist/server/deadline.d.ts +0 -2
  395. package/dist/server/deadline.js +1 -7
  396. package/dist/server/defaults.d.ts +1 -0
  397. package/dist/server/defaults.js +1 -0
  398. package/dist/server/mcp-http.js +16 -11
  399. package/dist/server/request.js +7 -2
  400. package/dist/server/route-checks.d.ts +7 -0
  401. package/dist/server/route-checks.js +42 -0
  402. package/dist/server/route-table.d.ts +9 -8
  403. package/dist/server/route-table.js +127 -133
  404. package/dist/server/routes/admin.d.ts +8 -8
  405. package/dist/server/routes/admin.js +13 -21
  406. package/dist/server/routes/customer-notes.d.ts +6 -6
  407. package/dist/server/routes/customer-notes.js +1 -2
  408. package/dist/server/routes/decisions.d.ts +6 -6
  409. package/dist/server/routes/decisions.js +2 -4
  410. package/dist/server/routes/incidents.d.ts +6 -6
  411. package/dist/server/routes/incidents.js +2 -5
  412. package/dist/server/routes/memories.d.ts +9 -9
  413. package/dist/server/routes/memories.js +19 -40
  414. package/dist/server/routes/object-routes.d.ts +6 -6
  415. package/dist/server/routes/object-routes.js +4 -7
  416. package/dist/server/routes/policies.d.ts +7 -7
  417. package/dist/server/routes/policies.js +2 -4
  418. package/dist/server/routes/predictions.d.ts +6 -6
  419. package/dist/server/routes/predictions.js +9 -13
  420. package/dist/server/routes/processes.d.ts +6 -6
  421. package/dist/server/routes/processes.js +1 -2
  422. package/dist/server/routes/project-briefs.d.ts +7 -7
  423. package/dist/server/routes/project-briefs.js +2 -4
  424. package/dist/server/routes/recall.d.ts +5 -5
  425. package/dist/server/routes/recall.js +6 -10
  426. package/dist/server/routes/skills.d.ts +7 -7
  427. package/dist/server/routes/skills.js +2 -4
  428. package/dist/server/server-detect.d.ts +7 -1
  429. package/dist/server/server-detect.js +36 -11
  430. package/dist/server/sleep-child.js +26 -7
  431. package/dist/server/sleep-offload.d.ts +11 -5
  432. package/dist/server/sleep-offload.js +17 -11
  433. package/dist/server/tls.d.ts +7 -2
  434. package/dist/server/tls.js +17 -5
  435. package/dist/server/types.d.ts +29 -12
  436. package/dist/server/validation.d.ts +1 -2
  437. package/dist/server/validation.js +6 -11
  438. package/dist/server.d.ts +4 -40
  439. package/dist/server.js +3 -29
  440. package/dist/sharing/global-store.d.ts +4 -5
  441. package/dist/sharing/global-store.js +8 -13
  442. package/dist/sharing/global-sync.js +38 -45
  443. package/dist/sharing/project-merge.d.ts +7 -14
  444. package/dist/sharing/project-merge.js +57 -121
  445. package/dist/sharing/search-both.js +10 -3
  446. package/dist/sharing/share.d.ts +2 -0
  447. package/dist/sharing/share.js +39 -38
  448. package/dist/store/ambient.js +7 -7
  449. package/dist/store/audit-event.d.ts +1 -1
  450. package/dist/store/audit.d.ts +0 -16
  451. package/dist/store/audit.js +4 -55
  452. package/dist/store/auth.d.ts +4 -2
  453. package/dist/store/auth.js +7 -3
  454. package/dist/store/candidates.d.ts +3 -2
  455. package/dist/store/candidates.js +59 -30
  456. package/dist/store/capture-write.d.ts +17 -0
  457. package/dist/store/capture-write.js +35 -0
  458. package/dist/store/cards.js +6 -5
  459. package/dist/store/compaction-caller.d.ts +18 -0
  460. package/dist/store/compaction-caller.js +29 -0
  461. package/dist/store/compactions-record.d.ts +115 -0
  462. package/dist/store/compactions-record.js +418 -0
  463. package/dist/store/compactions.js +6 -4
  464. package/dist/store/conflicts.d.ts +7 -1
  465. package/dist/store/conflicts.js +13 -9
  466. package/dist/store/connectors/dlq.d.ts +10 -0
  467. package/dist/store/connectors/dlq.js +33 -0
  468. package/dist/store/connectors/event-log.d.ts +13 -0
  469. package/dist/store/connectors/event-log.js +24 -0
  470. package/dist/store/connectors/github.d.ts +7 -9
  471. package/dist/store/connectors/github.js +35 -60
  472. package/dist/store/connectors/slack.d.ts +2 -11
  473. package/dist/store/connectors/slack.js +24 -57
  474. package/dist/store/dashboard-reads.js +8 -6
  475. package/dist/store/delete-and-batch.d.ts +6 -0
  476. package/dist/store/delete-and-batch.js +40 -6
  477. package/dist/store/delivery-recorder.d.ts +12 -5
  478. package/dist/store/delivery-recorder.js +13 -11
  479. package/dist/store/descendant-page.js +8 -4
  480. package/dist/store/dormant.d.ts +1 -1
  481. package/dist/store/dormant.js +10 -4
  482. package/dist/store/embeddings/index.d.ts +2 -12
  483. package/dist/store/embeddings/index.js +5 -374
  484. package/dist/store/entry-reads.d.ts +35 -4
  485. package/dist/store/entry-reads.js +86 -19
  486. package/dist/store/entry-row.js +9 -2
  487. package/dist/store/entry-writes.d.ts +15 -12
  488. package/dist/store/entry-writes.js +54 -22
  489. package/dist/store/failure-log-at.d.ts +8 -0
  490. package/dist/store/failure-log-at.js +16 -0
  491. package/dist/store/failure-log.d.ts +2 -2
  492. package/dist/store/failure-log.js +5 -3
  493. package/dist/{trust → store}/gated-write.d.ts +3 -1
  494. package/dist/{trust → store}/gated-write.js +7 -5
  495. package/dist/store/goals.js +22 -19
  496. package/dist/store/graph-reads.js +9 -6
  497. package/dist/store/graph-view-rows.js +2 -1
  498. package/dist/store/graph-writes.d.ts +6 -6
  499. package/dist/store/graph-writes.js +48 -71
  500. package/dist/store/half-life.js +2 -2
  501. package/dist/store/handoffs.d.ts +10 -4
  502. package/dist/store/handoffs.js +10 -9
  503. package/dist/store/index-and-stats.js +2 -2
  504. package/dist/store/index.d.ts +2 -2
  505. package/dist/store/index.js +1 -1
  506. package/dist/store/key-writes.js +1 -1
  507. package/dist/store/ledger-turn.d.ts +9 -0
  508. package/dist/store/ledger-turn.js +38 -0
  509. package/dist/store/mirrors.js +19 -10
  510. package/dist/store/open.d.ts +2 -5
  511. package/dist/store/open.js +42 -54
  512. package/dist/store/pilot-arm.d.ts +12 -0
  513. package/dist/store/pilot-arm.js +42 -0
  514. package/dist/store/port.d.ts +68 -6
  515. package/dist/store/port.js +8 -0
  516. package/dist/store/predictions.d.ts +1 -2
  517. package/dist/store/predictions.js +3 -6
  518. package/dist/store/project-tags.d.ts +62 -0
  519. package/dist/store/project-tags.js +67 -0
  520. package/dist/store/quality-repair.js +1 -1
  521. package/dist/store/quarantine.js +2 -1
  522. package/dist/store/recall-trace.d.ts +2 -0
  523. package/dist/store/recall-trace.js +20 -3
  524. package/dist/store/rejected-values.js +16 -10
  525. package/dist/store/rejection-probe.d.ts +4 -0
  526. package/dist/store/rejection-probe.js +19 -0
  527. package/dist/store/rejection.d.ts +0 -17
  528. package/dist/store/rejection.js +1 -21
  529. package/dist/store/rows.js +2 -2
  530. package/dist/store/rule-sql.d.ts +11 -0
  531. package/dist/store/rule-sql.js +31 -6
  532. package/dist/store/search-rows.d.ts +5 -5
  533. package/dist/store/search-rows.js +18 -16
  534. package/dist/store/sessions.js +4 -2
  535. package/dist/store/set-aside.d.ts +14 -0
  536. package/dist/store/set-aside.js +23 -0
  537. package/dist/store/sqlite/busy.d.ts +3 -0
  538. package/dist/store/sqlite/busy.js +8 -0
  539. package/dist/store/sqlite/connector-events-group.js +4 -7
  540. package/dist/store/sqlite/connector-writes-group.js +6 -4
  541. package/dist/store/sqlite/entry-writes-group.d.ts +3 -3
  542. package/dist/store/sqlite/entry-writes-group.js +3 -3
  543. package/dist/store/sqlite/error-codec.js +4 -3
  544. package/dist/store/sqlite/executor.js +10 -4
  545. package/dist/store/sqlite/graph-reads-group.js +1 -1
  546. package/dist/store/sqlite/held-connection.d.ts +11 -0
  547. package/dist/store/sqlite/held-connection.js +41 -0
  548. package/dist/store/sqlite/last-recall-group.d.ts +13 -0
  549. package/dist/store/sqlite/last-recall-group.js +57 -0
  550. package/dist/store/sqlite/local.d.ts +0 -17
  551. package/dist/store/sqlite/local.js +0 -31
  552. package/dist/store/sqlite/maintenance-groups.d.ts +6 -0
  553. package/dist/store/sqlite/maintenance-groups.js +60 -0
  554. package/dist/store/sqlite/object-rows.d.ts +1 -1
  555. package/dist/store/sqlite/objects-group.d.ts +1 -1
  556. package/dist/store/sqlite/objects-group.js +15 -7
  557. package/dist/store/sqlite/store.d.ts +1 -8
  558. package/dist/store/sqlite/store.js +13 -22
  559. package/dist/store/summaries.d.ts +10 -3
  560. package/dist/store/summaries.js +54 -11
  561. package/dist/store/token-ledger-rows.js +3 -2
  562. package/dist/store/token-ledger.d.ts +0 -7
  563. package/dist/store/token-ledger.js +0 -35
  564. package/dist/store/vector-index.js +8 -4
  565. package/dist/support-bundle.js +2 -3
  566. package/dist/trust/reject-flow.js +2 -2
  567. package/dist/util/agent-homes.d.ts +15 -0
  568. package/dist/util/agent-homes.js +34 -0
  569. package/dist/util/anthropic-messages.d.ts +6 -1
  570. package/dist/util/anthropic-messages.js +26 -7
  571. package/dist/util/atomic-write.d.ts +5 -2
  572. package/dist/util/atomic-write.js +12 -8
  573. package/dist/util/chunked.d.ts +3 -0
  574. package/dist/util/chunked.js +10 -0
  575. package/dist/util/ellipsize.d.ts +3 -0
  576. package/dist/util/ellipsize.js +5 -0
  577. package/dist/util/env.d.ts +12 -2
  578. package/dist/util/env.js +12 -2
  579. package/dist/util/format.d.ts +5 -0
  580. package/dist/util/format.js +10 -0
  581. package/dist/util/hook-payload.d.ts +8 -0
  582. package/dist/util/hook-payload.js +37 -0
  583. package/dist/util/http-retry.d.ts +6 -0
  584. package/dist/util/http-retry.js +43 -5
  585. package/dist/util/http-util.d.ts +0 -8
  586. package/dist/util/http-util.js +10 -21
  587. package/dist/util/limits.d.ts +7 -0
  588. package/dist/util/limits.js +14 -0
  589. package/dist/util/log.d.ts +4 -0
  590. package/dist/util/log.js +9 -0
  591. package/dist/util/request-scope.d.ts +9 -3
  592. package/dist/util/request-scope.js +18 -9
  593. package/dist/util/secret-detect.js +6 -1
  594. package/dist/util/spawn-argv.d.ts +9 -0
  595. package/dist/util/spawn-argv.js +57 -0
  596. package/dist/util/sql.d.ts +3 -0
  597. package/dist/util/sql.js +6 -0
  598. package/dist/util/sqlite-blocked.d.ts +2 -0
  599. package/dist/util/sqlite-blocked.js +5 -0
  600. package/dist/util/stream-tee.d.ts +3 -0
  601. package/dist/util/stream-tee.js +35 -0
  602. package/dist/util/time.d.ts +4 -0
  603. package/dist/util/time.js +4 -0
  604. package/dist/util/vector-codec.d.ts +4 -0
  605. package/dist/util/vector-codec.js +15 -0
  606. package/dist/util/version.d.ts +1 -1
  607. package/dist/util/version.js +1 -1
  608. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  609. package/extensions/openclaw-plugin/package.json +1 -1
  610. package/openclaw.plugin.json +1 -1
  611. package/package.json +4 -2
  612. package/dist/api/on-store.d.ts +0 -16
  613. package/dist/api/on-store.js +0 -38
  614. package/dist/store/embeddings/local.d.ts +0 -15
  615. package/dist/store/embeddings/local.js +0 -175
  616. /package/dist/{cli → api}/correction-latency.d.ts +0 -0
  617. /package/dist/{cli → api}/correction-latency.js +0 -0
  618. /package/dist/{cli → api}/provenance-coverage.d.ts +0 -0
  619. /package/dist/{cli → api}/provenance-coverage.js +0 -0
  620. /package/dist/{store → core}/object-types.d.ts +0 -0
  621. /package/dist/{store → core}/object-types.js +0 -0
  622. /package/dist/{capture → util}/compaction-items.d.ts +0 -0
  623. /package/dist/{capture → util}/compaction-items.js +0 -0
  624. /package/dist/{store → util}/yaml.d.ts +0 -0
  625. /package/dist/{store → util}/yaml.js +0 -0
@@ -2,7 +2,7 @@
2
2
  import { DEFAULT_TENANT_ID, envLossAversionRatio } from '../util/env.js';
3
3
  import { BadRequestError } from './api-errors.js';
4
4
  import { randomUUID } from 'crypto';
5
- import { isDecayAblated, isOutcomeSlowAblated, isRecallBoostAblated, evalNow, } from './ablation.js';
5
+ import { evalNow, NO_STRENGTH_ABLATION } from './ablation.js';
6
6
  import { AGENT_MEMORY_TOOLS, toolSourcePrefix } from './agent-memory-tools.js';
7
7
  import { isJsonString } from '../util/json.js';
8
8
  import { DAY_MS } from '../util/time.js';
@@ -16,6 +16,7 @@ export var Layer;
16
16
  Layer["Semantic"] = "semantic";
17
17
  Layer["Trace"] = "trace";
18
18
  })(Layer || (Layer = {}));
19
+ export const MEMORY_KINDS = ['raw', 'distilled', 'superseded', 'archived'];
19
20
  /** Tag on a memory whose named file/symbol/script changed after it was stored. */
20
21
  export const CHURN_STALE_TAG = 'churn-stale';
21
22
  // Losses weigh ~2x gains (Lovallo-Kahneman), so negative is 2.0; HIPPO_LOSS_AVERSION_RATIO scales it per process.
@@ -57,9 +58,9 @@ export function applyLossAversionRatio(valence, baseMultiplier) {
57
58
  return baseMultiplier * getLossAversionRatio();
58
59
  }
59
60
  /** 1 + REWARD_SLOPE * (pos - neg) / (pos + neg + 1), in (0.5, 1.5), 1.0 with no outcomes; it scales the effective half-life. */
60
- export function calculateRewardFactor(entry) {
61
+ export function calculateRewardFactor(entry, ablation = NO_STRENGTH_ABLATION) {
61
62
  // EVAL-ONLY ablation (see ablation.ts): the slow outcome channel.
62
- if (isOutcomeSlowAblated())
63
+ if (ablation.outcomeSlow)
63
64
  return 1.0;
64
65
  const pos = entry.outcome_positive ?? 0;
65
66
  const neg = entry.outcome_negative ?? 0;
@@ -75,8 +76,8 @@ export const DECAY_BASE = 0.5;
75
76
  export const REWARD_SLOPE = 0.5;
76
77
  export const RETRIEVAL_BOOST_SLOPE = 0.1;
77
78
  /** Bad outcome marks past good ones, floored at zero; each halves strength and stops recall strengthening, so a correction outranks pinning. */
78
- export function netWrong(entry) {
79
- if (isOutcomeSlowAblated() || isDecayAblated())
79
+ export function netWrong(entry, ablation = NO_STRENGTH_ABLATION) {
80
+ if (ablation.outcomeSlow || ablation.decay)
80
81
  return 0;
81
82
  return Math.max(0, (entry.outcome_negative ?? 0) - (entry.outcome_positive ?? 0));
82
83
  }
@@ -85,22 +86,23 @@ export function netWrong(entry) {
85
86
  export function calculateStrength(entry,
86
87
  // evalNow() is the real clock unless HIPPO_FAKE_NOW is set (eval-only); an explicit `now` wins.
87
88
  now = evalNow(), options = {}) {
89
+ const ablation = options.ablation ?? NO_STRENGTH_ABLATION;
88
90
  // Being marked wrong outranks every shield: pinning, error tags, heavy recall.
89
- const wrongPenalty = Math.pow(DECAY_BASE, Math.min(netWrong(entry), MAX_WRONG_HALVINGS));
91
+ const wrongPenalty = Math.pow(DECAY_BASE, Math.min(netWrong(entry, ablation), MAX_WRONG_HALVINGS));
90
92
  if (entry.pinned)
91
93
  return wrongPenalty;
92
94
  // EVAL-ONLY ablation (see ablation.ts): anchor decay at creation so clock resets from earlier unflagged runs cannot leak in.
93
- const lastRetrieved = new Date(isRecallBoostAblated() ? entry.created : entry.last_retrieved);
95
+ const lastRetrieved = new Date(ablation.recallBoost ? entry.created : entry.last_retrieved);
94
96
  const daysSince = (now.getTime() - lastRetrieved.getTime()) / DAY_MS;
95
- const rewardFactor = calculateRewardFactor(entry);
97
+ const rewardFactor = calculateRewardFactor(entry, ablation);
96
98
  const effectiveHalfLife = entry.half_life_days * rewardFactor;
97
99
  // Guard: zero half-life causes 0/0 = NaN in the exponent
98
100
  if (effectiveHalfLife <= 0)
99
101
  return 0.0;
100
102
  const decayExponent = decayExponentFor(options, daysSince, effectiveHalfLife);
101
103
  // EVAL-ONLY ablation (see ablation.ts): decay := 1, so the [0,1] clamp below caps the boost at baseline.
102
- const decay = isDecayAblated() ? 1.0 : Math.pow(DECAY_BASE, decayExponent);
103
- const retrievalBoost = retrievalBoostFor(entry);
104
+ const decay = ablation.decay ? 1.0 : Math.pow(DECAY_BASE, decayExponent);
105
+ const retrievalBoost = retrievalBoostFor(entry, ablation);
104
106
  const baseMultiplier = EMOTIONAL_MULTIPLIERS[entry.emotional_valence] ?? 1.0;
105
107
  const emotionalMultiplier = applyLossAversionRatio(entry.emotional_valence, baseMultiplier);
106
108
  const raw = decay * retrievalBoost * emotionalMultiplier;
@@ -127,8 +129,8 @@ function decayExponentFor(options, daysSince, halfLifeDays) {
127
129
  return daysSince / effectiveHalfLife;
128
130
  }
129
131
  // EVAL-ONLY ablation (see ablation.ts) neutralizes the read side too, so counts written before the flag cannot leak in.
130
- function retrievalBoostFor(entry) {
131
- return isRecallBoostAblated() || netWrong(entry) > 0
132
+ function retrievalBoostFor(entry, ablation) {
133
+ return ablation.recallBoost || netWrong(entry, ablation) > 0
132
134
  ? 1.0
133
135
  : 1 + RETRIEVAL_BOOST_SLOPE * Math.log2(entry.retrieval_count + 1);
134
136
  }
@@ -259,7 +261,7 @@ export function createMemory(content, options = {}) {
259
261
  scope: options.scope ?? null,
260
262
  owner: options.owner ?? null,
261
263
  artifact_ref: options.artifact_ref ?? null,
262
- tenantId: options.tenantId ?? DEFAULT_TENANT_ID,
264
+ tenant_id: options.tenantId ?? DEFAULT_TENANT_ID,
263
265
  };
264
266
  entry.strength = calculateStrength(entry);
265
267
  return entry;
@@ -296,16 +298,26 @@ export function createSuccessor(old, content, opts) {
296
298
  /** How well new content fits existing knowledge, 0..1: above 0.7 consolidates faster, below 0.3 is novel and decays faster if unused.
297
299
  * Tags shared with existing memories count by rarity (a rare shared tag fits more), plus content-token overlap. */
298
300
  export function computeSchemaFit(content, tags, existingEntries) {
301
+ return schemaFitFrom(content, tags, schemaFitSourceOf(existingEntries));
302
+ }
303
+ /** What schema fit reads from `entries`, built once for a caller that scores many contents against the same rows. */
304
+ export function schemaFitSourceOf(entries) {
299
305
  const tagCounts = new Map();
300
- for (const entry of existingEntries) {
306
+ for (const entry of entries) {
301
307
  for (const tag of entry.tags) {
302
308
  tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
303
309
  }
304
310
  }
305
- return schemaFitFrom(content, tags, { rows: existingEntries.length, tagCounts, contents: existingEntries.map((e) => e.content) });
311
+ return { rows: entries.length, tagCounts, contents: entries.map((e) => e.content) };
306
312
  }
313
+ // Tokens of this length or shorter are filler for schema fit.
314
+ const FILLER_TOKEN_MAX_LENGTH = 3;
315
+ // Matching half the tags at average weight scores about 0.5, so the raw overlap ratio is doubled.
316
+ const TAG_OVERLAP_SCALE = 2;
317
+ // Floor on the matches that saturate the content score, so a tiny store cannot saturate it on one hit.
318
+ const CONTENT_ENOUGH_MIN_MATCHES = 5;
307
319
  function significantTokens(text) {
308
- return new Set(text.toLowerCase().replace(/[^\w\s]/g, ' ').split(/\s+/).filter((t) => t.length > 3));
320
+ return new Set(text.toLowerCase().replace(/[^\w\s]/g, ' ').split(/\s+/).filter((t) => t.length > FILLER_TOKEN_MAX_LENGTH));
309
321
  }
310
322
  /** computeSchemaFit against `source`, for a caller that holds counts and texts but no rows. */
311
323
  export function schemaFitFrom(content, tags, source) {
@@ -325,13 +337,12 @@ export function schemaFitFrom(content, tags, source) {
325
337
  weightedOverlap += Math.log(N / freq) + 1;
326
338
  totalWeight += maxIdf;
327
339
  }
328
- // Scaled so that matching half the tags at average weight gives about 0.5.
329
- const tagScore = totalWeight > 0 ? Math.min(1, (weightedOverlap / totalWeight) * 2) : 0;
340
+ const tagScore = totalWeight > 0 ? Math.min(1, (weightedOverlap / totalWeight) * TAG_OVERLAP_SCALE) : 0;
330
341
  const newTokens = significantTokens(content);
331
342
  if (newTokens.size === 0)
332
343
  return Math.min(1, Math.max(0, tagScore));
333
344
  // The content score is capped at 1, which this many matching memories reach.
334
- const enough = Math.max(5, N * CONTENT_ENOUGH_FRACTION);
345
+ const enough = Math.max(CONTENT_ENOUGH_MIN_MATCHES, N * CONTENT_ENOUGH_FRACTION);
335
346
  let contentMatches = 0;
336
347
  for (const text of source.contents) {
337
348
  const entryTokens = significantTokens(text);
@@ -358,13 +369,14 @@ function inferValence(tags) {
358
369
  }
359
370
  /** Returns copies with the retrieval count, clock and half-life updated; the caller persists them.
360
371
  * Under HIPPO_ABLATE_RECALL_BOOST (eval-only) the entries come back unchanged, not empty, since callers derive last_retrieval_ids from them. */
361
- export function markRetrieved(entries, now = evalNow()) {
362
- if (isRecallBoostAblated())
372
+ export function markRetrieved(entries, now = evalNow(), options = {}) {
373
+ const ablation = options.ablation ?? NO_STRENGTH_ABLATION;
374
+ if (ablation.recallBoost)
363
375
  return entries;
364
376
  return entries.map((e) => {
365
377
  if (e.superseded_by)
366
378
  return e;
367
- const wrong = netWrong(e) > 0;
379
+ const wrong = netWrong(e, ablation) > 0;
368
380
  // Confidence stays untouched: it is an epistemic tier, not a recency signal.
369
381
  const updated = {
370
382
  ...e,
@@ -373,7 +385,7 @@ export function markRetrieved(entries, now = evalNow()) {
373
385
  last_retrieved: wrong ? e.last_retrieved : now.toISOString(),
374
386
  half_life_days: wrong ? e.half_life_days : e.half_life_days + RETRIEVAL_HALF_LIFE_GAIN_DAYS,
375
387
  };
376
- updated.strength = calculateStrength(updated, now);
388
+ updated.strength = calculateStrength(updated, now, { ablation });
377
389
  return updated;
378
390
  });
379
391
  }
@@ -1,4 +1,4 @@
1
- import { type MemoryEntry } from '../core/memory.js';
1
+ import { type MemoryEntry } from './memory.js';
2
2
  /** The row that replaces a merged row once its retired texts leave: undefined when it holds none, null when nothing else is left. */
3
3
  export declare function mergedSuccessor(row: MemoryEntry, retired: (text: string) => boolean, retiredIds: ReadonlySet<string>): MemoryEntry | null | undefined;
4
4
  /** Sleep's check on a merged row: drops texts whose source was superseded since the merge, or that a rejection now covers. */
@@ -1,7 +1,7 @@
1
1
  // A sleep-merged row copies its sources' texts, so retiring a text (reject, or superseding its source) must reach the row too.
2
2
  // SHORTCUT: reject rewrites merged rows at once, live and dormant; supersede and `resolve
3
3
  // --reject-loser` wait for the next sleep's check, which sees a dormant row only once restored.
4
- import { generateId } from '../core/memory.js';
4
+ import { generateId } from './memory.js';
5
5
  import { duplicateKey, heldTexts, mergedText } from '../util/same-text.js';
6
6
  /** The row that replaces a merged row once its retired texts leave: undefined when it holds none, null when nothing else is left. */
7
7
  export function mergedSuccessor(row, retired, retiredIds) {
@@ -1,7 +1,7 @@
1
1
  /** Memory-as-Physics engine: pure math (forces, Velocity Verlet integration, physics-based scoring, cluster amplification), no I/O.
2
2
  * Memories are particles on the unit hypersphere in embedding space (384-dim); query gravity, attraction, conflict repulsion and drag act on them,
3
3
  * and nearby high-scoring memories amplify each other. */
4
- import { type EmotionalValence } from './memory.js';
4
+ import { type EmotionalValence, type MemoryEntry } from './memory.js';
5
5
  import type { PhysicsConfig } from './physics-config.js';
6
6
  export interface PhysicsParticle {
7
7
  memoryId: string;
@@ -71,4 +71,6 @@ export declare function physicsScore(particles: PhysicsParticle[], queryEmbeddin
71
71
  tieKeyOf?: (memoryId: string) => string): ScoredPhysicsResult[];
72
72
  /** Nudges a particle toward (good outcome) or away from (bad outcome) the query embedding; new memories respond more (temperature). Mutates in place. */
73
73
  export declare function applyOutcomeFeedback(particle: PhysicsParticle, queryEmbedding: number[], good: boolean, feedbackAlpha: number): void;
74
+ /** Initialize physics state for a memory from its embedding vector; returns the new particle without persisting (the caller must save). */
75
+ export declare function initializeParticle(entry: MemoryEntry, embedding: number[], now?: Date): PhysicsParticle;
74
76
  //# sourceMappingURL=physics.d.ts.map
@@ -1,8 +1,10 @@
1
1
  /** Memory-as-Physics engine: pure math (forces, Velocity Verlet integration, physics-based scoring, cluster amplification), no I/O.
2
2
  * Memories are particles on the unit hypersphere in embedding space (384-dim); query gravity, attraction, conflict repulsion and drag act on them,
3
3
  * and nearby high-scoring memories amplify each other. */
4
- import { isRecallBoostAblated } from './ablation.js';
5
- import { FALLBACK_HALF_LIFE_DAYS } from './memory.js';
4
+ import { cosineOf } from './cosine.js';
5
+ import { evalNow, isRecallBoostAblated, strengthAblation } from './ablation.js';
6
+ import { calculateStrength, FALLBACK_HALF_LIFE_DAYS } from './memory.js';
7
+ import { DAY_MS } from '../util/time.js';
6
8
  import { comparePhysicsResultsBy } from './compare.js';
7
9
  export function vecDot(a, b) {
8
10
  let sum = 0;
@@ -48,17 +50,6 @@ export function vecClampMagnitude(v, maxMag) {
48
50
  return v;
49
51
  return vecScale(v, maxMag / mag);
50
52
  }
51
- /** Cosine similarity between two vectors. */
52
- function cosine(a, b) {
53
- if (a.length === 0 || b.length === 0 || a.length !== b.length)
54
- return 0;
55
- const dot = vecDot(a, b);
56
- const na = vecNorm(a);
57
- const nb = vecNorm(b);
58
- if (na < 1e-10 || nb < 1e-10)
59
- return 0;
60
- return Math.min(1, Math.max(-1, dot / (na * nb)));
61
- }
62
53
  const CHARGE_MAP = {
63
54
  neutral: 0,
64
55
  positive: 0.3,
@@ -79,7 +70,7 @@ export function computeTemperature(ageDays, temperatureDecay) {
79
70
  }
80
71
  /** Query gravity (retrieval-time, virtual: does not move the particle): scalar ranking magnitude F = G_Q * mass * max(0, cosine(pos, query))^2. */
81
72
  export function queryGravityMagnitude(particle, queryEmbedding, G_query) {
82
- const cos = cosine(particle.position, queryEmbedding);
73
+ const cos = cosineOf(particle.position, queryEmbedding);
83
74
  return G_query * particle.mass * Math.pow(Math.max(0, cos), 2);
84
75
  }
85
76
  /** Momentum bonus in [0, 1]: how aligned the particle's velocity is with the query direction. */
@@ -96,7 +87,7 @@ export function velocityAlignmentBonus(particle, queryEmbedding) {
96
87
  /** Attraction force on particle i from j (consolidation-time): G_M m_i m_j max(0, cosine)^3, directed along (pos_j - pos_i) projected tangent to the unit
97
88
  * sphere at pos_i (positions are re-normalized to the sphere after integration). Adds into `out` (a fresh zero vector by default) using `diff` as scratch. */
98
89
  export function attractionForce(pi, pj, G_memory, out = vecZero(pi.position.length), diff = vecZero(pi.position.length)) {
99
- const cos = cosine(pi.position, pj.position);
90
+ const cos = cosineOf(pi.position, pj.position);
100
91
  if (cos <= 0)
101
92
  return out;
102
93
  const magnitude = G_memory * pi.mass * pj.mass * Math.pow(cos, 3);
@@ -113,7 +104,7 @@ export function attractionForce(pi, pj, G_memory, out = vecZero(pi.position.leng
113
104
  }
114
105
  /** Conflict repulsion on i away from j (consolidation-time): K_R * m_i * m_j / max(0.01, cosine_distance)^2, where cosine_distance = 1 - cosine_similarity. */
115
106
  export function repulsionForce(pi, pj, K_repulsion) {
116
- const cos = cosine(pi.position, pj.position);
107
+ const cos = cosineOf(pi.position, pj.position);
117
108
  const dist = Math.max(0.01, 1 - cos);
118
109
  const magnitude = K_repulsion * pi.mass * pj.mass / (dist * dist);
119
110
  // Direction: away from j
@@ -237,7 +228,7 @@ export function computeSystemEnergy(particles, G_memory) {
237
228
  }
238
229
  for (let i = 0; i < particles.length; i++) {
239
230
  for (let j = i + 1; j < particles.length; j++) {
240
- const cos = cosine(particles[i].position, particles[j].position);
231
+ const cos = cosineOf(particles[i].position, particles[j].position);
241
232
  potential -= G_memory * particles[i].mass * particles[j].mass * Math.max(0, cos);
242
233
  }
243
234
  }
@@ -292,7 +283,7 @@ function applyClusterAmplification(results, particles, config) {
292
283
  const pj = particleMap.get(top[j].memoryId);
293
284
  if (!pj)
294
285
  continue;
295
- const proximity = cosine(pi.position, pj.position);
286
+ const proximity = cosineOf(pi.position, pj.position);
296
287
  if (proximity > config.cluster_threshold) {
297
288
  clusterSignal += top[j].baseScore * proximity;
298
289
  }
@@ -311,4 +302,18 @@ export function applyOutcomeFeedback(particle, queryEmbedding, good, feedbackAlp
311
302
  const nudge = vecScale(direction, sign * feedbackAlpha * particle.temperature);
312
303
  particle.position = vecNormalize(vecAdd(particle.position, nudge));
313
304
  }
305
+ /** Initialize physics state for a memory from its embedding vector; returns the new particle without persisting (the caller must save). */
306
+ export function initializeParticle(entry, embedding, now = evalNow()) {
307
+ const strength = calculateStrength(entry, now, { ablation: strengthAblation() });
308
+ const ageDays = (now.getTime() - new Date(entry.created).getTime()) / DAY_MS;
309
+ return {
310
+ memoryId: entry.id,
311
+ position: [...embedding], // copy: position will diverge from original embedding
312
+ velocity: vecZero(embedding.length),
313
+ mass: computeMass(strength, entry.retrieval_count),
314
+ charge: computeCharge(entry.emotional_valence),
315
+ temperature: computeTemperature(ageDays, 1.0),
316
+ lastSimulation: now.toISOString(),
317
+ };
318
+ }
314
319
  //# sourceMappingURL=physics.js.map
@@ -0,0 +1,3 @@
1
+ export type ClosureState = 'open' | 'closed' | 'closed-unknown';
2
+ export declare const VALID_CLOSURE_STATES: ReadonlySet<ClosureState>;
3
+ //# sourceMappingURL=prediction.d.ts.map
@@ -0,0 +1,6 @@
1
+ export const VALID_CLOSURE_STATES = new Set([
2
+ 'open',
3
+ 'closed',
4
+ 'closed-unknown',
5
+ ]);
6
+ //# sourceMappingURL=prediction.js.map
@@ -27,6 +27,9 @@ export declare function resolveProjectIdentity(cwd?: string, opts?: ResolveProje
27
27
  /** Nearest ancestor `.hippo` below home and the temp root (never projects; on Windows the temp root sits inside home).
28
28
  * Everything is realpath'd so a symlinked temp root or cwd still matches its bound. */
29
29
  export declare function findHippoStoreDir(cwd?: string, opts?: ResolveProjectIdentityOpts): string | null;
30
+ /** Nearest ancestor store like git; the strict join is the fallback so `hippo init` still creates `<cwd>/.hippo`. */
31
+ export declare function getHippoRoot(cwd?: string, opts?: ResolveProjectIdentityOpts): string;
32
+ export declare function isInitialized(hippoRoot: string): boolean;
30
33
  /** A reader's project: a bare name, or an identity whose own rows may also carry its legacy folder name. */
31
34
  export type ProjectRef = string | Pick<ProjectIdentity, 'name' | 'legacyName' | 'aliases'>;
32
35
  /** The id a reader's new rows are stamped with; '' outside a project. */
@@ -4,10 +4,11 @@ import * as os from 'os';
4
4
  import * as path from 'path';
5
5
  import { BadRequestError } from './api-errors.js';
6
6
  import { isSharedStore, loadConfig } from './config.js';
7
- import { MAX_ID_LEN } from '../util/http-util.js';
7
+ import { MAX_ID_LEN } from '../util/limits.js';
8
8
  import { errorMessage, log } from '../util/log.js';
9
9
  import { originRemoteId, projectFileId } from './project-remote.js';
10
10
  import { realpathOrResolve } from '../util/real-path.js';
11
+ import { placeholders } from '../util/sql.js';
11
12
  const MAX_WALK_DEPTH = 64;
12
13
  const identityCache = new Map();
13
14
  let remoteSwitch = null;
@@ -154,6 +155,15 @@ export function findHippoStoreDir(cwd, opts) {
154
155
  const { hippoRoot } = walkProjectMarkers(start, home, stops);
155
156
  return hippoRoot === null ? null : path.join(hippoRoot, '.hippo');
156
157
  }
158
+ /** Nearest ancestor store like git; the strict join is the fallback so `hippo init` still creates `<cwd>/.hippo`. */
159
+ export function getHippoRoot(cwd = process.cwd(), opts) {
160
+ return findHippoStoreDir(cwd, opts) ?? path.join(realpathOrResolve(cwd), '.hippo');
161
+ }
162
+ export function isInitialized(hippoRoot) {
163
+ // autoInstallHooks / setupDailySchedule can create a bare .hippo with no hippo.db; counting
164
+ // that as initialized makes `hippo init` skip initStore, so only hippo.db counts.
165
+ return fs.existsSync(path.join(hippoRoot, 'hippo.db'));
166
+ }
157
167
  function isBareName(project) {
158
168
  return typeof project === 'string';
159
169
  }
@@ -187,7 +197,7 @@ export function assertCallerProject(project) {
187
197
  }
188
198
  /** `origin_project IN (?, ...)` with one placeholder per name; an empty list matches nothing. */
189
199
  export function originInSql(names, column = 'origin_project') {
190
- return names.length === 0 ? '0' : `${column} IN (${names.map(() => '?').join(', ')})`;
200
+ return names.length === 0 ? '0' : `${column} IN (${placeholders(names.length)})`;
191
201
  }
192
202
  /** Classifies a memory's origin_project against the active project. An empty current id (home or markerless cwd) puts everything in scope;
193
203
  * NULL/undefined origin (legacy row, or a shared-store write naming none) counts as cross-project: deny by default for a security partition. */
@@ -1,6 +1,6 @@
1
1
  /** Recall-side scope predicates, in a leaf module so search-both.ts can apply the same default-deny rule to searchBothHybrid's candidate loads without an
2
2
  * import cycle. src/api/index.ts imports these and re-exports them for back-compat (`api.isPrivateScope`, tests importing `passesScopeFilterForRecall`). */
3
- import { ForbiddenError } from '../core/api-errors.js';
3
+ import { ForbiddenError } from './api-errors.js';
4
4
  /** Literal scopes excluded from recall by default-deny when the caller passes no `scope`; read by the SQL clause in `loadSearchRows` and by
5
5
  * `passesScopeFilterForRecall`. Invariant: never empty, or quarantine scopes would pass both paths silently (the module-load assertion below pins this). */
6
6
  export declare const RECALL_DEFAULT_DENY_SCOPES: readonly ["unknown:legacy"];
@@ -31,15 +31,6 @@ export declare function ownScopeTouches(ownScope: string | null, scope: string |
31
31
  /** JS half of the recall default-deny rule (SQL half: `loadSearchRows`). A non-empty `requested` needs an exact match;
32
32
  * otherwise `<source>:private:*` and `RECALL_DEFAULT_DENY_SCOPES` buckets are denied, while null, public scopes and the caller's `ownScope` pass. */
33
33
  export declare function passesScopeFilterForRecall(scope: string | null, requested: string | undefined, ownScope?: string | null): boolean;
34
- export interface SqlFragment {
35
- sql: string;
36
- params: string[];
37
- }
38
- /** SQL twin of the no-request arm of passesScopeFilterForRecall; `ownScope` is bound and compared with `=`, so `%` or `_` in an owner match nothing extra. */
39
- export declare function scopeAdmitSql(col: '' | 'm.', ownScope?: string | null): SqlFragment;
40
- /** SQL twin of canTouchScope, which is also canReadScope for an admin: every row but
41
- * another person's personal one. LIKE folds ASCII case as isPersonalScope's /i does. */
42
- export declare function touchableScopeSql(col: '' | 'm.', ownScope?: string | null): SqlFragment;
43
34
  /** CLI `--scope` variant: an explicit `--scope X` UNLOCKS scope X on top of the default-admitted set rather than narrowing to X (api.recall keeps 'exact').
44
35
  * Includes private/quarantine scopes (owner access); a named personal scope never unlocks: the CLI has no caller identity. */
45
36
  export declare function passesCliRecallScopeFilter(scope: string | null, requested: string | undefined): boolean;
@@ -1,7 +1,7 @@
1
1
  /** Recall-side scope predicates, in a leaf module so search-both.ts can apply the same default-deny rule to searchBothHybrid's candidate loads without an
2
2
  * import cycle. src/api/index.ts imports these and re-exports them for back-compat (`api.isPrivateScope`, tests importing `passesScopeFilterForRecall`). */
3
- import { BadRequestError, ForbiddenError } from '../core/api-errors.js';
4
- import { MAX_ID_LEN } from '../util/http-util.js';
3
+ import { BadRequestError, ForbiddenError } from './api-errors.js';
4
+ import { MAX_ID_LEN } from '../util/limits.js';
5
5
  /** Literal scopes excluded from recall by default-deny when the caller passes no `scope`; read by the SQL clause in `loadSearchRows` and by
6
6
  * `passesScopeFilterForRecall`. Invariant: never empty, or quarantine scopes would pass both paths silently (the module-load assertion below pins this). */
7
7
  export const RECALL_DEFAULT_DENY_SCOPES = ['unknown:legacy'];
@@ -63,23 +63,6 @@ export function passesScopeFilterForRecall(scope, requested, ownScope) {
63
63
  }
64
64
  return !isRestrictedScope(scope) || (ownScope != null && scope === ownScope);
65
65
  }
66
- /** SQL twin of the no-request arm of passesScopeFilterForRecall; `ownScope` is bound and compared with `=`, so `%` or `_` in an owner match nothing extra. */
67
- export function scopeAdmitSql(col, ownScope) {
68
- const placeholders = RECALL_DEFAULT_DENY_SCOPES.map(() => '?').join(', ');
69
- const admitted = `${col}scope IS NULL OR (${col}scope NOT IN (${placeholders}) AND ${col}scope NOT LIKE '%:private:%')`;
70
- if (ownScope == null)
71
- return { sql: `(${admitted})`, params: [...RECALL_DEFAULT_DENY_SCOPES] };
72
- // The own arm sits inside the outer parentheses so a caller's `AND ${sql}` cannot split it off.
73
- return { sql: `(${admitted} OR ${col}scope = ?)`, params: [...RECALL_DEFAULT_DENY_SCOPES, ownScope] };
74
- }
75
- /** SQL twin of canTouchScope, which is also canReadScope for an admin: every row but
76
- * another person's personal one. LIKE folds ASCII case as isPersonalScope's /i does. */
77
- export function touchableScopeSql(col, ownScope) {
78
- const notPersonal = `${col}scope IS NULL OR ${col}scope NOT LIKE '${PERSONAL_SCOPE_PREFIX}%'`;
79
- if (ownScope == null)
80
- return { sql: `(${notPersonal})`, params: [] };
81
- return { sql: `(${notPersonal} OR ${col}scope = ?)`, params: [ownScope] };
82
- }
83
66
  /** CLI `--scope` variant: an explicit `--scope X` UNLOCKS scope X on top of the default-admitted set rather than narrowing to X (api.recall keeps 'exact').
84
67
  * Includes private/quarantine scopes (owner access); a named personal scope never unlocks: the CLI has no caller identity. */
85
68
  export function passesCliRecallScopeFilter(scope, requested) {
@@ -63,7 +63,7 @@ export interface ScoreBreakdown {
63
63
  scopeBoost: number;
64
64
  /** Extra multiplier applied post-hybrid (e.g. 1.2x for local hits in a local+global merge); 1.0 otherwise. */
65
65
  sourceBump: number;
66
- /** Immediate outcome nudge, 1 + 0.15*tanh(pos - neg) clipped to [0.85, 1.15]; separate from the slow strength path. */
66
+ /** Immediate outcome nudge, 1 + 0.15*tanh((pos - neg) / 2) clipped to [0.85, 1.15]; separate from the slow strength path. */
67
67
  outcomeBoost: number;
68
68
  /** CHURN_STALE_RANK_MULTIPLIER if tagged 'churn-stale', else 1.0. */
69
69
  churnStaleMultiplier: number;
@@ -12,9 +12,9 @@ export interface ActionResult {
12
12
  /** Sets `pinned`; the audit row says `remember` with actor `dashboard`, the same op the old star toggle wrote. */
13
13
  export declare function pinMemory(hippoRoot: string, tenantId: string, rawId: string, body: ActionBody): ActionResult;
14
14
  /** Marks a memory wrong, the same signal as `hippo outcome --bad`. */
15
- export declare function markWrong(hippoRoot: string, tenantId: string, rawId: string): ActionResult;
15
+ export declare function markWrong(hippoRoot: string, tenantId: string, rawId: string): Promise<ActionResult>;
16
16
  /** Deletes a memory like `hippo forget`; a raw receipt is append-only and answers 409 with the archive hint. */
17
- export declare function forgetMemory(hippoRoot: string, tenantId: string, rawId: string): ActionResult;
17
+ export declare function forgetMemory(hippoRoot: string, tenantId: string, rawId: string): Promise<ActionResult>;
18
18
  /** Keeps one side of an open conflict and weakens the other; every refusal maps to its own status. */
19
19
  export declare function resolveOpenConflict(hippoRoot: string, tenantId: string, conflictId: number, body: ActionBody): ActionResult;
20
20
  //# sourceMappingURL=dashboard-actions.d.ts.map
@@ -29,25 +29,25 @@ export function pinMemory(hippoRoot, tenantId, rawId, body) {
29
29
  return { status: 200, body: null, changed: true, entry: next };
30
30
  }
31
31
  /** Marks a memory wrong, the same signal as `hippo outcome --bad`. */
32
- export function markWrong(hippoRoot, tenantId, rawId) {
32
+ export async function markWrong(hippoRoot, tenantId, rawId) {
33
33
  const id = parseMemoryId(rawId);
34
34
  if (liveEntry(hippoRoot, tenantId, id) === null)
35
35
  return notFound();
36
36
  const ctx = { hippoRoot, tenantId, actor: api.adminActor(ACTOR) };
37
37
  // outcome() answers `applied: 0` for a missing or other-tenant id instead of throwing.
38
- if (api.outcome(ctx, [id], false).applied === 0)
38
+ if ((await api.outcome(ctx, [id], false)).applied === 0)
39
39
  return notFound();
40
40
  const entry = readEntry(hippoRoot, id, tenantId);
41
41
  return entry === null ? notFound() : { status: 200, body: null, changed: true, entry };
42
42
  }
43
43
  /** Deletes a memory like `hippo forget`; a raw receipt is append-only and answers 409 with the archive hint. */
44
- export function forgetMemory(hippoRoot, tenantId, rawId) {
44
+ export async function forgetMemory(hippoRoot, tenantId, rawId) {
45
45
  const id = parseMemoryId(rawId);
46
46
  if (liveEntry(hippoRoot, tenantId, id) === null)
47
47
  return notFound();
48
48
  const ctx = { hippoRoot, tenantId, actor: api.adminActor(ACTOR) };
49
49
  try {
50
- const done = api.forget(ctx, id);
50
+ const done = await api.forget(ctx, id);
51
51
  return { status: 200, body: done, changed: true };
52
52
  }
53
53
  catch (err) {
@@ -79,7 +79,7 @@ export function resolveOpenConflict(hippoRoot, tenantId, conflictId, body) {
79
79
  if (liveEntry(hippoRoot, tenantId, conflict.memory_a_id) === null || liveEntry(hippoRoot, tenantId, conflict.memory_b_id) === null) {
80
80
  return notFound();
81
81
  }
82
- const resolved = resolveConflict(hippoRoot, conflictId, keep, false, tenantId, { rejectedBy: ACTOR });
82
+ const resolved = resolveConflict(hippoRoot, { conflictId, keepId: keep, tenantId }, { rejectedBy: ACTOR });
83
83
  // Both guards passed above, so null here is a race with another resolver.
84
84
  if (resolved === null)
85
85
  return already();
@@ -2,6 +2,7 @@
2
2
  import { calculateStrength } from '../core/memory.js';
3
3
  import { loadOpenConflictsOf } from '../store/conflicts.js';
4
4
  import { BANDS, DAY_MS, LAYERS, isLiveMemory, memoryFacts, projectIdentity, } from './dashboard-snapshot.js';
5
+ import { pct, round1 } from '../util/format.js';
5
6
  const SERIES_DAYS = 90;
6
7
  /** The server words each delta for this range; the client re-slices the series for 7d and 90d. */
7
8
  const DELTA_DAYS = 30;
@@ -9,9 +10,6 @@ const POINTS_MAX = 4_000;
9
10
  const GRID_COLS = 64;
10
11
  const GRID_ROWS = 32;
11
12
  const NAME_MATCHES = 3;
12
- function round1(value) {
13
- return Math.round(value * 10) / 10;
14
- }
15
13
  function round3(value) {
16
14
  return Math.round(value * 1000) / 1000;
17
15
  }
@@ -32,9 +30,6 @@ function cumulativeSeries(times, nowMs) {
32
30
  function addedWithin(series) {
33
31
  return series[SERIES_DAYS] - series[SERIES_DAYS - DELTA_DAYS];
34
32
  }
35
- function pct(share) {
36
- return `${(share * 100).toFixed(1)}%`;
37
- }
38
33
  function plural(n, one, many) {
39
34
  return `${n} ${n === 1 ? one : many}`;
40
35
  }
@@ -342,7 +337,7 @@ export function buildMemoryDetail(hippoRoot, tenantId, entry, options) {
342
337
  projectKey: project.key,
343
338
  projectName: project.name,
344
339
  scope: entry.scope,
345
- tenant: entry.tenantId,
340
+ tenant: entry.tenant_id,
346
341
  kind: entry.kind,
347
342
  embedded,
348
343
  conflicts: openConflictsOf(hippoRoot, tenantId, entry, nowMs),
@@ -5,6 +5,7 @@ import { isQuarantineScope } from '../trust/quarantine.js';
5
5
  import { listMemoryConflicts } from '../store/conflicts.js';
6
6
  import { DashboardConnection, loadDashboardRows } from '../store/dashboard-reads.js';
7
7
  import { DAY_MS } from '../util/time.js';
8
+ import { round1 } from '../util/format.js';
8
9
  export { DAY_MS };
9
10
  export const LAYERS = ['buffer', 'episodic', 'semantic', 'trace'];
10
11
  // Wire order of `Band`, and the order the mockup sorts bands in.
@@ -99,9 +100,6 @@ function cutHead(content) {
99
100
  end -= 1;
100
101
  return { head: content.slice(0, end), truncated: true };
101
102
  }
102
- function round1(value) {
103
- return Math.round(value * 10) / 10;
104
- }
105
103
  function newDraft(identity) {
106
104
  return {
107
105
  summary: {
@@ -179,7 +179,7 @@ async function handlePost(ctx, segments) {
179
179
  }
180
180
  const body = await readActionBody(req);
181
181
  const tenantId = resolveTenantId({});
182
- const result = action(hippoRoot, tenantId, body);
182
+ const result = await action(hippoRoot, tenantId, body);
183
183
  if (result.changed)
184
184
  snapshots.invalidate();
185
185
  if (result.entry) {
@@ -300,7 +300,7 @@ function answerDashboardFailure(req, res, err) {
300
300
  const clientFault = err instanceof ParamError || err instanceof URIError || err instanceof BodyTimeoutError;
301
301
  // A cut-short response is logged even for a client fault; a bare 400 is not.
302
302
  if (res.headersSent || !clientFault) {
303
- log.error('dashboard request failed', { ...errorFields(err), error: errorMessage(err), path: req.url });
303
+ log.error('dashboard request failed', { ...errorFields(err), error: errorMessage(err), path: (req.url ?? '/').split('?')[0] });
304
304
  }
305
305
  if (res.headersSent) {
306
306
  res.end();
@@ -318,9 +318,20 @@ function answerDashboardFailure(req, res, err) {
318
318
  return jsonResponse(res, { error: 'Malformed URL path' }, 400);
319
319
  jsonResponse(res, { error: 'Internal error' }, 500);
320
320
  }
321
- function printDashboardBanner(boundPort, token, distUiDir) {
321
+ const DASHBOARD_URL_FILE = 'dashboard.url';
322
+ function printDashboardBanner(hippoRoot, boundPort, token, distUiDir) {
322
323
  // The banner is the `hippo dashboard` command's printed result, so it stays on stdout.
323
- console.log(`Hippo Dashboard running at http://localhost:${boundPort}/?token=${token}`);
324
+ const url = `http://localhost:${boundPort}/?token=${token}`;
325
+ if (process.stdout.isTTY) {
326
+ console.log(`Hippo Dashboard running at ${url}`);
327
+ }
328
+ else {
329
+ // Captured stdout lands in logs, so the token goes to an owner-only file, created fresh so its mode holds.
330
+ const file = path.join(hippoRoot, DASHBOARD_URL_FILE);
331
+ fs.rmSync(file, { force: true });
332
+ fs.writeFileSync(file, `${url}\n`, { flag: 'wx', mode: 0o600 });
333
+ console.log(`Hippo Dashboard running at http://localhost:${boundPort}/ (open the URL with its token from ${file})`);
334
+ }
324
335
  if (distUiDir !== null)
325
336
  console.log(`Serving React UI from ${distUiDir}`);
326
337
  console.log('Press Ctrl+C to stop.');
@@ -406,7 +417,7 @@ export function serveDashboard(hippoRoot, port = 3333, token = randomBytes(32).t
406
417
  server.listen(port, '127.0.0.1', () => {
407
418
  // SAFETY: listen() was given a TCP port, so address() is AddressInfo, never a pipe name.
408
419
  const boundPort = server.address().port;
409
- printDashboardBanner(boundPort, token, hasDistUi ? distUiDir : null);
420
+ printDashboardBanner(hippoRoot, boundPort, token, hasDistUi ? distUiDir : null);
410
421
  });
411
422
  return server;
412
423
  }
package/dist/db/busy.d.ts CHANGED
@@ -1,12 +1,7 @@
1
1
  import type { DatabaseSyncLike } from './sqlite.js';
2
2
  export declare function isSqliteBusy(cause: unknown): boolean;
3
- export declare const STORE_BUSY_MESSAGE = "store busy (another hippo process holds the write lock); retry shortly";
4
- /** A store behind the port throws this when its lock wait runs out, so the server answers 503 as it does for a busy hippo.db. */
5
- export declare class StoreBusyError extends Error {
6
- constructor(message?: string, options?: ErrorOptions);
7
- }
8
- /** A held lock in any store: SQLite's busy codes or a port's StoreBusyError. */
9
- export declare function isStoreBusy<E>(error: E): boolean;
3
+ /** The lock wait of an open that names none: its busy_timeout and every explicit wait below. */
4
+ export declare const DEFAULT_BUSY_WAIT_MS = 5000;
10
5
  export declare function execWithBusyRetry(db: DatabaseSyncLike, sql: string, timeoutMs?: number): void;
11
6
  /** Waits for the lock past busy_timeout when given; ignored inside a caller's transaction, which already holds it. */
12
7
  export interface WriteScopeOptions {