hippo-memory 1.62.0 → 1.63.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 (370) hide show
  1. package/README.md +9 -0
  2. package/dist/agent-memories/apply.d.ts +2 -0
  3. package/dist/agent-memories/apply.js +23 -1
  4. package/dist/agent-memories/claude-code.d.ts +2 -1
  5. package/dist/agent-memories/claude-code.js +4 -4
  6. package/dist/agent-memories/report.d.ts +1 -0
  7. package/dist/agent-memories/report.js +2 -2
  8. package/dist/agent-memories/sync.js +37 -23
  9. package/dist/ambient-store.d.ts +4 -4
  10. package/dist/ambient-store.js +4 -3
  11. package/dist/api/assemble.d.ts +7 -10
  12. package/dist/api/assemble.js +7 -12
  13. package/dist/api/audit.d.ts +2 -2
  14. package/dist/api/audit.js +2 -2
  15. package/dist/api/auth.d.ts +6 -6
  16. package/dist/api/auth.js +5 -7
  17. package/dist/api/context-select.d.ts +4 -3
  18. package/dist/api/context-select.js +9 -11
  19. package/dist/api/context-types.d.ts +12 -17
  20. package/dist/api/context.d.ts +3 -2
  21. package/dist/api/context.js +20 -20
  22. package/dist/api/dormant.js +1 -1
  23. package/dist/api/drill-down.d.ts +4 -4
  24. package/dist/api/drill-down.js +22 -10
  25. package/dist/api/outcome.d.ts +8 -13
  26. package/dist/api/outcome.js +13 -17
  27. package/dist/api/promote.d.ts +5 -8
  28. package/dist/api/quarantine.js +3 -2
  29. package/dist/api/recall-types.d.ts +43 -55
  30. package/dist/api/recall.d.ts +3 -3
  31. package/dist/api/recall.js +7 -18
  32. package/dist/api/remember.d.ts +2 -2
  33. package/dist/api/sleep.d.ts +7 -35
  34. package/dist/api/sleep.js +3 -2
  35. package/dist/api/tokens.d.ts +2 -2
  36. package/dist/api/tokens.js +2 -2
  37. package/dist/api/types.d.ts +6 -10
  38. package/dist/api/types.js +2 -4
  39. package/dist/audit-prune.d.ts +4 -6
  40. package/dist/audit-prune.js +3 -5
  41. package/dist/audit.js +11 -33
  42. package/dist/auth.d.ts +8 -9
  43. package/dist/auth.js +5 -7
  44. package/dist/autolearn.d.ts +1 -1
  45. package/dist/autolearn.js +1 -1
  46. package/dist/availability.js +3 -5
  47. package/dist/capture/command.d.ts +5 -4
  48. package/dist/capture/command.js +8 -22
  49. package/dist/capture/compact.d.ts +1 -1
  50. package/dist/capture/compact.js +9 -13
  51. package/dist/capture/extract.js +37 -115
  52. package/dist/capture-error.d.ts +1 -1
  53. package/dist/capture-error.js +1 -1
  54. package/dist/churn-git.d.ts +1 -1
  55. package/dist/churn-git.js +1 -1
  56. package/dist/cli/audit.js +3 -4
  57. package/dist/cli/auth.js +3 -6
  58. package/dist/cli/curate.d.ts +1 -1
  59. package/dist/cli/curate.js +8 -24
  60. package/dist/cli/dag.js +5 -9
  61. package/dist/cli/decisions.js +10 -21
  62. package/dist/cli/explain.js +2 -1
  63. package/dist/cli/goals.js +1 -1
  64. package/dist/cli/init.js +1 -1
  65. package/dist/cli/playbooks.js +4 -9
  66. package/dist/cli/projects.js +3 -1
  67. package/dist/cli/recall.js +2 -1
  68. package/dist/cli/remember.js +6 -18
  69. package/dist/cli/session-hooks.js +21 -40
  70. package/dist/cli/setup.d.ts +1 -1
  71. package/dist/cli/setup.js +2 -6
  72. package/dist/cli/shared.js +3 -3
  73. package/dist/cli/slack.js +1 -1
  74. package/dist/cli/sleep.js +27 -1
  75. package/dist/cli/status.d.ts +4 -4
  76. package/dist/cli/status.js +7 -12
  77. package/dist/cli/transfer.js +8 -13
  78. package/dist/cli/usage.js +9 -6
  79. package/dist/cli.d.ts +1 -1
  80. package/dist/cli.js +4 -9
  81. package/dist/compaction-record.d.ts +2 -0
  82. package/dist/compaction-record.js +89 -68
  83. package/dist/compare.d.ts +11 -16
  84. package/dist/compare.js +11 -16
  85. package/dist/config.d.ts +19 -18
  86. package/dist/config.js +90 -67
  87. package/dist/connectors/github/backfill.d.ts +2 -2
  88. package/dist/connectors/github/backfill.js +8 -15
  89. package/dist/connectors/github/cli-impl.js +3 -8
  90. package/dist/connectors/github/deletion.d.ts +5 -12
  91. package/dist/connectors/github/deletion.js +5 -12
  92. package/dist/connectors/github/dlq.d.ts +6 -9
  93. package/dist/connectors/github/dlq.js +2 -3
  94. package/dist/connectors/github/ingest.d.ts +5 -7
  95. package/dist/connectors/github/ingest.js +8 -12
  96. package/dist/connectors/github/octokit-client.d.ts +3 -5
  97. package/dist/connectors/github/octokit-client.js +5 -6
  98. package/dist/connectors/github/signature.d.ts +9 -39
  99. package/dist/connectors/github/signature.js +9 -39
  100. package/dist/connectors/github/tenant-routing.d.ts +1 -1
  101. package/dist/connectors/github/tenant-routing.js +1 -1
  102. package/dist/connectors/github/transform.js +2 -2
  103. package/dist/connectors/github/types.d.ts +2 -10
  104. package/dist/connectors/github/types.js +1 -3
  105. package/dist/connectors/slack/deletion.d.ts +3 -8
  106. package/dist/connectors/slack/deletion.js +3 -8
  107. package/dist/connectors/slack/dlq.d.ts +1 -1
  108. package/dist/connectors/slack/ingest.d.ts +1 -1
  109. package/dist/connectors/slack/ingest.js +7 -16
  110. package/dist/connectors/slack/signature.d.ts +1 -1
  111. package/dist/connectors/slack/tenant-routing.d.ts +3 -5
  112. package/dist/connectors/slack/tenant-routing.js +3 -5
  113. package/dist/connectors/slack/transform.d.ts +5 -6
  114. package/dist/connectors/slack/transform.js +5 -6
  115. package/dist/connectors/slack/types.d.ts +2 -6
  116. package/dist/connectors/slack/types.js +1 -3
  117. package/dist/connectors/slack/web-client.js +10 -3
  118. package/dist/connectors/slack/workspaces.d.ts +3 -5
  119. package/dist/connectors/slack/workspaces.js +3 -5
  120. package/dist/consolidate/conflicts.js +3 -14
  121. package/dist/consolidate/decay.js +9 -29
  122. package/dist/consolidate/llm-passes.js +4 -5
  123. package/dist/consolidate/merge.js +8 -23
  124. package/dist/consolidate/run.d.ts +1 -8
  125. package/dist/consolidate/run.js +3 -25
  126. package/dist/consolidate/sleep.js +5 -17
  127. package/dist/consolidate/traces.js +9 -21
  128. package/dist/customer-notes.d.ts +5 -7
  129. package/dist/customer-notes.js +6 -9
  130. package/dist/dag.d.ts +10 -21
  131. package/dist/dag.js +23 -73
  132. package/dist/db/continuity.js +2 -2
  133. package/dist/db/migrations/v14.js +1 -1
  134. package/dist/db/migrations/v15.js +1 -2
  135. package/dist/db/migrations/v16.js +3 -4
  136. package/dist/db/migrations/v17.js +2 -3
  137. package/dist/db/migrations/v19.js +1 -1
  138. package/dist/db/migrations/v20.js +1 -1
  139. package/dist/db/migrations/v21.js +2 -6
  140. package/dist/db/migrations/v22.js +2 -4
  141. package/dist/db/migrations/v23.js +1 -1
  142. package/dist/db/migrations/v24.js +4 -6
  143. package/dist/db/migrations/v25.js +2 -3
  144. package/dist/db/migrations/v26.js +3 -3
  145. package/dist/db/migrations/v27.js +2 -10
  146. package/dist/db/migrations/v28.js +5 -8
  147. package/dist/db/migrations/v29.js +3 -4
  148. package/dist/db/migrations/v30.js +2 -2
  149. package/dist/db/migrations/v31.js +1 -1
  150. package/dist/db/migrations/v32.js +1 -1
  151. package/dist/db/migrations/v33.js +3 -3
  152. package/dist/db/migrations/v34.js +1 -1
  153. package/dist/db/migrations/v35.js +3 -4
  154. package/dist/db/migrations/v36.js +3 -4
  155. package/dist/db/migrations/v37.js +5 -5
  156. package/dist/db/migrations/v38.js +7 -8
  157. package/dist/db/migrations/v39.js +1 -1
  158. package/dist/db/migrations/v40.js +4 -16
  159. package/dist/db/migrations/v41.js +3 -4
  160. package/dist/db/migrations/v42.js +3 -4
  161. package/dist/db/migrations/v45.js +1 -1
  162. package/dist/db/migrations/v46.js +1 -1
  163. package/dist/db/migrations/v47.js +1 -1
  164. package/dist/db/migrations/v48.js +1 -1
  165. package/dist/decisions.d.ts +2 -2
  166. package/dist/decisions.js +6 -6
  167. package/dist/dedupe.js +86 -61
  168. package/dist/delivery-recorder.js +154 -135
  169. package/dist/doctor.js +119 -104
  170. package/dist/dormant.js +1 -4
  171. package/dist/embedding-provider.d.ts +4 -8
  172. package/dist/embedding-provider.js +4 -8
  173. package/dist/embeddings.js +55 -47
  174. package/dist/env.d.ts +1 -1
  175. package/dist/env.js +12 -12
  176. package/dist/escape.d.ts +5 -0
  177. package/dist/escape.js +10 -0
  178. package/dist/eval-stats.d.ts +1 -2
  179. package/dist/eval-stats.js +1 -2
  180. package/dist/eval-suite.js +27 -21
  181. package/dist/extract.js +4 -9
  182. package/dist/failure-log.d.ts +3 -3
  183. package/dist/failure-log.js +1 -1
  184. package/dist/forward-claim-detector.d.ts +2 -4
  185. package/dist/forward-claim-detector.js +6 -11
  186. package/dist/goals.d.ts +3 -3
  187. package/dist/goals.js +5 -6
  188. package/dist/graph/read.d.ts +2 -2
  189. package/dist/graph/read.js +5 -6
  190. package/dist/graph/types.d.ts +8 -8
  191. package/dist/graph/write.d.ts +7 -14
  192. package/dist/graph/write.js +16 -23
  193. package/dist/graph-extract.d.ts +7 -8
  194. package/dist/graph-extract.js +62 -72
  195. package/dist/graph-recall.d.ts +2 -2
  196. package/dist/graph-recall.js +55 -49
  197. package/dist/graph-stream.d.ts +5 -6
  198. package/dist/graph-stream.js +66 -57
  199. package/dist/graph-view.d.ts +2 -2
  200. package/dist/graph-view.js +7 -7
  201. package/dist/half-life-migration.d.ts +1 -2
  202. package/dist/half-life-migration.js +2 -3
  203. package/dist/hooks/codex-session.js +1 -1
  204. package/dist/hooks/codex-wrapper.d.ts +1 -1
  205. package/dist/hooks/codex-wrapper.js +3 -2
  206. package/dist/hooks/json-hooks.d.ts +2 -2
  207. package/dist/hooks/json-hooks.js +5 -4
  208. package/dist/hooks/opencode.d.ts +1 -1
  209. package/dist/hooks/opencode.js +5 -4
  210. package/dist/hooks/shared.d.ts +3 -7
  211. package/dist/hooks/shared.js +1 -8
  212. package/dist/http-util.d.ts +2 -3
  213. package/dist/http-util.js +3 -0
  214. package/dist/importers/core.d.ts +2 -9
  215. package/dist/importers/core.js +15 -30
  216. package/dist/importers/sources.js +2 -1
  217. package/dist/importers/vault.js +2 -20
  218. package/dist/incidents.d.ts +1 -1
  219. package/dist/incidents.js +1 -1
  220. package/dist/instruction-detect.d.ts +1 -1
  221. package/dist/instruction-detect.js +1 -1
  222. package/dist/invalidation.d.ts +3 -0
  223. package/dist/invalidation.js +160 -114
  224. package/dist/json.d.ts +5 -0
  225. package/dist/json.js +4 -0
  226. package/dist/judgment.js +1 -2
  227. package/dist/local-embedding.js +1 -1
  228. package/dist/mcp/admin-tools.js +7 -17
  229. package/dist/mcp/format.js +1 -1
  230. package/dist/mcp/framing.js +3 -6
  231. package/dist/mcp/protocol.d.ts +2 -5
  232. package/dist/mcp/protocol.js +1 -3
  233. package/dist/mcp/recall-tools.js +12 -15
  234. package/dist/mcp/request.js +4 -3
  235. package/dist/mcp/session-state.js +2 -3
  236. package/dist/mcp/stdio.js +2 -1
  237. package/dist/mcp/tools.js +9 -6
  238. package/dist/memory-value-weights.d.ts +5 -8
  239. package/dist/memory-value-weights.js +5 -8
  240. package/dist/memory-value.d.ts +13 -13
  241. package/dist/memory-value.js +26 -37
  242. package/dist/memory.d.ts +20 -22
  243. package/dist/memory.js +24 -48
  244. package/dist/multihop.d.ts +1 -1
  245. package/dist/multihop.js +3 -2
  246. package/dist/owner-validation.d.ts +4 -5
  247. package/dist/owner-validation.js +4 -5
  248. package/dist/physics.d.ts +4 -4
  249. package/dist/physics.js +7 -9
  250. package/dist/policies.d.ts +9 -10
  251. package/dist/policies.js +12 -14
  252. package/dist/postinstall.js +3 -6
  253. package/dist/predictions/planning-fallacy.d.ts +9 -14
  254. package/dist/predictions/planning-fallacy.js +10 -16
  255. package/dist/predictions/store.d.ts +15 -23
  256. package/dist/predictions/store.js +36 -33
  257. package/dist/processes.d.ts +2 -7
  258. package/dist/processes.js +3 -3
  259. package/dist/project-briefs.d.ts +2 -3
  260. package/dist/project-briefs.js +9 -13
  261. package/dist/project-identity.d.ts +22 -9
  262. package/dist/project-identity.js +47 -12
  263. package/dist/project-merge.d.ts +17 -2
  264. package/dist/project-merge.js +109 -27
  265. package/dist/project-remote.d.ts +12 -0
  266. package/dist/project-remote.js +138 -0
  267. package/dist/prompt-recall.js +1 -2
  268. package/dist/rate-limit.d.ts +1 -1
  269. package/dist/rate-limit.js +1 -1
  270. package/dist/raw-archive.d.ts +9 -0
  271. package/dist/raw-archive.js +70 -53
  272. package/dist/recall-history.d.ts +19 -20
  273. package/dist/recall-history.js +24 -42
  274. package/dist/recall-pipeline.js +4 -28
  275. package/dist/recall-scope.d.ts +7 -8
  276. package/dist/recall-scope.js +7 -8
  277. package/dist/recall-trace.d.ts +5 -9
  278. package/dist/recall-trace.js +6 -10
  279. package/dist/refine-llm.d.ts +1 -1
  280. package/dist/refine-llm.js +2 -2
  281. package/dist/reject-flow.d.ts +3 -4
  282. package/dist/reject-flow.js +122 -117
  283. package/dist/rejection.d.ts +5 -6
  284. package/dist/rejection.js +7 -15
  285. package/dist/rerankers/clef.d.ts +1 -1
  286. package/dist/rerankers/jev.d.ts +1 -2
  287. package/dist/rerankers/jev.js +4 -5
  288. package/dist/rerankers/llm.d.ts +1 -2
  289. package/dist/rerankers/llm.js +1 -2
  290. package/dist/rerankers/types.d.ts +1 -2
  291. package/dist/rrf.d.ts +2 -2
  292. package/dist/rrf.js +2 -2
  293. package/dist/search/bm25-search.d.ts +1 -1
  294. package/dist/search/bm25-search.js +2 -1
  295. package/dist/search/boosts.js +2 -1
  296. package/dist/search/hybrid.d.ts +1 -1
  297. package/dist/search/hybrid.js +2 -1
  298. package/dist/search/physics-search.d.ts +1 -1
  299. package/dist/search/physics-search.js +2 -1
  300. package/dist/search/types.d.ts +2 -0
  301. package/dist/search/types.js +3 -1
  302. package/dist/secret-detect.d.ts +4 -5
  303. package/dist/secret-detect.js +6 -10
  304. package/dist/server/auth.js +5 -5
  305. package/dist/server/client-ip.js +1 -1
  306. package/dist/server/cursor.js +2 -1
  307. package/dist/server/mcp-http.js +4 -4
  308. package/dist/server/request.d.ts +3 -6
  309. package/dist/server/request.js +6 -7
  310. package/dist/server/routes/admin.js +5 -4
  311. package/dist/server/routes/customer-notes.js +6 -5
  312. package/dist/server/routes/decisions.js +4 -3
  313. package/dist/server/routes/incidents.js +7 -5
  314. package/dist/server/routes/memories.js +7 -7
  315. package/dist/server/routes/policies.js +3 -2
  316. package/dist/server/routes/predictions.js +12 -15
  317. package/dist/server/routes/processes.js +3 -2
  318. package/dist/server/routes/project-briefs.js +8 -7
  319. package/dist/server/routes/recall.js +95 -93
  320. package/dist/server/routes/skills.js +6 -5
  321. package/dist/server/types.d.ts +1 -1
  322. package/dist/server/validation.d.ts +1 -2
  323. package/dist/server/validation.js +7 -14
  324. package/dist/server-detect.js +72 -58
  325. package/dist/server.d.ts +2 -2
  326. package/dist/server.js +131 -117
  327. package/dist/shared.d.ts +17 -17
  328. package/dist/shared.js +93 -97
  329. package/dist/skills.d.ts +3 -3
  330. package/dist/skills.js +8 -8
  331. package/dist/store/audit-event.d.ts +2 -2
  332. package/dist/store/audit-event.js +1 -1
  333. package/dist/store/candidates.d.ts +2 -2
  334. package/dist/store/candidates.js +4 -3
  335. package/dist/store/conflicts.js +30 -22
  336. package/dist/store/delete-and-batch.d.ts +11 -14
  337. package/dist/store/delete-and-batch.js +40 -91
  338. package/dist/store/entry-reads.d.ts +17 -25
  339. package/dist/store/entry-reads.js +59 -40
  340. package/dist/store/entry-row.d.ts +6 -24
  341. package/dist/store/entry-row.js +6 -24
  342. package/dist/store/entry-writes.d.ts +6 -7
  343. package/dist/store/entry-writes.js +13 -11
  344. package/dist/store/handoffs.d.ts +1 -1
  345. package/dist/store/handoffs.js +7 -10
  346. package/dist/store/index-and-stats.d.ts +2 -6
  347. package/dist/store/index-and-stats.js +4 -10
  348. package/dist/store/mirrors.d.ts +6 -19
  349. package/dist/store/mirrors.js +14 -39
  350. package/dist/store/open.js +9 -31
  351. package/dist/store/rows.d.ts +5 -11
  352. package/dist/store/rows.js +6 -11
  353. package/dist/store/search-rows.d.ts +17 -34
  354. package/dist/store/search-rows.js +31 -59
  355. package/dist/store/sessions.d.ts +4 -5
  356. package/dist/store/sessions.js +5 -6
  357. package/dist/store/summaries.d.ts +13 -17
  358. package/dist/store/summaries.js +26 -70
  359. package/dist/support-bundle.js +4 -8
  360. package/dist/tenant.d.ts +1 -5
  361. package/dist/token-ledger.d.ts +1 -1
  362. package/dist/token-ledger.js +3 -5
  363. package/dist/trace.js +1 -3
  364. package/dist/version.d.ts +1 -1
  365. package/dist/version.js +1 -1
  366. package/dist/working-memory.d.ts +1 -1
  367. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  368. package/extensions/openclaw-plugin/package.json +1 -1
  369. package/openclaw.plugin.json +1 -1
  370. package/package.json +1 -1
