hippo-memory 1.59.0 → 1.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (596) hide show
  1. package/README.md +1 -1
  2. package/dist/ablation.js +9 -27
  3. package/dist/agent-memories/apply.js +4 -1
  4. package/dist/agent-memories/claude-code.js +1 -1
  5. package/dist/agent-memories/codex.js +1 -1
  6. package/dist/agent-memories/gemini.js +1 -1
  7. package/dist/agent-memories/legacy.js +1 -1
  8. package/dist/agent-memories/source.js +1 -1
  9. package/dist/agent-memories/sync.js +8 -3
  10. package/dist/ambient-store.d.ts +14 -0
  11. package/dist/ambient-store.js +90 -0
  12. package/dist/ambient.d.ts +23 -0
  13. package/dist/ambient.js +72 -50
  14. package/dist/api/assemble.d.ts +93 -0
  15. package/dist/api/assemble.js +152 -0
  16. package/dist/api/audit.d.ts +17 -0
  17. package/dist/api/audit.js +23 -0
  18. package/dist/api/auth.d.ts +79 -0
  19. package/dist/api/auth.js +178 -0
  20. package/dist/api/context-types.d.ts +105 -0
  21. package/dist/api/context-types.js +3 -0
  22. package/dist/api/context.d.ts +31 -0
  23. package/dist/api/context.js +705 -0
  24. package/dist/api/dormant.d.ts +30 -0
  25. package/dist/api/dormant.js +140 -0
  26. package/dist/api/drill-down.d.ts +84 -0
  27. package/dist/api/drill-down.js +123 -0
  28. package/dist/api/forget.d.ts +57 -0
  29. package/dist/api/forget.js +87 -0
  30. package/dist/api/goals.d.ts +18 -0
  31. package/dist/api/goals.js +33 -0
  32. package/dist/api/learn.d.ts +31 -0
  33. package/dist/api/learn.js +88 -0
  34. package/dist/api/outcome.d.ts +61 -0
  35. package/dist/api/outcome.js +66 -0
  36. package/dist/api/promote.d.ts +54 -0
  37. package/dist/api/promote.js +203 -0
  38. package/dist/api/quarantine.d.ts +24 -0
  39. package/dist/api/quarantine.js +121 -0
  40. package/dist/api/recall-types.d.ts +390 -0
  41. package/dist/api/recall-types.js +3 -0
  42. package/dist/api/recall.d.ts +36 -0
  43. package/dist/api/recall.js +634 -0
  44. package/dist/api/remember.d.ts +35 -0
  45. package/dist/api/remember.js +43 -0
  46. package/dist/api/sleep.d.ts +136 -0
  47. package/dist/api/sleep.js +271 -0
  48. package/dist/api/tokens.d.ts +26 -0
  49. package/dist/api/tokens.js +60 -0
  50. package/dist/api/types.d.ts +56 -0
  51. package/dist/api/types.js +38 -0
  52. package/dist/api.d.ts +20 -1260
  53. package/dist/api.js +25 -2711
  54. package/dist/audit.d.ts +3 -0
  55. package/dist/audit.js +7 -3
  56. package/dist/auth.d.ts +45 -4
  57. package/dist/auth.js +125 -48
  58. package/dist/autolearn.js +7 -4
  59. package/dist/capture/command.d.ts +33 -0
  60. package/dist/capture/command.js +264 -0
  61. package/dist/capture/compact.d.ts +44 -0
  62. package/dist/capture/compact.js +354 -0
  63. package/dist/capture/extract.d.ts +21 -0
  64. package/dist/capture/extract.js +464 -0
  65. package/dist/capture/transcript.d.ts +40 -0
  66. package/dist/capture/transcript.js +193 -0
  67. package/dist/capture-error.js +2 -1
  68. package/dist/churn-git.js +4 -2
  69. package/dist/cli/audit.d.ts +3 -0
  70. package/dist/cli/audit.js +159 -0
  71. package/dist/cli/auth.d.ts +2 -0
  72. package/dist/cli/auth.js +171 -0
  73. package/dist/cli/briefs.d.ts +4 -0
  74. package/dist/cli/briefs.js +435 -0
  75. package/dist/cli/card.d.ts +3 -0
  76. package/dist/cli/card.js +333 -0
  77. package/dist/cli/context.d.ts +15 -0
  78. package/dist/cli/context.js +366 -0
  79. package/dist/cli/continuity.d.ts +6 -0
  80. package/dist/cli/continuity.js +445 -0
  81. package/dist/cli/curate.d.ts +19 -0
  82. package/dist/cli/curate.js +555 -0
  83. package/dist/cli/dag.d.ts +5 -0
  84. package/dist/cli/dag.js +177 -0
  85. package/dist/cli/decisions.d.ts +4 -0
  86. package/dist/cli/decisions.js +528 -0
  87. package/dist/cli/eval.d.ts +5 -0
  88. package/dist/cli/eval.js +213 -0
  89. package/dist/cli/explain.d.ts +4 -0
  90. package/dist/cli/explain.js +150 -0
  91. package/dist/cli/goals.d.ts +2 -0
  92. package/dist/cli/goals.js +196 -0
  93. package/dist/cli/hook-blocks.d.ts +18 -0
  94. package/dist/cli/hook-blocks.js +233 -0
  95. package/dist/cli/init.d.ts +2 -0
  96. package/dist/cli/init.js +305 -0
  97. package/dist/cli/maintenance.d.ts +4 -0
  98. package/dist/cli/maintenance.js +179 -0
  99. package/dist/cli/playbooks.d.ts +4 -0
  100. package/dist/cli/playbooks.js +556 -0
  101. package/dist/cli/projects.js +1 -1
  102. package/dist/cli/recall.d.ts +7 -0
  103. package/dist/cli/recall.js +597 -0
  104. package/dist/cli/remember.d.ts +5 -0
  105. package/dist/cli/remember.js +442 -0
  106. package/dist/cli/serve.d.ts +5 -0
  107. package/dist/cli/serve.js +40 -0
  108. package/dist/cli/session-hooks.d.ts +29 -0
  109. package/dist/cli/session-hooks.js +637 -0
  110. package/dist/cli/setup.d.ts +4 -0
  111. package/dist/cli/setup.js +376 -0
  112. package/dist/cli/shared.d.ts +15 -19
  113. package/dist/cli/shared.js +50 -341
  114. package/dist/cli/slack.d.ts +2 -0
  115. package/dist/cli/slack.js +171 -0
  116. package/dist/cli/status.d.ts +18 -0
  117. package/dist/cli/status.js +400 -0
  118. package/dist/cli/transfer.d.ts +10 -0
  119. package/dist/cli/transfer.js +438 -0
  120. package/dist/cli/usage.d.ts +85 -0
  121. package/dist/cli/usage.js +741 -0
  122. package/dist/cli.d.ts +319 -13
  123. package/dist/cli.js +436 -8580
  124. package/dist/client.js +15 -8
  125. package/dist/compaction-record.js +12 -9
  126. package/dist/config.js +12 -11
  127. package/dist/connectors/github/backfill.js +94 -87
  128. package/dist/connectors/github/cli-impl.js +5 -3
  129. package/dist/connectors/github/dlq.js +67 -54
  130. package/dist/connectors/github/ingest.js +34 -35
  131. package/dist/connectors/github/tenant-routing.js +3 -2
  132. package/dist/connectors/github/webhook.js +135 -216
  133. package/dist/connectors/slack/dlq.js +49 -61
  134. package/dist/connectors/slack/ingest.js +55 -48
  135. package/dist/connectors/slack/tenant-routing.js +4 -3
  136. package/dist/connectors/slack/webhook.js +72 -74
  137. package/dist/consolidate/conflicts.d.ts +10 -0
  138. package/dist/consolidate/conflicts.js +178 -0
  139. package/dist/consolidate/decay.d.ts +12 -0
  140. package/dist/consolidate/decay.js +145 -0
  141. package/dist/consolidate/llm-passes.d.ts +3 -0
  142. package/dist/consolidate/llm-passes.js +141 -0
  143. package/dist/consolidate/merge.d.ts +7 -0
  144. package/dist/consolidate/merge.js +251 -0
  145. package/dist/consolidate/physics-pass.d.ts +3 -0
  146. package/dist/consolidate/physics-pass.js +60 -0
  147. package/dist/consolidate/run.d.ts +69 -0
  148. package/dist/consolidate/run.js +76 -0
  149. package/dist/consolidate/sleep.d.ts +18 -0
  150. package/dist/consolidate/sleep.js +209 -0
  151. package/dist/consolidate/traces.d.ts +4 -0
  152. package/dist/consolidate/traces.js +178 -0
  153. package/dist/context-auto.js +7 -11
  154. package/dist/context-render.d.ts +1 -1
  155. package/dist/context-render.js +1 -1
  156. package/dist/customer-notes.d.ts +3 -0
  157. package/dist/customer-notes.js +6 -4
  158. package/dist/dag.js +13 -12
  159. package/dist/dashboard-actions.d.ts +20 -0
  160. package/dist/dashboard-actions.js +88 -0
  161. package/dist/dashboard-params.d.ts +45 -0
  162. package/dist/dashboard-params.js +127 -0
  163. package/dist/dashboard-queries.d.ts +15 -0
  164. package/dist/dashboard-queries.js +355 -0
  165. package/dist/dashboard-snapshot.d.ts +138 -0
  166. package/dist/dashboard-snapshot.js +308 -0
  167. package/dist/dashboard-types.d.ts +163 -0
  168. package/dist/dashboard-types.js +3 -0
  169. package/dist/dashboard.d.ts +6 -7
  170. package/dist/dashboard.js +231 -203
  171. package/dist/db/busy.d.ts +5 -0
  172. package/dist/db/busy.js +24 -0
  173. package/dist/db/continuity.d.ts +5 -0
  174. package/dist/db/continuity.js +145 -0
  175. package/dist/db/meta.d.ts +8 -0
  176. package/dist/db/meta.js +35 -0
  177. package/dist/db/migrate.d.ts +9 -0
  178. package/dist/db/migrate.js +138 -0
  179. package/dist/db/migrations/index.d.ts +5 -0
  180. package/dist/db/migrations/index.js +109 -0
  181. package/dist/db/migrations/types.d.ts +14 -0
  182. package/dist/db/migrations/types.js +2 -0
  183. package/dist/db/migrations/v01.d.ts +3 -0
  184. package/dist/db/migrations/v01.js +38 -0
  185. package/dist/db/migrations/v02.d.ts +3 -0
  186. package/dist/db/migrations/v02.js +21 -0
  187. package/dist/db/migrations/v03.d.ts +3 -0
  188. package/dist/db/migrations/v03.js +22 -0
  189. package/dist/db/migrations/v04.d.ts +3 -0
  190. package/dist/db/migrations/v04.js +28 -0
  191. package/dist/db/migrations/v05.d.ts +3 -0
  192. package/dist/db/migrations/v05.js +21 -0
  193. package/dist/db/migrations/v06.d.ts +3 -0
  194. package/dist/db/migrations/v06.js +25 -0
  195. package/dist/db/migrations/v07.d.ts +3 -0
  196. package/dist/db/migrations/v07.js +13 -0
  197. package/dist/db/migrations/v08.d.ts +3 -0
  198. package/dist/db/migrations/v08.js +8 -0
  199. package/dist/db/migrations/v09.d.ts +3 -0
  200. package/dist/db/migrations/v09.js +13 -0
  201. package/dist/db/migrations/v10.d.ts +3 -0
  202. package/dist/db/migrations/v10.js +17 -0
  203. package/dist/db/migrations/v11.d.ts +3 -0
  204. package/dist/db/migrations/v11.js +15 -0
  205. package/dist/db/migrations/v12.d.ts +3 -0
  206. package/dist/db/migrations/v12.js +11 -0
  207. package/dist/db/migrations/v13.d.ts +3 -0
  208. package/dist/db/migrations/v13.js +15 -0
  209. package/dist/db/migrations/v14.d.ts +3 -0
  210. package/dist/db/migrations/v14.js +66 -0
  211. package/dist/db/migrations/v15.d.ts +3 -0
  212. package/dist/db/migrations/v15.js +42 -0
  213. package/dist/db/migrations/v16.d.ts +3 -0
  214. package/dist/db/migrations/v16.js +61 -0
  215. package/dist/db/migrations/v17.d.ts +3 -0
  216. package/dist/db/migrations/v17.js +46 -0
  217. package/dist/db/migrations/v18.d.ts +3 -0
  218. package/dist/db/migrations/v18.js +60 -0
  219. package/dist/db/migrations/v19.d.ts +3 -0
  220. package/dist/db/migrations/v19.js +28 -0
  221. package/dist/db/migrations/v20.d.ts +3 -0
  222. package/dist/db/migrations/v20.js +42 -0
  223. package/dist/db/migrations/v21.d.ts +3 -0
  224. package/dist/db/migrations/v21.js +16 -0
  225. package/dist/db/migrations/v22.d.ts +3 -0
  226. package/dist/db/migrations/v22.js +82 -0
  227. package/dist/db/migrations/v23.d.ts +3 -0
  228. package/dist/db/migrations/v23.js +48 -0
  229. package/dist/db/migrations/v24.d.ts +3 -0
  230. package/dist/db/migrations/v24.js +72 -0
  231. package/dist/db/migrations/v25.d.ts +3 -0
  232. package/dist/db/migrations/v25.js +46 -0
  233. package/dist/db/migrations/v26.d.ts +3 -0
  234. package/dist/db/migrations/v26.js +23 -0
  235. package/dist/db/migrations/v27.d.ts +3 -0
  236. package/dist/db/migrations/v27.js +57 -0
  237. package/dist/db/migrations/v28.d.ts +3 -0
  238. package/dist/db/migrations/v28.js +38 -0
  239. package/dist/db/migrations/v29.d.ts +3 -0
  240. package/dist/db/migrations/v29.js +78 -0
  241. package/dist/db/migrations/v30.d.ts +3 -0
  242. package/dist/db/migrations/v30.js +92 -0
  243. package/dist/db/migrations/v31.d.ts +3 -0
  244. package/dist/db/migrations/v31.js +74 -0
  245. package/dist/db/migrations/v32.d.ts +3 -0
  246. package/dist/db/migrations/v32.js +102 -0
  247. package/dist/db/migrations/v33.d.ts +3 -0
  248. package/dist/db/migrations/v33.js +104 -0
  249. package/dist/db/migrations/v34.d.ts +3 -0
  250. package/dist/db/migrations/v34.js +93 -0
  251. package/dist/db/migrations/v35.d.ts +3 -0
  252. package/dist/db/migrations/v35.js +98 -0
  253. package/dist/db/migrations/v36.d.ts +3 -0
  254. package/dist/db/migrations/v36.js +98 -0
  255. package/dist/db/migrations/v37.d.ts +3 -0
  256. package/dist/db/migrations/v37.js +219 -0
  257. package/dist/db/migrations/v38.d.ts +3 -0
  258. package/dist/db/migrations/v38.js +277 -0
  259. package/dist/db/migrations/v39.d.ts +3 -0
  260. package/dist/db/migrations/v39.js +59 -0
  261. package/dist/db/migrations/v40.d.ts +3 -0
  262. package/dist/db/migrations/v40.js +74 -0
  263. package/dist/db/migrations/v41.d.ts +3 -0
  264. package/dist/db/migrations/v41.js +43 -0
  265. package/dist/db/migrations/v42.d.ts +3 -0
  266. package/dist/db/migrations/v42.js +41 -0
  267. package/dist/db/migrations/v43.d.ts +3 -0
  268. package/dist/db/migrations/v43.js +67 -0
  269. package/dist/db/migrations/v44.d.ts +3 -0
  270. package/dist/db/migrations/v44.js +28 -0
  271. package/dist/db/migrations/v45.d.ts +3 -0
  272. package/dist/db/migrations/v45.js +30 -0
  273. package/dist/db/migrations/v46.d.ts +3 -0
  274. package/dist/db/migrations/v46.js +25 -0
  275. package/dist/db/migrations/v47.d.ts +3 -0
  276. package/dist/db/migrations/v47.js +17 -0
  277. package/dist/db/migrations/v48.d.ts +3 -0
  278. package/dist/db/migrations/v48.js +10 -0
  279. package/dist/db/migrations/v49.d.ts +3 -0
  280. package/dist/db/migrations/v49.js +31 -0
  281. package/dist/db/migrations/v50.d.ts +3 -0
  282. package/dist/db/migrations/v50.js +67 -0
  283. package/dist/db/migrations/v51.d.ts +3 -0
  284. package/dist/db/migrations/v51.js +14 -0
  285. package/dist/db/migrations/v52.d.ts +3 -0
  286. package/dist/db/migrations/v52.js +7 -0
  287. package/dist/db/open.d.ts +23 -0
  288. package/dist/db/open.js +146 -0
  289. package/dist/db/sqlite.d.ts +21 -0
  290. package/dist/db/sqlite.js +8 -0
  291. package/dist/db/tables.d.ts +7 -0
  292. package/dist/db/tables.js +38 -0
  293. package/dist/db.d.ts +6 -46
  294. package/dist/db.js +5 -3036
  295. package/dist/decisions.d.ts +4 -1
  296. package/dist/decisions.js +9 -7
  297. package/dist/dedupe.js +3 -2
  298. package/dist/delivery-recorder.js +5 -1
  299. package/dist/doctor.js +4 -3
  300. package/dist/dormant.js +1 -0
  301. package/dist/embedding-provider.d.ts +1 -1
  302. package/dist/embedding-provider.js +7 -5
  303. package/dist/embeddings.d.ts +9 -52
  304. package/dist/embeddings.js +47 -298
  305. package/dist/env.d.ts +75 -0
  306. package/dist/env.js +119 -0
  307. package/dist/eval-suite.js +1 -1
  308. package/dist/eval.js +2 -2
  309. package/dist/extract.js +5 -4
  310. package/dist/gated-write.js +3 -1
  311. package/dist/goals.d.ts +3 -1
  312. package/dist/goals.js +18 -0
  313. package/dist/graph/read.d.ts +73 -0
  314. package/dist/graph/read.js +325 -0
  315. package/dist/graph/rows.d.ts +45 -0
  316. package/dist/graph/rows.js +51 -0
  317. package/dist/graph/types.d.ts +83 -0
  318. package/dist/graph/types.js +11 -0
  319. package/dist/graph/write.d.ts +93 -0
  320. package/dist/{graph.js → graph/write.js} +8 -391
  321. package/dist/graph-extract.js +2 -1
  322. package/dist/graph-recall.d.ts +1 -1
  323. package/dist/graph-recall.js +2 -2
  324. package/dist/graph-stream.js +1 -1
  325. package/dist/graph-view.d.ts +1 -1
  326. package/dist/graph-view.js +1 -1
  327. package/dist/half-life-migration.d.ts +1 -1
  328. package/dist/half-life-migration.js +2 -1
  329. package/dist/handoff.js +3 -0
  330. package/dist/hooks/codex-session.d.ts +8 -0
  331. package/dist/hooks/codex-session.js +76 -0
  332. package/dist/hooks/codex-wrapper.d.ts +55 -0
  333. package/dist/hooks/codex-wrapper.js +288 -0
  334. package/dist/hooks/json-hooks.d.ts +63 -0
  335. package/dist/hooks/json-hooks.js +356 -0
  336. package/dist/hooks/opencode.d.ts +50 -0
  337. package/dist/hooks/opencode.js +202 -0
  338. package/dist/hooks/shared.d.ts +54 -0
  339. package/dist/hooks/shared.js +77 -0
  340. package/dist/http-retry.d.ts +2 -0
  341. package/dist/http-retry.js +4 -3
  342. package/dist/http-util.d.ts +3 -0
  343. package/dist/http-util.js +10 -0
  344. package/dist/{importers.d.ts → importers/core.d.ts} +13 -17
  345. package/dist/importers/core.js +141 -0
  346. package/dist/importers/markdown-parse.d.ts +41 -0
  347. package/dist/importers/markdown-parse.js +132 -0
  348. package/dist/importers/markdown.d.ts +3 -0
  349. package/dist/importers/markdown.js +92 -0
  350. package/dist/importers/sources.d.ts +7 -0
  351. package/dist/importers/sources.js +229 -0
  352. package/dist/importers/vault.d.ts +11 -0
  353. package/dist/importers/vault.js +352 -0
  354. package/dist/incidents.d.ts +3 -0
  355. package/dist/incidents.js +8 -5
  356. package/dist/index.d.ts +25 -6
  357. package/dist/index.js +23 -6
  358. package/dist/invalidation.js +2 -1
  359. package/dist/judgment.js +7 -3
  360. package/dist/keyset.d.ts +13 -0
  361. package/dist/keyset.js +8 -0
  362. package/dist/local-embedding.d.ts +13 -0
  363. package/dist/local-embedding.js +165 -0
  364. package/dist/log.d.ts +7 -0
  365. package/dist/log.js +19 -1
  366. package/dist/mcp/admin-tools.d.ts +8 -0
  367. package/dist/mcp/admin-tools.js +116 -0
  368. package/dist/mcp/format.d.ts +27 -0
  369. package/dist/mcp/format.js +135 -0
  370. package/dist/mcp/memory-tools.d.ts +5 -0
  371. package/dist/mcp/memory-tools.js +83 -0
  372. package/dist/mcp/protocol.d.ts +83 -0
  373. package/dist/mcp/protocol.js +55 -0
  374. package/dist/mcp/recall-tools.d.ts +6 -0
  375. package/dist/mcp/recall-tools.js +320 -0
  376. package/dist/mcp/request.d.ts +10 -0
  377. package/dist/mcp/request.js +163 -0
  378. package/dist/mcp/server.d.ts +4 -70
  379. package/dist/mcp/server.js +8 -1172
  380. package/dist/mcp/session-state.d.ts +11 -0
  381. package/dist/mcp/session-state.js +28 -0
  382. package/dist/mcp/stdio.d.ts +8 -0
  383. package/dist/mcp/stdio.js +79 -0
  384. package/dist/mcp/tools.d.ts +11 -0
  385. package/dist/mcp/tools.js +247 -0
  386. package/dist/memory.d.ts +8 -3
  387. package/dist/memory.js +27 -1
  388. package/dist/multihop.d.ts +1 -1
  389. package/dist/multihop.js +2 -1
  390. package/dist/owner-validation.js +2 -1
  391. package/dist/physics-state.js +10 -7
  392. package/dist/policies.d.ts +3 -0
  393. package/dist/policies.js +8 -6
  394. package/dist/postinstall.js +3 -2
  395. package/dist/predictions/planning-fallacy.d.ts +100 -0
  396. package/dist/predictions/planning-fallacy.js +190 -0
  397. package/dist/{predictions.d.ts → predictions/store.d.ts} +7 -102
  398. package/dist/predictions/store.js +434 -0
  399. package/dist/processes.d.ts +3 -0
  400. package/dist/processes.js +8 -5
  401. package/dist/project-briefs.d.ts +3 -0
  402. package/dist/project-briefs.js +7 -5
  403. package/dist/project-identity.d.ts +1 -1
  404. package/dist/project-identity.js +12 -6
  405. package/dist/project-merge.js +3 -1
  406. package/dist/quarantine.d.ts +2 -1
  407. package/dist/quarantine.js +8 -5
  408. package/dist/raw-archive-mirror-cleanup.js +2 -1
  409. package/dist/raw-archive.js +1 -1
  410. package/dist/recall-history.js +3 -2
  411. package/dist/recall-pipeline.d.ts +2 -2
  412. package/dist/recall-pipeline.js +16 -8
  413. package/dist/recall-scope.js +1 -1
  414. package/dist/recall-trace.d.ts +3 -3
  415. package/dist/recall-trace.js +10 -14
  416. package/dist/refine-llm.js +20 -11
  417. package/dist/reject-flow.js +6 -1
  418. package/dist/rerankers/clef.d.ts +2 -2
  419. package/dist/rerankers/clef.js +72 -30
  420. package/dist/rerankers/cross-encoder.js +6 -4
  421. package/dist/rerankers/jev.d.ts +4 -2
  422. package/dist/rerankers/jev.js +24 -19
  423. package/dist/rerankers/llm.d.ts +4 -2
  424. package/dist/rerankers/llm.js +61 -41
  425. package/dist/rerankers/types.d.ts +1 -1
  426. package/dist/salience.js +1 -1
  427. package/dist/same-text.d.ts +2 -0
  428. package/dist/same-text.js +4 -0
  429. package/dist/scheduler.d.ts +1 -0
  430. package/dist/scheduler.js +26 -3
  431. package/dist/scope.js +4 -3
  432. package/dist/search/as-of.d.ts +10 -0
  433. package/dist/search/as-of.js +22 -0
  434. package/dist/search/bm25-search.d.ts +14 -0
  435. package/dist/search/bm25-search.js +43 -0
  436. package/dist/search/bm25.d.ts +15 -0
  437. package/dist/search/bm25.js +54 -0
  438. package/dist/search/boosts.d.ts +54 -0
  439. package/dist/search/boosts.js +94 -0
  440. package/dist/search/breakdown.d.ts +7 -0
  441. package/dist/search/breakdown.js +20 -0
  442. package/dist/search/explain.d.ts +25 -0
  443. package/dist/search/explain.js +31 -0
  444. package/dist/search/finalize.d.ts +8 -0
  445. package/dist/search/finalize.js +52 -0
  446. package/dist/search/fusion.d.ts +27 -0
  447. package/dist/search/fusion.js +42 -0
  448. package/dist/search/hybrid-score.d.ts +20 -0
  449. package/dist/search/hybrid-score.js +73 -0
  450. package/dist/search/hybrid.d.ts +46 -0
  451. package/dist/search/hybrid.js +64 -0
  452. package/dist/search/physics-search.d.ts +29 -0
  453. package/dist/search/physics-search.js +162 -0
  454. package/dist/search/rerank.d.ts +10 -0
  455. package/dist/search/rerank.js +72 -0
  456. package/dist/search/temporal.d.ts +15 -0
  457. package/dist/search/temporal.js +45 -0
  458. package/dist/search/types.d.ts +91 -0
  459. package/dist/search/types.js +2 -0
  460. package/dist/search/vector.d.ts +30 -0
  461. package/dist/search/vector.js +71 -0
  462. package/dist/secret-detect.d.ts +2 -0
  463. package/dist/secret-detect.js +3 -1
  464. package/dist/server/auth.d.ts +52 -0
  465. package/dist/server/auth.js +221 -0
  466. package/dist/server/client-ip.d.ts +25 -0
  467. package/dist/server/client-ip.js +92 -0
  468. package/dist/server/cursor.d.ts +23 -0
  469. package/dist/server/cursor.js +58 -0
  470. package/dist/server/lifecycle.d.ts +7 -0
  471. package/dist/server/lifecycle.js +29 -0
  472. package/dist/server/mcp-http.d.ts +5 -0
  473. package/dist/server/mcp-http.js +199 -0
  474. package/dist/server/request.d.ts +33 -0
  475. package/dist/server/request.js +103 -0
  476. package/dist/server/routes/admin.d.ts +9 -0
  477. package/dist/server/routes/admin.js +158 -0
  478. package/dist/server/routes/customer-notes.d.ts +7 -0
  479. package/dist/server/routes/customer-notes.js +112 -0
  480. package/dist/server/routes/decisions.d.ts +7 -0
  481. package/dist/server/routes/decisions.js +133 -0
  482. package/dist/server/routes/incidents.d.ts +7 -0
  483. package/dist/server/routes/incidents.js +126 -0
  484. package/dist/server/routes/memories.d.ts +10 -0
  485. package/dist/server/routes/memories.js +179 -0
  486. package/dist/server/routes/policies.d.ts +8 -0
  487. package/dist/server/routes/policies.js +151 -0
  488. package/dist/server/routes/predictions.d.ts +7 -0
  489. package/dist/server/routes/predictions.js +164 -0
  490. package/dist/server/routes/processes.d.ts +7 -0
  491. package/dist/server/routes/processes.js +161 -0
  492. package/dist/server/routes/project-briefs.d.ts +8 -0
  493. package/dist/server/routes/project-briefs.js +136 -0
  494. package/dist/server/routes/recall.d.ts +8 -0
  495. package/dist/server/routes/recall.js +340 -0
  496. package/dist/server/routes/skills.d.ts +8 -0
  497. package/dist/server/routes/skills.js +150 -0
  498. package/dist/server/types.d.ts +56 -0
  499. package/dist/server/types.js +2 -0
  500. package/dist/server/validation.d.ts +14 -0
  501. package/dist/server/validation.js +91 -0
  502. package/dist/server.d.ts +7 -61
  503. package/dist/server.js +92 -2368
  504. package/dist/session-digest.d.ts +1 -1
  505. package/dist/session-digest.js +9 -2
  506. package/dist/shared.d.ts +3 -1
  507. package/dist/shared.js +46 -40
  508. package/dist/skills.d.ts +3 -0
  509. package/dist/skills.js +7 -5
  510. package/dist/stdin.js +3 -2
  511. package/dist/store/audit-event.d.ts +19 -0
  512. package/dist/store/audit-event.js +33 -0
  513. package/dist/store/candidates.d.ts +42 -0
  514. package/dist/store/candidates.js +152 -0
  515. package/dist/store/conflicts.d.ts +44 -0
  516. package/dist/store/conflicts.js +444 -0
  517. package/dist/store/delete-and-batch.d.ts +63 -0
  518. package/dist/store/delete-and-batch.js +310 -0
  519. package/dist/store/entry-reads.d.ts +92 -0
  520. package/dist/store/entry-reads.js +255 -0
  521. package/dist/store/entry-row.d.ts +67 -0
  522. package/dist/store/entry-row.js +208 -0
  523. package/dist/store/entry-writes.d.ts +46 -0
  524. package/dist/store/entry-writes.js +148 -0
  525. package/dist/store/handoffs.d.ts +26 -0
  526. package/dist/store/handoffs.js +184 -0
  527. package/dist/store/index-and-stats.d.ts +52 -0
  528. package/dist/store/index-and-stats.js +214 -0
  529. package/dist/store/markdown.d.ts +10 -0
  530. package/dist/store/markdown.js +108 -0
  531. package/dist/store/mirrors.d.ts +49 -0
  532. package/dist/store/mirrors.js +312 -0
  533. package/dist/store/open.d.ts +15 -0
  534. package/dist/store/open.js +223 -0
  535. package/dist/store/rows.d.ts +178 -0
  536. package/dist/store/rows.js +158 -0
  537. package/dist/store/search-rows.d.ts +86 -0
  538. package/dist/store/search-rows.js +254 -0
  539. package/dist/store/sessions.d.ts +83 -0
  540. package/dist/store/sessions.js +272 -0
  541. package/dist/store/summaries.d.ts +94 -0
  542. package/dist/store/summaries.js +377 -0
  543. package/dist/store/tenant-lookup.d.ts +12 -0
  544. package/dist/store/tenant-lookup.js +16 -0
  545. package/dist/store-cards.js +2 -1
  546. package/dist/summary-dirty.d.ts +4 -0
  547. package/dist/summary-dirty.js +32 -0
  548. package/dist/support-bundle.js +4 -3
  549. package/dist/tenant.js +2 -2
  550. package/dist/token-ledger.js +1 -0
  551. package/dist/tokenize.d.ts +2 -0
  552. package/dist/tokenize.js +16 -0
  553. package/dist/transcript-tail.d.ts +7 -0
  554. package/dist/transcript-tail.js +48 -0
  555. package/dist/vector-store.d.ts +27 -0
  556. package/dist/vector-store.js +210 -0
  557. package/dist/version.d.ts +2 -2
  558. package/dist/version.js +2 -2
  559. package/dist/working-memory.js +1 -1
  560. package/dist/yaml.js +36 -11
  561. package/dist-ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  562. package/dist-ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  563. package/dist-ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  564. package/dist-ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  565. package/dist-ui/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
  566. package/dist-ui/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
  567. package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  568. package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  569. package/dist-ui/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  570. package/dist-ui/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  571. package/dist-ui/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  572. package/dist-ui/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  573. package/dist-ui/assets/index-DPN7cP19.js +33 -0
  574. package/dist-ui/assets/index-dFloKRVr.css +1 -0
  575. package/dist-ui/index.html +3 -25
  576. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  577. package/extensions/openclaw-plugin/package.json +1 -1
  578. package/openclaw.plugin.json +1 -1
  579. package/package.json +2 -1
  580. package/dist/capture.d.ts +0 -155
  581. package/dist/capture.js +0 -1293
  582. package/dist/consolidate.d.ts +0 -58
  583. package/dist/consolidate.js +0 -1123
  584. package/dist/graph.d.ts +0 -245
  585. package/dist/hooks.d.ts +0 -208
  586. package/dist/hooks.js +0 -1073
  587. package/dist/importers.js +0 -897
  588. package/dist/predictions.js +0 -620
  589. package/dist/search.d.ts +0 -320
  590. package/dist/search.js +0 -968
  591. package/dist/store.d.ts +0 -744
  592. package/dist/store.js +0 -3372
  593. package/dist-ui/assets/d3-BiWEKnn4.js +0 -1
  594. package/dist-ui/assets/index-BhT8RvO6.js +0 -61
  595. package/dist-ui/assets/index-RoXXJ5dq.css +0 -1
  596. package/dist-ui/assets/three-BDgTxR1l.js +0 -4112
@@ -0,0 +1,221 @@
1
+ // Bearer and loopback auth for the HTTP server.
2
+ import { envRequireAuth } from '../env.js';
3
+ import { resolveTenantId } from '../tenant.js';
4
+ import { log } from '../log.js';
5
+ import { API_KEY_PREFIX, verifyApiKeyCached } from '../auth.js';
6
+ import { HttpError, isCrossSite, isHeaderString, LOOPBACK_HOST_HEADER } from '../http-util.js';
7
+ import { requestIds } from './request.js';
8
+ import { isJsonString } from './validation.js';
9
+ /**
10
+ * Recognise loopback remote addresses. Node reports IPv6-mapped IPv4 as
11
+ * '::ffff:127.0.0.1' on dual-stack sockets, so we accept that alongside
12
+ * the bare v4 and v6 loopbacks. Anything else is treated as remote.
13
+ */
14
+ export function isLoopback(remoteAddress) {
15
+ if (!remoteAddress)
16
+ return false;
17
+ if (remoteAddress === '127.0.0.1')
18
+ return true;
19
+ if (remoteAddress === '::1')
20
+ return true;
21
+ if (remoteAddress === '::ffff:127.0.0.1')
22
+ return true;
23
+ return false;
24
+ }
25
+ // A proxy on this host (nginx, Caddy, cloudflared) connects from loopback, so these headers mean the caller is not local.
26
+ const PROXY_HEADERS = [
27
+ 'forwarded', 'x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-real-ip', 'cf-connecting-ip', 'true-client-ip',
28
+ ];
29
+ // A browser on this machine is loopback too, so the no-key fallback also needs a local Host and a same-site caller.
30
+ function assertLocalCaller(req) {
31
+ if (!isLoopback(req.socket.remoteAddress))
32
+ throw new HttpError(401, 'auth required');
33
+ const proxyHeader = PROXY_HEADERS.find((name) => req.headers[name] !== undefined);
34
+ if (proxyHeader !== undefined) {
35
+ log.warn(`proxied loopback request refused: it carries ${proxyHeader}, so the no-key local fallback does not apply. ` +
36
+ 'Send an API key (hippo auth create, then Authorization: Bearer hk_...).', { requestId: requestIds.get(req) });
37
+ throw new HttpError(401, 'auth required');
38
+ }
39
+ const host = req.headers.host;
40
+ if ((host !== undefined && !LOOPBACK_HOST_HEADER.test(host)) || isCrossSite(req)) {
41
+ throw new HttpError(403, 'cross-site or non-local request refused; send an API key');
42
+ }
43
+ }
44
+ export function readAuthHeader(req) {
45
+ const raw = req.headers['authorization'];
46
+ if (raw === undefined)
47
+ return { kind: 'absent' };
48
+ const value = Array.isArray(raw) ? raw[0] : raw;
49
+ if (!isHeaderString(value) || value.length === 0) {
50
+ return { kind: 'malformed' };
51
+ }
52
+ const space = value.indexOf(' ');
53
+ if (space < 0)
54
+ return { kind: 'malformed' };
55
+ const scheme = value.slice(0, space);
56
+ const token = value.slice(space + 1).trim();
57
+ if (scheme.toLowerCase() !== 'bearer')
58
+ return { kind: 'malformed' };
59
+ if (token.length === 0)
60
+ return { kind: 'malformed' };
61
+ return { kind: 'bearer', token };
62
+ }
63
+ // Built-in actors are the bare names below or `<name>:<detail>`; a plain prefix would also reject `clinton@corp`.
64
+ const RESERVED_ACTOR_NAMES = [
65
+ 'api_key', 'localhost', 'cli', 'system', 'mcp', 'connector', 'sleep', 'post-compact', 'recall', 'agent-memories',
66
+ ];
67
+ /** Add-ons call this to refuse a subject that would collide with a built-in actor. */
68
+ export function isReservedActor(subject) {
69
+ const lower = subject.toLowerCase();
70
+ return RESERVED_ACTOR_NAMES.some((n) => lower === n || lower.startsWith(`${n}:`));
71
+ }
72
+ function hasControlChar(s) {
73
+ for (let i = 0; i < s.length; i++) {
74
+ const c = s.charCodeAt(i);
75
+ if (c < 0x20 || c === 0x7f)
76
+ return true;
77
+ }
78
+ return false;
79
+ }
80
+ /** Core owns the checks so a resolver cannot mint an actor that collides with a built-in one. */
81
+ function sanitiseResolved(r) {
82
+ // Each field is read once: a getter on plugin code could answer differently the second time.
83
+ const { tenantId, subject, role, scopes } = r;
84
+ // A resolver is plugin code, so the runtime checks hold even though the static types say string.
85
+ if (!isJsonString(tenantId) || tenantId.trim().length === 0)
86
+ return null;
87
+ // Core reserves `__`-prefixed tenants (`__host__`, `__unroutable__`).
88
+ const tenant = tenantId.trim();
89
+ if (tenant.startsWith('__') || tenant.length > 256 || hasControlChar(tenant))
90
+ return null;
91
+ if (!isJsonString(subject) || subject.length < 1 || subject.length > 256)
92
+ return null;
93
+ // Padding would let "system " pass the reserved-name check yet read as `system` in an audit log.
94
+ if (hasControlChar(subject) || subject !== subject.trim())
95
+ return null;
96
+ if (isReservedActor(subject))
97
+ return null;
98
+ const clean = { tenantId: tenant, subject, role: role === 'admin' ? 'admin' : 'member' };
99
+ if (Array.isArray(scopes))
100
+ clean.scopes = scopes.filter((s) => isJsonString(s));
101
+ return clean;
102
+ }
103
+ function logResolverFailure(what, raw, token) {
104
+ // The plugin's message is logged, but never the token, even if the plugin echoed it.
105
+ log.error(`auth resolver ${what}: ${raw.split(token).join('[token]')}`);
106
+ }
107
+ /** 503 when upstream throws or misses the deadline, so a stream heartbeat can tell an outage from a revocation. */
108
+ async function askResolver(resolver, token, deadlineMs) {
109
+ let timer;
110
+ const deadline = new Promise((_, reject) => {
111
+ timer = setTimeout(() => reject(new Error(`no answer within ${deadlineMs} ms`)), deadlineMs);
112
+ });
113
+ let resolved;
114
+ try {
115
+ resolved = await Promise.race([(async () => resolver(token))(), deadline]);
116
+ }
117
+ catch (err) {
118
+ logResolverFailure('threw', err instanceof Error ? err.message : 'unknown error', token);
119
+ throw new HttpError(503, 'auth provider unavailable');
120
+ }
121
+ finally {
122
+ clearTimeout(timer);
123
+ }
124
+ let clean = null;
125
+ try {
126
+ clean = resolved ? sanitiseResolved(resolved) : null;
127
+ }
128
+ catch (err) {
129
+ // A throwing getter is a resolver bug, not an outage, so it is a 401 like any malformed answer.
130
+ logResolverFailure('threw', err instanceof Error ? err.message : 'unknown error', token);
131
+ }
132
+ if (!clean)
133
+ throw new HttpError(401, 'invalid api key');
134
+ return clean;
135
+ }
136
+ const DEFAULT_RESOLVER_DEADLINE_MS = 5000;
137
+ /** Shared by buildContextWithAuth and requireAuth so the two cannot drift. */
138
+ async function resolveBearer(token, opts) {
139
+ // Routing by shape keeps key plaintext out of plugin code and stops a resolver overriding a key's identity.
140
+ if (opts.authResolver && !token.startsWith(API_KEY_PREFIX)) {
141
+ const t = opts.authResolverTimeoutMs;
142
+ const deadlineMs = t !== undefined && Number.isFinite(t) && t > 0 ? t : DEFAULT_RESOLVER_DEADLINE_MS;
143
+ return { ...(await askResolver(opts.authResolver, token, deadlineMs)), viaAuthResolver: true };
144
+ }
145
+ const key = verifyApiKeyCached(opts.hippoRoot, token);
146
+ if (!key)
147
+ throw new HttpError(401, 'invalid api key');
148
+ return { tenantId: key.tenantId, subject: `api_key:${key.keyId}`, role: key.role, scopes: key.scopes };
149
+ }
150
+ /**
151
+ * Build a per-request Context from the Authorization header and remote
152
+ * address. Throws HttpError(401) for invalid / missing credentials. Opens
153
+ * the DB only for an API-key-shaped Bearer token (or any Bearer token when no
154
+ * auth resolver is registered), so loopback no-auth requests stay cheap.
155
+ */
156
+ export async function buildContextWithAuth(req, opts) {
157
+ const auth = readAuthHeader(req);
158
+ if (auth.kind === 'malformed') {
159
+ throw new HttpError(401, 'invalid api key');
160
+ }
161
+ if (auth.kind === 'bearer') {
162
+ const id = await resolveBearer(auth.token, opts);
163
+ const actor = { subject: id.subject, role: id.role, scopes: id.scopes };
164
+ if (id.viaAuthResolver)
165
+ actor.viaAuthResolver = true;
166
+ // Only the server's own tenant owns the host; any other tenant's admin key is a tenant admin.
167
+ else if (id.role === 'admin' && id.tenantId === resolveTenantId({}))
168
+ actor.hostAdmin = true;
169
+ return { hippoRoot: opts.hippoRoot, tenantId: id.tenantId, actor };
170
+ }
171
+ // No Authorization header. Loopback-only fallback for a direct local caller (no proxy headers),
172
+ // unless HIPPO_REQUIRE_AUTH=1 forbids the local-CLI escape hatch.
173
+ if (envRequireAuth()) {
174
+ throw new HttpError(401, 'auth required');
175
+ }
176
+ assertLocalCaller(req);
177
+ // v1.12.0: loopback fallback is process-local, treat as admin.
178
+ return {
179
+ hippoRoot: opts.hippoRoot,
180
+ tenantId: resolveTenantId({}),
181
+ actor: { subject: 'localhost:cli', role: 'admin', hostAdmin: true },
182
+ };
183
+ }
184
+ /**
185
+ * Auth check for routes that do not need a tenant Context (e.g. MCP transport,
186
+ * which builds its own root resolution via findHippoRoot). Throws HttpError
187
+ * 401 the same way buildContextWithAuth does, but skips building the Context
188
+ * envelope. Loopback no-auth still passes.
189
+ */
190
+ export async function requireAuth(req, opts) {
191
+ const auth = readAuthHeader(req);
192
+ if (auth.kind === 'malformed') {
193
+ throw new HttpError(401, 'invalid api key');
194
+ }
195
+ if (auth.kind === 'bearer') {
196
+ await resolveBearer(auth.token, opts);
197
+ return;
198
+ }
199
+ if (envRequireAuth()) {
200
+ throw new HttpError(401, 'auth required');
201
+ }
202
+ assertLocalCaller(req);
203
+ }
204
+ /** Never rejects: an outage (5xx) skips one heartbeat tick, only a definite 4xx denial closes the stream. */
205
+ export async function heartbeatVerdict(req, opts) {
206
+ try {
207
+ await requireAuth(req, opts);
208
+ return 'ok';
209
+ }
210
+ catch (err) {
211
+ return err instanceof HttpError && err.status < 500 ? 'revoked' : 'unavailable';
212
+ }
213
+ }
214
+ /** Gate for any action beyond the caller's own tenant: a tenant's admin, by key or resolver, is never a host admin. */
215
+ export function assertCrossTenantAdmin(ctx, what) {
216
+ if (ctx.actor.role !== 'admin')
217
+ throw new HttpError(403, `${what} requires admin role`);
218
+ if (!ctx.actor.hostAdmin)
219
+ throw new HttpError(403, `${what} requires a host admin`);
220
+ }
221
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1,25 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+ import type { RateLimiter } from '../rate-limit.js';
3
+ /**
4
+ * Rate-limit key for a request. Defaults to the socket's remote address.
5
+ *
6
+ * Behind a TLS-terminating proxy (Fly, most PaaS ingress) every socket
7
+ * carries the proxy's address, so per-IP buckets collapse into one global
8
+ * bucket that unauthenticated traffic can drain before auth runs. Set
9
+ * HIPPO_CLIENT_IP_HEADER to the header the proxy stamps with the real
10
+ * client address (fly-client-ip on Fly, which the edge always overwrites)
11
+ * to key buckets per client instead.
12
+ *
13
+ * Only set this when a trusted proxy fronts EVERY request: a directly
14
+ * reachable server honoring the header would let clients mint a fresh
15
+ * bucket per request and bypass the limiter entirely.
16
+ *
17
+ * HIPPO_TRUSTED_PROXIES (comma-separated IPs or CIDRs) pins which peers count
18
+ * as that proxy: the header is read only when the socket peer is listed, and
19
+ * listed hops are skipped. In a comma-joined chain (X-Forwarded-For) the key is
20
+ * the rightmost hop no trusted proxy added; entries to its left are whatever
21
+ * the client sent, so they never pick the bucket.
22
+ */
23
+ export declare function clientIpForRateLimit(req: IncomingMessage): string;
24
+ export declare function enforceRateLimit(req: IncomingMessage, path: string, limiter?: RateLimiter): void;
25
+ //# sourceMappingURL=client-ip.d.ts.map
@@ -0,0 +1,92 @@
1
+ // Client IP keying and the per-IP rate limit for /v1 and /mcp.
2
+ import { envClientIpHeader, envTrustedProxies } from '../env.js';
3
+ import { BlockList, isIP } from 'node:net';
4
+ import { log } from '../log.js';
5
+ import { HttpError } from '../http-util.js';
6
+ /**
7
+ * Rate-limit key for a request. Defaults to the socket's remote address.
8
+ *
9
+ * Behind a TLS-terminating proxy (Fly, most PaaS ingress) every socket
10
+ * carries the proxy's address, so per-IP buckets collapse into one global
11
+ * bucket that unauthenticated traffic can drain before auth runs. Set
12
+ * HIPPO_CLIENT_IP_HEADER to the header the proxy stamps with the real
13
+ * client address (fly-client-ip on Fly, which the edge always overwrites)
14
+ * to key buckets per client instead.
15
+ *
16
+ * Only set this when a trusted proxy fronts EVERY request: a directly
17
+ * reachable server honoring the header would let clients mint a fresh
18
+ * bucket per request and bypass the limiter entirely.
19
+ *
20
+ * HIPPO_TRUSTED_PROXIES (comma-separated IPs or CIDRs) pins which peers count
21
+ * as that proxy: the header is read only when the socket peer is listed, and
22
+ * listed hops are skipped. In a comma-joined chain (X-Forwarded-For) the key is
23
+ * the rightmost hop no trusted proxy added; entries to its left are whatever
24
+ * the client sent, so they never pick the bucket.
25
+ */
26
+ export function clientIpForRateLimit(req) {
27
+ const socketIp = req.socket.remoteAddress ?? 'unknown';
28
+ const header = envClientIpHeader();
29
+ if (!header)
30
+ return socketIp;
31
+ const trusted = trustedProxyList(envTrustedProxies());
32
+ if (trusted && !isTrustedProxy(trusted, socketIp))
33
+ return socketIp;
34
+ const raw = req.headers[header];
35
+ const hops = (Array.isArray(raw) ? raw.join(',') : raw ?? '')
36
+ .split(',')
37
+ .map((hop) => hop.trim())
38
+ .filter((hop) => hop.length > 0);
39
+ for (let i = hops.length - 1; i >= 0; i--) {
40
+ if (!trusted || !isTrustedProxy(trusted, hops[i]))
41
+ return hops[i];
42
+ }
43
+ return hops[0] ?? socketIp;
44
+ }
45
+ let trustedProxyCache;
46
+ /** Parses HIPPO_TRUSTED_PROXIES once per distinct value; undefined when unset, so any peer may set the header. */
47
+ function trustedProxyList(raw) {
48
+ if (!raw?.trim())
49
+ return undefined;
50
+ if (trustedProxyCache?.raw === raw)
51
+ return trustedProxyCache.list;
52
+ const list = new BlockList();
53
+ for (const entry of raw.split(',').map((e) => e.trim()).filter((e) => e.length > 0)) {
54
+ const [addr = '', prefix] = entry.split('/');
55
+ const family = isIP(addr) === 6 ? 'ipv6' : 'ipv4';
56
+ const bits = prefix === undefined ? undefined : Number(prefix);
57
+ const maxBits = family === 'ipv6' ? 128 : 32;
58
+ if (isIP(addr) === 0 || (bits !== undefined && (!Number.isInteger(bits) || bits < 0 || bits > maxBits))) {
59
+ log.warn(`HIPPO_TRUSTED_PROXIES: ignoring '${entry}', not an IP address or CIDR`);
60
+ continue;
61
+ }
62
+ if (bits === undefined)
63
+ list.addAddress(addr, family);
64
+ else
65
+ list.addSubnet(addr, bits, family);
66
+ }
67
+ trustedProxyCache = { raw, list };
68
+ return list;
69
+ }
70
+ function isTrustedProxy(list, ip) {
71
+ const bare = ip.startsWith('::ffff:') && isIP(ip.slice(7)) === 4 ? ip.slice(7) : ip;
72
+ const family = isIP(bare);
73
+ return family !== 0 && list.check(bare, family === 6 ? 'ipv6' : 'ipv4');
74
+ }
75
+ export function enforceRateLimit(req, path, limiter) {
76
+ // E3: per-IP rate limit on /v1/* and /mcp* to bound api-key-id enumeration. /health
77
+ // (a liveness probe) and other paths are never throttled. A 429 thrown
78
+ // here lands in the createServer catch like any other HttpError.
79
+ //
80
+ // Keyed on the socket's remote address by default. Behind a TLS-terminating
81
+ // proxy every socket carries the proxy's address, collapsing the per-IP
82
+ // buckets into one global bucket that pre-auth traffic can drain; set
83
+ // HIPPO_CLIENT_IP_HEADER there so each real client gets its own bucket
84
+ // (see clientIpForRateLimit).
85
+ if (limiter && (path.startsWith('/v1/') || path === '/mcp' || path === '/mcp/stream')) {
86
+ const ip = clientIpForRateLimit(req);
87
+ if (!limiter.check(ip)) {
88
+ throw new HttpError(429, 'rate limit exceeded');
89
+ }
90
+ }
91
+ }
92
+ //# sourceMappingURL=client-ip.js.map
@@ -0,0 +1,23 @@
1
+ import type { ServerResponse } from 'node:http';
2
+ import type { KeysetPosition } from '../keyset.js';
3
+ type CursorPart = 'string' | 'integer';
4
+ /** Bare-array routes cannot grow a body field without breaking clients, so their next cursor rides this header. */
5
+ export declare const NEXT_CURSOR_HEADER = "X-Next-Cursor";
6
+ export declare function encodeCursor(pos: KeysetPosition): string;
7
+ /** The position a `?cursor=` names, or undefined when the param is absent; anything this server did not mint is a 400. */
8
+ export declare function parseCursor(raw: string | null, keyPart: CursorPart, idPart: CursorPart): KeysetPosition | undefined;
9
+ export interface Page<T> {
10
+ items: T[];
11
+ nextCursor: string | null;
12
+ }
13
+ /** Drops the one-row lookahead the query fetched past `limit` and names the next page's cursor, null on the last page. */
14
+ export declare function pageOf<T>(rows: readonly T[], limit: number, positionOf: (row: T) => KeysetPosition): Page<T>;
15
+ /** Rows ordered by (created_at DESC, id DESC): the decision, incident, process, policy, skill, brief, note and prediction tables. */
16
+ export declare function byCreatedAt(row: {
17
+ createdAt: string;
18
+ id: number;
19
+ }): KeysetPosition;
20
+ /** Sets the next-page header on a bare-array response; absent means this was the last page. */
21
+ export declare function setNextCursorHeader(res: ServerResponse, nextCursor: string | null): void;
22
+ export {};
23
+ //# sourceMappingURL=cursor.d.ts.map
@@ -0,0 +1,58 @@
1
+ import { HttpError } from '../http-util.js';
2
+ import { isJsonNumber, isJsonString } from './validation.js';
3
+ /** Bare-array routes cannot grow a body field without breaking clients, so their next cursor rides this header. */
4
+ export const NEXT_CURSOR_HEADER = 'X-Next-Cursor';
5
+ const MAX_CURSOR_CHARS = 1024;
6
+ const CURSOR_RE = /^[A-Za-z0-9_-]+$/;
7
+ export function encodeCursor(pos) {
8
+ return Buffer.from(JSON.stringify([pos.key, pos.id])).toString('base64url');
9
+ }
10
+ function decodeJson(raw) {
11
+ try {
12
+ const parsed = JSON.parse(Buffer.from(raw, 'base64url').toString('utf8'));
13
+ return parsed;
14
+ }
15
+ catch (e) {
16
+ if (e instanceof SyntaxError)
17
+ return undefined;
18
+ throw e;
19
+ }
20
+ }
21
+ function isPart(value, part) {
22
+ if (part === 'string')
23
+ return isJsonString(value) && value.length > 0;
24
+ return isJsonNumber(value) && Number.isInteger(value);
25
+ }
26
+ function toPosition(parsed, keyPart, idPart) {
27
+ if (!Array.isArray(parsed) || parsed.length !== 2)
28
+ return undefined;
29
+ const [key, id] = parsed;
30
+ return isPart(key, keyPart) && isPart(id, idPart) ? { key, id } : undefined;
31
+ }
32
+ /** The position a `?cursor=` names, or undefined when the param is absent; anything this server did not mint is a 400. */
33
+ export function parseCursor(raw, keyPart, idPart) {
34
+ if (raw === null)
35
+ return undefined;
36
+ const parsed = raw.length <= MAX_CURSOR_CHARS && CURSOR_RE.test(raw) ? decodeJson(raw) : undefined;
37
+ const pos = toPosition(parsed, keyPart, idPart);
38
+ if (!pos)
39
+ throw new HttpError(400, 'cursor is malformed; pass next_cursor from the previous page unchanged');
40
+ return pos;
41
+ }
42
+ /** Drops the one-row lookahead the query fetched past `limit` and names the next page's cursor, null on the last page. */
43
+ export function pageOf(rows, limit, positionOf) {
44
+ if (rows.length <= limit)
45
+ return { items: [...rows], nextCursor: null };
46
+ const items = rows.slice(0, limit);
47
+ return { items, nextCursor: encodeCursor(positionOf(items[items.length - 1])) };
48
+ }
49
+ /** Rows ordered by (created_at DESC, id DESC): the decision, incident, process, policy, skill, brief, note and prediction tables. */
50
+ export function byCreatedAt(row) {
51
+ return { key: row.createdAt, id: row.id };
52
+ }
53
+ /** Sets the next-page header on a bare-array response; absent means this was the last page. */
54
+ export function setNextCursorHeader(res, nextCursor) {
55
+ if (nextCursor !== null)
56
+ res.setHeader(NEXT_CURSOR_HEADER, nextCursor);
57
+ }
58
+ //# sourceMappingURL=cursor.js.map
@@ -0,0 +1,7 @@
1
+ import type { Server, ServerResponse } from 'node:http';
2
+ /**
3
+ * Stop accepting, end streams at once (they never finish on their own), give other in-flight
4
+ * requests up to `drainMs`, then close whatever is left.
5
+ */
6
+ export declare function drainAndClose(server: Server, inflight: ReadonlySet<ServerResponse>, drainMs: number): Promise<void>;
7
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1,29 @@
1
+ import { log } from '../log.js';
2
+ /**
3
+ * Stop accepting, end streams at once (they never finish on their own), give other in-flight
4
+ * requests up to `drainMs`, then close whatever is left.
5
+ */
6
+ export async function drainAndClose(server, inflight, drainMs) {
7
+ const closed = new Promise((resolve, reject) => {
8
+ server.close((err) => (err ? reject(err) : resolve()));
9
+ });
10
+ server.closeIdleConnections?.();
11
+ for (const res of inflight) {
12
+ if (res.headersSent && !res.writableEnded)
13
+ res.destroy();
14
+ else if (!res.headersSent)
15
+ res.setHeader('Connection', 'close');
16
+ }
17
+ let timer;
18
+ const timedOut = new Promise((resolve) => {
19
+ timer = setTimeout(() => resolve(true), drainMs);
20
+ });
21
+ const late = await Promise.race([closed.then(() => false), timedOut]);
22
+ clearTimeout(timer);
23
+ if (late) {
24
+ log.warn(`shutdown: ${inflight.size} request(s) still running after ${drainMs} ms; closing them`);
25
+ server.closeAllConnections?.();
26
+ }
27
+ await closed;
28
+ }
29
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1,5 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { ServeOpts } from './types.js';
3
+ export declare function handleMcpPost(req: IncomingMessage, res: ServerResponse, opts: ServeOpts): Promise<void>;
4
+ export declare function handleMcpStream(req: IncomingMessage, res: ServerResponse, opts: ServeOpts, streamSlots: Map<string, number>): Promise<void>;
5
+ //# sourceMappingURL=mcp-http.d.ts.map
@@ -0,0 +1,199 @@
1
+ // MCP over HTTP: POST /mcp and the GET /mcp/stream SSE keepalive.
2
+ import { envMcpSseHeartbeatMs, envMcpSseMaxAgeSec, envMcpSseMaxStreams } from '../env.js';
3
+ import { createHash } from 'node:crypto';
4
+ import { handleMcpRequest, mcpErrorResponse } from '../mcp/server.js';
5
+ import { HttpError, isJsonObjectRecord, readBody, sendJson } from '../http-util.js';
6
+ import { buildContextWithAuth, heartbeatVerdict, readAuthHeader, requireAuth } from './auth.js';
7
+ import { clientIpForRateLimit } from './client-ip.js';
8
+ import { requestIds } from './request.js';
9
+ import { isJsonString } from './validation.js';
10
+ /**
11
+ * Build a per-client key for MCP state isolation under HTTP-MCP. Used by
12
+ * mcp/server.ts to scope `lastRecalledIds` to the calling client so two
13
+ * clients on the same tenant cannot poison each other's outcome feedback.
14
+ *
15
+ * Token is hashed (sha256, 16-hex-char prefix) so we never log or persist
16
+ * the raw bearer. Combined with remoteAddress so two clients sharing a key
17
+ * (e.g. on a shared Postman environment) are still separable in the common
18
+ * case. 'noauth' covers loopback no-auth and is acceptable because that
19
+ * path is single-host single-user.
20
+ */
21
+ function buildMcpClientKey(req) {
22
+ const auth = readAuthHeader(req);
23
+ const tokenHash = auth.kind === 'bearer'
24
+ ? createHash('sha256').update(auth.token).digest('hex').slice(0, 16)
25
+ : 'noauth';
26
+ const addr = req.socket.remoteAddress ?? 'unknown';
27
+ return `http:${tokenHash}:${addr}`;
28
+ }
29
+ // ── MCP-over-HTTP/SSE transport (Task 11) ──
30
+ //
31
+ // Two routes implement an MCP HTTP transport alongside the stdio one. Both
32
+ // dispatch to the same `handleMcpRequest` as the stdio loop in src/mcp/server.ts.
33
+ //
34
+ // POST /mcp — Send a JSON-RPC request, get a JSON-RPC response synchronously
35
+ // in the body. Content-type: application/json both ways.
36
+ // GET /mcp/stream — Open an SSE stream for server-initiated messages.
37
+ // v1 simplification: this stream is keepalive-only. Clients
38
+ // that need server-pushed notifications/progress will see
39
+ // only `: ping` comments every 30s. All real responses come
40
+ // back synchronously on POST /mcp. This matches the
41
+ // "synchronous JSON in body" leg of the MCP HTTP spec and
42
+ // is enough for `tools/list` / `tools/call` round-trips.
43
+ // Server-initiated SSE messages will be wired in a later task.
44
+ //
45
+ // Auth: same as /v1/* — Bearer token validated via `requireAuth`, with the
46
+ // loopback no-auth fallback. SSE check runs once at stream-open.
47
+ export async function handleMcpPost(req, res, opts) {
48
+ // Build the same Context the /v1/* routes use so MCP tool calls inherit
49
+ // the server's bound hippoRoot and the auth-resolved tenantId / actor.
50
+ // Without this, executeTool would walk from cwd via findHippoRoot() and
51
+ // pull tenant from HIPPO_TENANT, dropping a valid Bearer for tenant B
52
+ // back to whatever the env says.
53
+ const ctx = await buildContextWithAuth(req, opts);
54
+ const raw = await readBody(req);
55
+ let mcpReq;
56
+ try {
57
+ mcpReq = JSON.parse(raw);
58
+ }
59
+ catch {
60
+ throw new HttpError(400, 'invalid JSON-RPC body');
61
+ }
62
+ if (!isJsonObjectRecord(mcpReq) || !isJsonString(mcpReq.method)) {
63
+ throw new HttpError(400, 'JSON-RPC body must include a method string');
64
+ }
65
+ // SAFETY: validated above as a plain JSON object carrying a string method;
66
+ // the remaining McpRequest wire fields (jsonrpc, id, params) are checked or
67
+ // safely defaulted inside handleMcpRequest's JSON-RPC dispatch.
68
+ const rpcReq = mcpReq;
69
+ let mcpRes;
70
+ try {
71
+ mcpRes = await handleMcpRequest(rpcReq, {
72
+ hippoRoot: ctx.hippoRoot,
73
+ tenantId: ctx.tenantId,
74
+ // v1.12.0: McpContext.actor stays string; extract subject at the boundary.
75
+ actor: ctx.actor.subject,
76
+ // The caller's real role: MCP tools must not run a member key as admin.
77
+ role: ctx.actor.role,
78
+ scopes: ctx.actor.scopes,
79
+ viaAuthResolver: ctx.actor.viaAuthResolver,
80
+ hostAdmin: ctx.actor.hostAdmin,
81
+ clientKey: buildMcpClientKey(req),
82
+ });
83
+ }
84
+ catch (err) {
85
+ mcpRes = mcpErrorResponse(rpcReq.id, err, requestIds.get(req));
86
+ }
87
+ if (mcpRes === null) {
88
+ // Notification — no body, 202 Accepted.
89
+ res.writeHead(202);
90
+ res.end();
91
+ return;
92
+ }
93
+ sendJson(res, 200, mcpRes);
94
+ }
95
+ // Each open stream holds a socket and a timer, so one key (or one IP when keyless) gets a bounded number.
96
+ const DEFAULT_MAX_STREAMS_PER_CLIENT = 8;
97
+ /** The bucket a stream counts against: a hash of the bearer token, else the client IP. */
98
+ function streamSlotKey(req) {
99
+ const auth = readAuthHeader(req);
100
+ if (auth.kind === 'bearer')
101
+ return `key:${createHash('sha256').update(auth.token).digest('hex').slice(0, 16)}`;
102
+ return `ip:${clientIpForRateLimit(req)}`;
103
+ }
104
+ /** Takes a stream slot or throws 429; the slot is released once, when the response closes. */
105
+ function acquireStreamSlot(req, res, slots) {
106
+ const max = envMcpSseMaxStreams() ?? DEFAULT_MAX_STREAMS_PER_CLIENT;
107
+ const key = streamSlotKey(req);
108
+ const open = slots.get(key) ?? 0;
109
+ if (open >= max)
110
+ throw new HttpError(429, `too many open streams for this client (limit ${max}); close one first`);
111
+ slots.set(key, open + 1);
112
+ res.once('close', () => {
113
+ const left = (slots.get(key) ?? 1) - 1;
114
+ if (left > 0)
115
+ slots.set(key, left);
116
+ else
117
+ slots.delete(key);
118
+ });
119
+ }
120
+ export async function handleMcpStream(req, res, opts, streamSlots) {
121
+ await requireAuth(req, opts);
122
+ // An async resolver can outlive the client; 'close' has already fired, so no timer may start.
123
+ if (req.destroyed || res.destroyed || req.socket.destroyed)
124
+ return;
125
+ acquireStreamSlot(req, res, streamSlots);
126
+ res.writeHead(200, {
127
+ 'content-type': 'text/event-stream',
128
+ 'cache-control': 'no-cache',
129
+ connection: 'keep-alive',
130
+ });
131
+ // Initial ping so smoke tests can confirm the stream is live without
132
+ // waiting for the first keepalive interval.
133
+ res.write(': ping\n\n');
134
+ // v0.39 SSE hardening:
135
+ // - Heartbeat re-validates the bearer (default 60s). If the key was
136
+ // revoked or rotated, close the stream with reason='auth_revoked'.
137
+ // - MCP_SSE_MAX_AGE_SEC (default 3600) caps stream lifetime; close
138
+ // with reason='max_age_exceeded' when reached.
139
+ // - MCP_SSE_HEARTBEAT_MS (default 60000) lets tests run with a short
140
+ // interval without waiting a full minute.
141
+ const heartbeatMs = envMcpSseHeartbeatMs() ?? 60000;
142
+ const maxAgeMs = (envMcpSseMaxAgeSec() ?? 3600) * 1000;
143
+ const startedAt = Date.now();
144
+ let closed = false;
145
+ let checking = false;
146
+ const closeWith = (reason) => {
147
+ if (closed)
148
+ return;
149
+ closed = true;
150
+ try {
151
+ res.write(`event: closed\ndata: ${JSON.stringify({ reason })}\n\n`);
152
+ }
153
+ catch { /* socket already gone */ }
154
+ try {
155
+ res.end();
156
+ }
157
+ catch { /* socket already gone */ }
158
+ };
159
+ const ping = setInterval(() => {
160
+ if (closed) {
161
+ clearInterval(ping);
162
+ return;
163
+ }
164
+ if (Date.now() - startedAt >= maxAgeMs) {
165
+ closeWith('max_age_exceeded');
166
+ clearInterval(ping);
167
+ return;
168
+ }
169
+ if (checking)
170
+ return;
171
+ checking = true;
172
+ void heartbeatVerdict(req, opts).then((verdict) => {
173
+ checking = false;
174
+ if (closed || verdict === 'unavailable')
175
+ return;
176
+ if (verdict === 'revoked') {
177
+ closeWith('auth_revoked');
178
+ clearInterval(ping);
179
+ return;
180
+ }
181
+ try {
182
+ res.write(': ping\n\n');
183
+ }
184
+ catch {
185
+ clearInterval(ping); // the client hung up; stop pinging a dead socket
186
+ }
187
+ });
188
+ }, heartbeatMs);
189
+ // Don't keep the event loop alive just for this timer — the server's
190
+ // listener already does that, and tests want the process to exit cleanly.
191
+ if (ping.unref instanceof Function)
192
+ ping.unref();
193
+ // res 'close' covers an early socket drop that req 'close' can miss.
194
+ res.on('close', () => {
195
+ closed = true;
196
+ clearInterval(ping);
197
+ });
198
+ }
199
+ //# sourceMappingURL=mcp-http.js.map