package/dist/doctor.js CHANGED
@@ -139,7 +139,8 @@ function projectsCheck(globalRoot) {
139
139
  const r = planProjectRepair(db, globalRoot, resolveTenantId({}));
140
140
  const found = [
141
141
  r.copies.length > 0 ? `${r.copies.length} imported notes copied under the wrong project` : '',
142
- r.folds.length > 0 ? `${r.folds.length} old project names that now resolve to another project` : '',
142
+ r.folds.length > 0 ? `old project names that now resolve to another project: ${r.folds.map((f) => `${f.from} -> ${f.into}`).join(', ')}` : '',
143
+ r.collisions.length > 0 ? `old project names shared by several projects, folded by hand only: ${r.collisions.map((c) => `${c.name} (${c.ids.join(', ')})`).join('; ')}` : '',
143
144
  r.toProject.length + r.setAside.length > 0 ? `${r.toProject.length + r.setAside.length} merged memories tagged user-global` : '',
144
145
  ].filter((s) => s !== '');
145
146
  return found.length === 0
@@ -154,124 +155,138 @@ function projectsCheck(globalRoot) {
154
155
  closeHippoDb(db);
155
156
  }
156
157
  }
158
+ function nodeCheck(node) {
159
+ return versionAtLeast(node, MIN_NODE)
160
+ ? { id: 'node', status: 'pass', detail: `Node.js ${node}` }
161
+ : { id: 'node', status: 'fail', detail: `Node.js ${node} is older than ${MIN_NODE}`, fix: `Install Node.js ${MIN_NODE} or newer` };
162
+ }
163
+ /** The store the checks run against (project first, then global), and the check that says which. */
164
+ function storeCheck(local, globalRoot, hasGlobal) {
165
+ if (local !== null && isInitialized(local)) {
166
+ return { store: local, check: { id: 'store', status: 'pass', detail: `project store at ${local}${hasGlobal ? ` (global store at ${globalRoot} too)` : ''}` } };
167
+ }
168
+ if (local !== null) {
169
+ // The walk stops at the first .hippo it finds, so a bare one (no hippo.db) blocks a parent or global store too.
170
+ return { store: null, check: { id: 'store', status: 'fail', detail: `${local} has no hippo.db, so hippo commands run here stop at it`, fix: `run hippo init in ${path.dirname(local)}, or remove that .hippo folder` } };
171
+ }
172
+ if (hasGlobal) {
173
+ return { store: globalRoot, check: { id: 'store', status: 'warn', detail: `no project store here; using the global store at ${globalRoot}`, fix: 'hippo init (in the project root)' } };
174
+ }
175
+ return { store: null, check: { id: 'store', status: 'fail', detail: 'no hippo store found (project or global)', fix: 'hippo init (in the project root), or hippo init --global' } };
176
+ }
177
+ function schemaCheck(have, want) {
178
+ return have === want
179
+ ? { id: 'schema', status: 'pass', detail: `database schema v${have}` }
180
+ : have > want
181
+ ? { id: 'schema', status: 'fail', detail: `database schema v${have} is newer than this hippo (v${want})`, fix: 'npm install -g hippo-memory@latest' }
182
+ : { id: 'schema', status: 'info', detail: `database schema v${have}; hippo migrates it to v${want} on the next write` };
183
+ }
184
+ function memoriesCheck(db) {
185
+ const memories = countTableRows(db, 'memories');
186
+ const dormant = countTableRows(db, 'dormant_memories');
187
+ const memoryCheck = { id: 'memories', status: 'info', detail: `${memories ?? '?'} memories${dormant !== null ? `, ${dormant} dormant` : ''}` };
188
+ if (memories === 0) {
189
+ memoryCheck.status = 'warn';
190
+ memoryCheck.fix = 'hippo learn --git (seed lessons from git history)';
191
+ }
192
+ return memoryCheck;
193
+ }
194
+ function tokensCheck(db, since) {
195
+ try {
196
+ // SAFETY: COUNT/SUM aggregate row.
197
+ const row = db.prepare(`SELECT COUNT(CASE WHEN event = 'inject' THEN 1 END) AS n,
198
+ COALESCE(SUM(CASE WHEN event = 'inject' THEN tokens END), 0) AS t,
199
+ COALESCE(SUM(CASE WHEN event = 'reread' THEN tokens END), 0) AS r
200
+ FROM token_ledger WHERE ts >= ?`).get(since);
201
+ return {
202
+ id: 'tokens',
203
+ status: 'info',
204
+ detail: `${Number(row?.n ?? 0)} memory blocks sent to agents in 7 days, about ${Number(row?.t ?? 0)} tokens sent and ${Number(row?.r ?? 0)} re-read by later model calls (hippo tokens for detail)`,
205
+ };
206
+ }
207
+ catch {
208
+ return { id: 'tokens', status: 'info', detail: 'no token ledger yet (created on the next write)' };
209
+ }
210
+ }
211
+ /** Checks that read the store's database; a failed open becomes a failed schema check after whatever already passed. */
212
+ function databaseChecks(store, now) {
213
+ const checks = [];
214
+ let db = null;
215
+ try {
216
+ db = openHippoDbReadOnly(store);
217
+ const have = getSchemaVersion(db);
218
+ checks.push(schemaCheck(have, getCurrentSchemaVersion()));
219
+ checks.push(memoriesCheck(db));
220
+ const since = new Date(now.getTime() - 7 * 86_400_000).toISOString();
221
+ checks.push(tokensCheck(db, since));
222
+ checks.push(failuresCheck(db, since, have));
223
+ checks.push(sleepCheck(db, now));
224
+ checks.push(compactionsCheck(db, now));
225
+ }
226
+ catch (err) {
227
+ checks.push({
228
+ id: 'schema',
229
+ status: 'fail',
230
+ detail: `cannot open the database: ${err instanceof Error ? err.message : String(err)}`,
231
+ fix: err instanceof IncompatibleBinaryError ? 'npm install -g hippo-memory@latest' : 'check file permissions on the .hippo folder',
232
+ });
233
+ }
234
+ finally {
235
+ if (db !== null)
236
+ closeHippoDb(db);
237
+ }
238
+ return checks;
239
+ }
240
+ // Each hook and what it does, so a partial install says what is missing.
241
+ const CLAUDE_CODE_HOOKS = [
242
+ ['hippo context --pinned-only', 'per-prompt memory'],
243
+ ['hippo session-end', 'session-end capture and sleep'],
244
+ ['hippo pre-compact', 'compaction snapshot and memories request'],
245
+ ['hippo compact-resume', 'resume after compaction'],
246
+ ['hippo post-compact', 'saving the memories a compaction lists'],
247
+ ['hippo capture-error', 'failed-tool capture'],
248
+ ];
249
+ function claudeCodeCheck(home) {
250
+ const claudeDir = path.join(home, '.claude');
251
+ if (!fs.existsSync(claudeDir)) {
252
+ return { id: 'claude-code', status: 'info', detail: 'Claude Code not found; other agents can use hippo over MCP (hippo mcp)' };
253
+ }
254
+ const settings = readJson(path.join(claudeDir, 'settings.json'));
255
+ const text = settings === null ? '' : JSON.stringify(settings);
256
+ const viaPlugin = /hippo-memory@/.test(text);
257
+ const missing = CLAUDE_CODE_HOOKS.filter(([marker]) => !text.includes(marker)).map(([, what]) => what);
258
+ if (viaPlugin) {
259
+ return { id: 'claude-code', status: 'pass', detail: 'Claude Code: hippo plugin enabled (all hooks, including compaction and failed-tool capture)' };
260
+ }
261
+ if (missing.length === 0) {
262
+ return { id: 'claude-code', status: 'pass', detail: 'Claude Code: all hippo hooks installed, including compaction and failed-tool capture' };
263
+ }
264
+ if (missing.length === CLAUDE_CODE_HOOKS.length) {
265
+ return { id: 'claude-code', status: 'warn', detail: 'Claude Code found, but no hippo hooks are installed', fix: 'hippo hook install claude-code' };
266
+ }
267
+ return { id: 'claude-code', status: 'warn', detail: `Claude Code: hippo hooks missing for ${missing.join(', ')}`, fix: 'hippo hook install claude-code (adds only what is missing)' };
268
+ }
157
269
  /** Run every check. Never throws for a broken install; broken parts become failed checks. */
158
270
  export function runDoctor(opts) {
159
271
  const cwd = opts.cwd ?? process.cwd();
160
272
  const home = opts.home ?? os.homedir();
161
273
  const now = opts.now ?? new Date();
162
274
  const checks = [];
163
- const node = opts.nodeVersion ?? process.versions.node;
164
- checks.push(versionAtLeast(node, MIN_NODE)
165
- ? { id: 'node', status: 'pass', detail: `Node.js ${node}` }
166
- : { id: 'node', status: 'fail', detail: `Node.js ${node} is older than ${MIN_NODE}`, fix: `Install Node.js ${MIN_NODE} or newer` });
275
+ checks.push(nodeCheck(opts.nodeVersion ?? process.versions.node));
167
276
  const local = findHippoStoreDir(cwd);
168
277
  const globalRoot = getGlobalRoot();
169
278
  const hasGlobal = isInitialized(globalRoot);
170
- let store = null;
171
- if (local !== null && isInitialized(local)) {
172
- store = local;
173
- checks.push({ id: 'store', status: 'pass', detail: `project store at ${local}${hasGlobal ? ` (global store at ${globalRoot} too)` : ''}` });
174
- }
175
- else if (local !== null) {
176
- // The walk stops at the first .hippo it finds, so a bare one (no hippo.db) blocks a parent or global store too.
177
- checks.push({ id: 'store', status: 'fail', detail: `${local} has no hippo.db, so hippo commands run here stop at it`, fix: `run hippo init in ${path.dirname(local)}, or remove that .hippo folder` });
178
- }
179
- else if (hasGlobal) {
180
- store = globalRoot;
181
- checks.push({ id: 'store', status: 'warn', detail: `no project store here; using the global store at ${globalRoot}`, fix: 'hippo init (in the project root)' });
182
- }
183
- else {
184
- checks.push({ id: 'store', status: 'fail', detail: 'no hippo store found (project or global)', fix: 'hippo init (in the project root), or hippo init --global' });
185
- }
186
- if (store !== null) {
187
- let db = null;
188
- try {
189
- db = openHippoDbReadOnly(store);
190
- const have = getSchemaVersion(db);
191
- const want = getCurrentSchemaVersion();
192
- checks.push(have === want
193
- ? { id: 'schema', status: 'pass', detail: `database schema v${have}` }
194
- : have > want
195
- ? { id: 'schema', status: 'fail', detail: `database schema v${have} is newer than this hippo (v${want})`, fix: 'npm install -g hippo-memory@latest' }
196
- : { id: 'schema', status: 'info', detail: `database schema v${have}; hippo migrates it to v${want} on the next write` });
197
- const memories = countTableRows(db, 'memories');
198
- const dormant = countTableRows(db, 'dormant_memories');
199
- const memoryCheck = { id: 'memories', status: 'info', detail: `${memories ?? '?'} memories${dormant !== null ? `, ${dormant} dormant` : ''}` };
200
- if (memories === 0) {
201
- memoryCheck.status = 'warn';
202
- memoryCheck.fix = 'hippo learn --git (seed lessons from git history)';
203
- }
204
- checks.push(memoryCheck);
205
- const since = new Date(now.getTime() - 7 * 86_400_000).toISOString();
206
- try {
207
- // SAFETY: COUNT/SUM aggregate row.
208
- const row = db.prepare(`SELECT COUNT(CASE WHEN event = 'inject' THEN 1 END) AS n,
209
- COALESCE(SUM(CASE WHEN event = 'inject' THEN tokens END), 0) AS t,
210
- COALESCE(SUM(CASE WHEN event = 'reread' THEN tokens END), 0) AS r
211
- FROM token_ledger WHERE ts >= ?`).get(since);
212
- checks.push({
213
- id: 'tokens',
214
- status: 'info',
215
- detail: `${Number(row?.n ?? 0)} memory blocks sent to agents in 7 days, about ${Number(row?.t ?? 0)} tokens sent and ${Number(row?.r ?? 0)} re-read by later model calls (hippo tokens for detail)`,
216
- });
217
- }
218
- catch {
219
- checks.push({ id: 'tokens', status: 'info', detail: 'no token ledger yet (created on the next write)' });
220
- }
221
- checks.push(failuresCheck(db, since, have));
222
- checks.push(sleepCheck(db, now));
223
- checks.push(compactionsCheck(db, now));
224
- }
225
- catch (err) {
226
- checks.push({
227
- id: 'schema',
228
- status: 'fail',
229
- detail: `cannot open the database: ${err instanceof Error ? err.message : String(err)}`,
230
- fix: err instanceof IncompatibleBinaryError ? 'npm install -g hippo-memory@latest' : 'check file permissions on the .hippo folder',
231
- });
232
- }
233
- finally {
234
- if (db !== null)
235
- closeHippoDb(db);
236
- }
237
- }
279
+ const { store, check } = storeCheck(local, globalRoot, hasGlobal);
280
+ checks.push(check);
281
+ if (store !== null)
282
+ checks.push(...databaseChecks(store, now));
238
283
  const holdoutRateBp = store === null ? 0 : loadConfig(store).pilot.holdoutRateBp;
239
284
  if (holdoutRateBp > 0) {
240
285
  checks.push({ id: 'pilot', status: 'info', detail: `pilot holdout on: about ${holdoutRateBp / 100}% of sessions get no memories pushed by hippo (pilot.holdoutRateBp=${holdoutRateBp})` });
241
286
  }
242
287
  if (hasGlobal)
243
288
  checks.push(projectsCheck(globalRoot));
244
- const claudeDir = path.join(home, '.claude');
245
- if (fs.existsSync(claudeDir)) {
246
- const settings = readJson(path.join(claudeDir, 'settings.json'));
247
- const text = settings === null ? '' : JSON.stringify(settings);
248
- const viaPlugin = /hippo-memory@/.test(text);
249
- // Each hook and what it does, so a partial install says what is missing.
250
- const hooks = [
251
- ['hippo context --pinned-only', 'per-prompt memory'],
252
- ['hippo session-end', 'session-end capture and sleep'],
253
- ['hippo pre-compact', 'compaction snapshot and memories request'],
254
- ['hippo compact-resume', 'resume after compaction'],
255
- ['hippo post-compact', 'saving the memories a compaction lists'],
256
- ['hippo capture-error', 'failed-tool capture'],
257
- ];
258
- const missing = hooks.filter(([marker]) => !text.includes(marker)).map(([, what]) => what);
259
- if (viaPlugin) {
260
- checks.push({ id: 'claude-code', status: 'pass', detail: 'Claude Code: hippo plugin enabled (all hooks, including compaction and failed-tool capture)' });
261
- }
262
- else if (missing.length === 0) {
263
- checks.push({ id: 'claude-code', status: 'pass', detail: 'Claude Code: all hippo hooks installed, including compaction and failed-tool capture' });
264
- }
265
- else if (missing.length === hooks.length) {
266
- checks.push({ id: 'claude-code', status: 'warn', detail: 'Claude Code found, but no hippo hooks are installed', fix: 'hippo hook install claude-code' });
267
- }
268
- else {
269
- checks.push({ id: 'claude-code', status: 'warn', detail: `Claude Code: hippo hooks missing for ${missing.join(', ')}`, fix: 'hippo hook install claude-code (adds only what is missing)' });
270
- }
271
- }
272
- else {
273
- checks.push({ id: 'claude-code', status: 'info', detail: 'Claude Code not found; other agents can use hippo over MCP (hippo mcp)' });
274
- }
289
+ checks.push(claudeCodeCheck(home));
275
290
  if (isCodexPresent(home))
276
291
  checks.push(codexCheck(home));
277
292
  checks.push({ id: 'embeddings', status: 'info', detail: isEmbeddingAvailable() ? 'local embeddings available (hybrid search)' : 'embeddings not installed; recall uses BM25 (optional: hippo embed --help)' });
package/dist/dormant.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { rejectionDigest } from './rejection.js';
2
+ import { escapeLike } from './escape.js';
2
3
  const DEFAULT_LIST_LIMIT = 20;
3
4
  /**
4
5
  * Insert (or refresh) the dormant snapshot for `move.entry`. Does not touch
@@ -17,10 +18,6 @@ export function insertDormantRow(db, move) {
17
18
  dormant_at = excluded.dormant_at
18
19
  `).run(move.entry.tenantId, move.entry.id, move.entry.content, JSON.stringify(move.entry), move.reason, move.strength, move.dormantAt);
19
20
  }
20
- /** Escape LIKE metacharacters so a search term matches literally (ESCAPE '\'). */
21
- function escapeLike(term) {
22
- return term.replace(/[\\%_]/g, (ch) => `\\${ch}`);
23
- }
24
21
  /**
25
22
  * Parse a stored snapshot back into a MemoryEntry, or null when the row no
26
23
  * longer matches the snapshot it carries (edited by hand, or truncated).
@@ -8,14 +8,10 @@
8
8
  * read their key from a conventional env var. The provider is selected by
9
9
  * `config.embeddings.provider` (default `'local'`).
10
10
  *
11
- * Design contract (see docs/plans/2026-06-08-b-pluggable-embedding-provider.md):
12
- * - Local provider `id` is the BARE model string. (Historical note: this
13
- * originally guaranteed NO identity change on upgrade; since the
14
- * embed-text-format versioning in embeddings.ts (`embeddingIndexIdentity`,
15
- * `${id}#t2`, docs/plans/2026-07-09-recall-determinism.md T1), the STORED
16
- * identity carries a `#t<N>` suffix and pre-#t2 stores get exactly one
17
- * forced reindex on their next embed-touching operation — deliberate,
18
- * because their vectors were computed over path-contaminated text.)
11
+ * Design contract:
12
+ * - Local provider `id` is the BARE model string; the STORED identity adds a `#t<N>`
13
+ * embed-text-format suffix (`embeddingIndexIdentity`), so older stores reindex once:
14
+ * their vectors were computed over path-contaminated text.
19
15
  * - API provider `id` is `${kind}:${model}`; switching to/from an API embedder
20
16
  * (or a dimension change) flips the identity and triggers the existing
21
17
  * reindex-on-change path.
@@ -8,14 +8,10 @@
8
8
  * read their key from a conventional env var. The provider is selected by
9
9
  * `config.embeddings.provider` (default `'local'`).
10
10
  *
11
- * Design contract (see docs/plans/2026-06-08-b-pluggable-embedding-provider.md):
12
- * - Local provider `id` is the BARE model string. (Historical note: this
13
- * originally guaranteed NO identity change on upgrade; since the
14
- * embed-text-format versioning in embeddings.ts (`embeddingIndexIdentity`,
15
- * `${id}#t2`, docs/plans/2026-07-09-recall-determinism.md T1), the STORED
16
- * identity carries a `#t<N>` suffix and pre-#t2 stores get exactly one
17
- * forced reindex on their next embed-touching operation — deliberate,
18
- * because their vectors were computed over path-contaminated text.)
11
+ * Design contract:
12
+ * - Local provider `id` is the BARE model string; the STORED identity adds a `#t<N>`
13
+ * embed-text-format suffix (`embeddingIndexIdentity`), so older stores reindex once:
14
+ * their vectors were computed over path-contaminated text.
19
15
  * - API provider `id` is `${kind}:${model}`; switching to/from an API embedder
20
16
  * (or a dimension change) flips the identity and triggers the existing
21
17
  * reindex-on-change path.
@@ -314,7 +314,7 @@ export async function embedMemory(hippoRoot, entry, model) {
314
314
  try {
315
315
  const identity = provider.id;
316
316
  if (embeddingModelRequiresReindex(hippoRoot, identity)) {
317
- // L9: host-wide rebuild. The embedding index is keyed by entry.id
317
+ // Host-wide rebuild. The embedding index is keyed by entry.id
318
318
  // (which is tenant-scoped) but the index itself is one per hippoRoot.
319
319
  // Cross-tenant content equivalence is visible at the vector level.
320
320
  // Per-tenant indices would be a larger architecture change.
@@ -357,6 +357,57 @@ export async function embedMemory(hippoRoot, entry, model) {
357
357
  log.warn(`skipped embedding ${entry.id} (${err instanceof Error ? err.message : String(err)}); run 'hippo embed' to backfill`);
358
358
  });
359
359
  }
360
+ /** Throws when an unavailable provider is a misconfiguration rather than an intentional no-op. */
361
+ function throwIfProviderKeyMissing(hippoRoot, provider) {
362
+ // A configured (non-disabled) API provider with a missing key is a
363
+ // misconfiguration, not a no-op: surface it so programmatic callers of the
364
+ // exported embedAll() learn nothing was written. Local-not-installed and an
365
+ // explicit enabled=false stay silent no-ops (best-effort / intentional).
366
+ const cfg = loadConfig(hippoRoot).embeddings;
367
+ if (provider.kind !== 'local' &&
368
+ cfg.enabled !== false &&
369
+ provider.keyEnv &&
370
+ !envByName(provider.keyEnv)?.trim()) {
371
+ throw new Error(`Embedding provider '${provider.kind}' is configured but ${provider.keyEnv} is not set.`);
372
+ }
373
+ }
374
+ // Embed entries without a cached vector in save-checkpointed chunks.
375
+ // provider.embed batches internally (one HTTP request per batchSize for API
376
+ // providers; sequential for local). A `[]` row means that single item could
377
+ // not be embedded and is left for a later run (resumable). On a hard provider
378
+ // failure mid-backfill we persist the chunks already embedded this run rather
379
+ // than discarding paid progress, then stop and resume on the next run.
380
+ async function backfillPending(hippoRoot, provider, pending, model) {
381
+ let count = 0;
382
+ // Initialized to `undefined` (not a known-evidence literal like `null`) so
383
+ // it stays a plain `unknown` binding for the arbitrary caught value below;
384
+ // falsy either way, so `if (backfillError)` behaves identically.
385
+ let backfillError = undefined;
386
+ const SAVE_CHUNK = 64;
387
+ for (let i = 0; i < pending.length; i += SAVE_CHUNK) {
388
+ const chunk = pending.slice(i, i + SAVE_CHUNK);
389
+ let vectors;
390
+ try {
391
+ vectors = await provider.embed(chunk.map((e) => embeddingInputText(e)), 'passage');
392
+ }
393
+ catch (err) {
394
+ // Preserve the chunks already saved this run, then surface the failure
395
+ // below so the explicit `hippo embed` path never reports a false success.
396
+ backfillError = err;
397
+ break;
398
+ }
399
+ const rows = [];
400
+ for (let j = 0; j < chunk.length; j++) {
401
+ const vec = vectors[j];
402
+ if (vec && vec.length > 0)
403
+ rows.push([chunk[j].id, vec]);
404
+ else
405
+ noteSkippedEmbedding(chunk[j].id);
406
+ }
407
+ count += withVectorDb(hippoRoot, (db) => upsertVectors(db, rows, model));
408
+ }
409
+ return { count, backfillError };
410
+ }
360
411
  /**
361
412
  * Embed all entries in hippoRoot that don't already have cached vectors.
362
413
  * Prunes orphaned embeddings for memories that no longer exist.
@@ -364,22 +415,12 @@ export async function embedMemory(hippoRoot, entry, model) {
364
415
  */
365
416
  export async function embedAll(hippoRoot, model, provider = resolveEmbeddingProvider(hippoRoot, { model })) {
366
417
  if (!provider.isAvailable()) {
367
- // A configured (non-disabled) API provider with a missing key is a
368
- // misconfiguration, not a no-op: surface it so programmatic callers of the
369
- // exported embedAll() learn nothing was written. Local-not-installed and an
370
- // explicit enabled=false stay silent no-ops (best-effort / intentional).
371
- const cfg = loadConfig(hippoRoot).embeddings;
372
- if (provider.kind !== 'local' &&
373
- cfg.enabled !== false &&
374
- provider.keyEnv &&
375
- !envByName(provider.keyEnv)?.trim()) {
376
- throw new Error(`Embedding provider '${provider.kind}' is configured but ${provider.keyEnv} is not set.`);
377
- }
418
+ throwIfProviderKeyMissing(hippoRoot, provider);
378
419
  return 0;
379
420
  }
380
421
  return withEmbedLock(hippoRoot, async () => {
381
422
  const identity = provider.id;
382
- // L9: host-wide by design. embedAll backfills vectors for all tenants'
423
+ // Host-wide by design. embedAll backfills vectors for all tenants'
383
424
  // entries into the per-host embedding index. Per-tenant filtering would
384
425
  // produce partial indices and break recall.
385
426
  const entries = loadAllEntries(hippoRoot);
@@ -395,41 +436,8 @@ export async function embedAll(hippoRoot, model, provider = resolveEmbeddingProv
395
436
  deleteOrphanVectors(db);
396
437
  return storedVectorIds(db);
397
438
  });
398
- // Embed entries without a cached vector in save-checkpointed chunks.
399
- // provider.embed batches internally (one HTTP request per batchSize for API
400
- // providers; sequential for local). A `[]` row means that single item could
401
- // not be embedded and is left for a later run (resumable). On a hard provider
402
- // failure mid-backfill we persist the chunks already embedded this run rather
403
- // than discarding paid progress, then stop and resume on the next run.
404
439
  const pending = entries.filter((e) => !embedded.has(e.id));
405
- let count = 0;
406
- // Initialized to `undefined` (not a known-evidence literal like `null`) so
407
- // it stays a plain `unknown` binding for the arbitrary caught value below;
408
- // falsy either way, so `if (backfillError)` behaves identically.
409
- let backfillError = undefined;
410
- const SAVE_CHUNK = 64;
411
- for (let i = 0; i < pending.length; i += SAVE_CHUNK) {
412
- const chunk = pending.slice(i, i + SAVE_CHUNK);
413
- let vectors;
414
- try {
415
- vectors = await provider.embed(chunk.map((e) => embeddingInputText(e)), 'passage');
416
- }
417
- catch (err) {
418
- // Preserve the chunks already saved this run, then surface the failure
419
- // below so the explicit `hippo embed` path never reports a false success.
420
- backfillError = err;
421
- break;
422
- }
423
- const rows = [];
424
- for (let j = 0; j < chunk.length; j++) {
425
- const vec = vectors[j];
426
- if (vec && vec.length > 0)
427
- rows.push([chunk[j].id, vec]);
428
- else
429
- noteSkippedEmbedding(chunk[j].id);
430
- }
431
- count += withVectorDb(hippoRoot, (db) => upsertVectors(db, rows, model));
432
- }
440
+ const { count, backfillError } = await backfillPending(hippoRoot, provider, pending, model);
433
441
  saveStoredEmbeddingModel(hippoRoot, identity);
434
442
  // Partial progress is now persisted; surface a hard backfill failure so the
435
443
  // explicit embed path reports it (best-effort callers go via embedMemory).
package/dist/env.d.ts CHANGED
@@ -35,7 +35,7 @@ export declare function envTenant(): string;
35
35
  export declare function envSkipAutoIntegrations(): boolean;
36
36
  export declare function envSkipPostinstall(): boolean;
37
37
  export declare function envMcpStdio(): boolean;
38
- /** Any non-empty value counts, unlike the `=1` switches. */
38
+ /** `1` or `true`, so `0` and `false` turn it off; `true` stays on for anyone who set it that way. */
39
39
  export declare function envRequireServer(): boolean;
40
40
  export declare function envRequireSessionScopedFreshTail(): boolean;
41
41
  export declare function envStdinWaitMs(): number | undefined;
package/dist/env.js CHANGED
@@ -15,9 +15,9 @@ function positiveInt(name) {
15
15
  const parsed = Number.parseInt(raw(name) ?? '', 10);
16
16
  return parsed > 0 ? parsed : undefined;
17
17
  }
18
- /** parseInt, kept when nonzero: these sites have always used `|| default`, so a negative value passes through. */
19
- function nonZeroInt(name) {
20
- return Number.parseInt(raw(name) ?? '', 10) || undefined;
18
+ /** Trimmed, with an empty or whitespace-only value read as unset. */
19
+ function trimmed(name) {
20
+ return raw(name)?.trim() || undefined;
21
21
  }
22
22
  /** The whole environment, for code that takes it as an injectable map (agent-memory machines, owner checks, support bundles). */
23
23
  export function processEnv() {
@@ -61,10 +61,10 @@ export function envHippoHome() { return raw('HIPPO_HOME')?.trim(); }
61
61
  export function envXdgDataHome() { return raw('XDG_DATA_HOME')?.trim(); }
62
62
  export function envHomeDir() { return raw('HOME') || raw('USERPROFILE'); }
63
63
  export function envPath() { return raw('PATH'); }
64
- export function envModelCache() { return raw('HIPPO_MODEL_CACHE'); }
64
+ export function envModelCache() { return trimmed('HIPPO_MODEL_CACHE'); }
65
65
  // Session, scope and tenant.
66
- export function envHippoSessionId() { return raw('HIPPO_SESSION_ID'); }
67
- export function envClaudeCodeSessionId() { return raw('CLAUDE_CODE_SESSION_ID'); }
66
+ export function envHippoSessionId() { return trimmed('HIPPO_SESSION_ID'); }
67
+ export function envClaudeCodeSessionId() { return trimmed('CLAUDE_CODE_SESSION_ID'); }
68
68
  export function envScope() { return raw('HIPPO_SCOPE'); }
69
69
  export function envGstackSkill() { return raw('GSTACK_SKILL'); }
70
70
  export function envOpenclawSkill() { return raw('OPENCLAW_SKILL'); }
@@ -76,8 +76,8 @@ export function envTenant() {
76
76
  export function envSkipAutoIntegrations() { return isOne('HIPPO_SKIP_AUTO_INTEGRATIONS'); }
77
77
  export function envSkipPostinstall() { return isOne('HIPPO_SKIP_POSTINSTALL'); }
78
78
  export function envMcpStdio() { return isOne('HIPPO_MCP_STDIO'); }
79
- /** Any non-empty value counts, unlike the `=1` switches. */
80
- export function envRequireServer() { return Boolean(raw('HIPPO_REQUIRE_SERVER')); }
79
+ /** `1` or `true`, so `0` and `false` turn it off; `true` stays on for anyone who set it that way. */
80
+ export function envRequireServer() { return isOneOrTrue('HIPPO_REQUIRE_SERVER'); }
81
81
  export function envRequireSessionScopedFreshTail() { return isOne('HIPPO_REQUIRE_SESSION_SCOPED_FRESH_TAIL'); }
82
82
  export function envStdinWaitMs() { return positiveInt('HIPPO_STDIN_WAIT_MS'); }
83
83
  export function envLogLevel() { return raw('HIPPO_LOG')?.trim().toLowerCase() ?? ''; }
@@ -89,18 +89,18 @@ export function envApiKey() { return raw('HIPPO_API_KEY'); }
89
89
  export function envClientIpHeader() { return raw('HIPPO_CLIENT_IP_HEADER')?.trim().toLowerCase(); }
90
90
  export function envTrustedProxies() { return raw('HIPPO_TRUSTED_PROXIES'); }
91
91
  export function envMcpSseMaxStreams() { return positiveInt('MCP_SSE_MAX_STREAMS'); }
92
- export function envMcpSseHeartbeatMs() { return nonZeroInt('MCP_SSE_HEARTBEAT_MS'); }
93
- export function envMcpSseMaxAgeSec() { return nonZeroInt('MCP_SSE_MAX_AGE_SEC'); }
92
+ export function envMcpSseHeartbeatMs() { return positiveInt('MCP_SSE_HEARTBEAT_MS'); }
93
+ export function envMcpSseMaxAgeSec() { return positiveInt('MCP_SSE_MAX_AGE_SEC'); }
94
94
  // LLM and reranker credentials and knobs.
95
95
  export function envAnthropicApiKey() { return raw('ANTHROPIC_API_KEY'); }
96
96
  export function envLlmTimeoutMs() { return positiveInt('HIPPO_LLM_TIMEOUT_MS'); }
97
- export function envTypesafeApiKey() { return raw('TYPESAFE_API_KEY'); }
97
+ export function envTypesafeApiKey() { return trimmed('TYPESAFE_API_KEY'); }
98
98
  export function envJevTimeoutMs() { return positiveInt('HIPPO_JEV_TIMEOUT_MS'); }
99
99
  export function envJevModel() { return raw('HIPPO_JEV_MODEL'); }
100
100
  export function envLlmRerankerUrl() { return raw('HIPPO_LLM_RERANKER_URL'); }
101
101
  export function envLlmRerankerKey() { return raw('HIPPO_LLM_RERANKER_KEY'); }
102
102
  export function envLlmRerankerModel() { return raw('HIPPO_LLM_RERANKER_MODEL'); }
103
- export function envLlmRerankerTimeoutMs() { return nonZeroInt('HIPPO_LLM_RERANKER_TIMEOUT_MS'); }
103
+ export function envLlmRerankerTimeoutMs() { return positiveInt('HIPPO_LLM_RERANKER_TIMEOUT_MS'); }
104
104
  export function envClefEndpoint() { return raw('HIPPO_CLEF_ENDPOINT')?.trim(); }
105
105
  export function envClefEndpointToken() { return raw('HIPPO_CLEF_ENDPOINT_TOKEN')?.trim() || undefined; }
106
106
  export function envClefTimeoutMs() { return raw('HIPPO_CLEF_TIMEOUT_MS'); }
@@ -0,0 +1,5 @@
1
+ /** Escape LIKE metacharacters for a query that declares `ESCAPE '\'`. */
2
+ export declare function escapeLike(term: string): string;
3
+ /** Escape a string for use as a literal inside a RegExp source. */
4
+ export declare function escapeRegex(s: string): string;
5
+ //# sourceMappingURL=escape.d.ts.map
package/dist/escape.js ADDED
@@ -0,0 +1,10 @@
1
+ // Leaf module: literal-match escapes, kept in one place so every LIKE and RegExp caller escapes the same set.
2
+ /** Escape LIKE metacharacters for a query that declares `ESCAPE '\'`. */
3
+ export function escapeLike(term) {
4
+ return term.replace(/[%_\\]/g, '\\$&');
5
+ }
6
+ /** Escape a string for use as a literal inside a RegExp source. */
7
+ export function escapeRegex(s) {
8
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
9
+ }
10
+ //# sourceMappingURL=escape.js.map
@@ -1,6 +1,5 @@
1
1
  /**
2
- * Statistics and cost accounting for the token-efficiency evals (ROADMAP
3
- * Part IX, TE3-TE5).
2
+ * Statistics and cost accounting for the token-efficiency evals.
4
3
  *
5
4
  * - Cost: price provider usage over four buckets (uncached input, cache
6
5
  * write, cache read, output). Raw token counts overstate savings when most
@@ -1,6 +1,5 @@
1
1
  /**
2
- * Statistics and cost accounting for the token-efficiency evals (ROADMAP
3
- * Part IX, TE3-TE5).
2
+ * Statistics and cost accounting for the token-efficiency evals.
4
3
  *
5
4
  * - Cost: price provider usage over four buckets (uncached input, cache
6
5
  * write, cache read, output). Raw token counts overstate savings when most