hippo-memory 1.71.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (625) hide show
  1. package/README.md +7 -5
  2. package/dist/agent-memories/apply.d.ts +0 -11
  3. package/dist/agent-memories/apply.js +11 -26
  4. package/dist/agent-memories/claude-code.js +1 -1
  5. package/dist/agent-memories/codex.js +1 -1
  6. package/dist/agent-memories/copilot.d.ts +1 -3
  7. package/dist/agent-memories/copilot.js +1 -17
  8. package/dist/agent-memories/project-folds.d.ts +10 -0
  9. package/dist/agent-memories/project-folds.js +34 -0
  10. package/dist/agent-memories/sync.js +8 -6
  11. package/dist/api/assemble.js +3 -2
  12. package/dist/api/audit.d.ts +30 -2
  13. package/dist/api/audit.js +48 -7
  14. package/dist/api/auth.d.ts +11 -9
  15. package/dist/api/auth.js +39 -40
  16. package/dist/api/auto-sleep.d.ts +5 -2
  17. package/dist/api/auto-sleep.js +7 -3
  18. package/dist/api/availability.js +2 -3
  19. package/dist/api/cards.d.ts +42 -0
  20. package/dist/api/cards.js +52 -0
  21. package/dist/api/conflicts.d.ts +28 -0
  22. package/dist/api/conflicts.js +61 -0
  23. package/dist/api/context-auto.js +4 -2
  24. package/dist/api/context-render.js +4 -3
  25. package/dist/api/context-select.js +15 -29
  26. package/dist/api/context.js +29 -18
  27. package/dist/api/continuity.d.ts +47 -0
  28. package/dist/api/continuity.js +66 -0
  29. package/dist/api/dormant.js +4 -2
  30. package/dist/api/drill-down.js +3 -2
  31. package/dist/api/embeddings.d.ts +17 -0
  32. package/dist/api/embeddings.js +28 -0
  33. package/dist/api/error-reply.d.ts +8 -0
  34. package/dist/api/error-reply.js +21 -0
  35. package/dist/api/forget.d.ts +7 -3
  36. package/dist/api/forget.js +24 -14
  37. package/dist/api/goals.d.ts +1 -1
  38. package/dist/api/goals.js +5 -9
  39. package/dist/api/graph.js +2 -1
  40. package/dist/api/index.d.ts +4 -2
  41. package/dist/api/index.js +4 -2
  42. package/dist/api/instruction-detect.js +5 -1
  43. package/dist/api/learn.d.ts +5 -1
  44. package/dist/api/learn.js +71 -44
  45. package/dist/api/ledger-db.d.ts +2 -6
  46. package/dist/api/ledger-db.js +4 -36
  47. package/dist/api/memories.d.ts +21 -1
  48. package/dist/api/memories.js +51 -1
  49. package/dist/api/objects.d.ts +1 -1
  50. package/dist/api/objects.js +2 -1
  51. package/dist/api/outcome.d.ts +5 -5
  52. package/dist/api/outcome.js +17 -16
  53. package/dist/api/pilot-arm.d.ts +1 -11
  54. package/dist/api/pilot-arm.js +5 -45
  55. package/dist/api/predictions.d.ts +3 -1
  56. package/dist/api/predictions.js +8 -1
  57. package/dist/api/projects.d.ts +21 -0
  58. package/dist/api/projects.js +24 -0
  59. package/dist/api/promote.d.ts +3 -3
  60. package/dist/api/promote.js +26 -28
  61. package/dist/api/prompt-hook.d.ts +17 -2
  62. package/dist/api/prompt-hook.js +45 -4
  63. package/dist/api/quarantine.js +2 -1
  64. package/dist/api/recall-cli.d.ts +3 -0
  65. package/dist/api/recall-cli.js +13 -4
  66. package/dist/api/recall-core.js +10 -5
  67. package/dist/api/recall-pipeline.js +23 -24
  68. package/dist/api/recall-request.js +1 -1
  69. package/dist/api/recall-types.d.ts +3 -1
  70. package/dist/api/recall.d.ts +0 -3
  71. package/dist/api/recall.js +51 -73
  72. package/dist/api/remember-local.d.ts +5 -2
  73. package/dist/api/remember-local.js +15 -7
  74. package/dist/api/remember.d.ts +7 -3
  75. package/dist/api/remember.js +19 -16
  76. package/dist/api/session-end.d.ts +46 -0
  77. package/dist/api/session-end.js +131 -0
  78. package/dist/api/sleep-run.d.ts +1 -1
  79. package/dist/api/sleep-run.js +16 -11
  80. package/dist/api/sleep.js +4 -3
  81. package/dist/api/status.d.ts +24 -0
  82. package/dist/api/status.js +40 -0
  83. package/dist/api/store-init.d.ts +4 -0
  84. package/dist/api/store-init.js +9 -0
  85. package/dist/api/tokens.js +2 -2
  86. package/dist/api/types.d.ts +3 -7
  87. package/dist/api/types.js +6 -1
  88. package/dist/capture/caller-session.d.ts +0 -3
  89. package/dist/capture/caller-session.js +1 -12
  90. package/dist/capture/capture-error.d.ts +2 -2
  91. package/dist/capture/capture-error.js +23 -18
  92. package/dist/capture/codex-patch.js +3 -1
  93. package/dist/capture/command.js +36 -95
  94. package/dist/capture/compact.js +10 -5
  95. package/dist/capture/compaction-items-caller.js +11 -20
  96. package/dist/capture/compaction-record.d.ts +7 -93
  97. package/dist/capture/compaction-record.js +29 -426
  98. package/dist/capture/compaction-spool.d.ts +7 -7
  99. package/dist/capture/compaction-spool.js +60 -51
  100. package/dist/capture/copilot-transcript.d.ts +2 -1
  101. package/dist/capture/copilot-transcript.js +2 -1
  102. package/dist/capture/extract.d.ts +5 -2
  103. package/dist/capture/extract.js +4 -3
  104. package/dist/capture/failure-caller.d.ts +1 -1
  105. package/dist/capture/failure-caller.js +21 -16
  106. package/dist/capture/failure-reading.d.ts +2 -1
  107. package/dist/capture/failure-reading.js +7 -9
  108. package/dist/capture/pre-compact-caller.js +5 -4
  109. package/dist/capture/session-digest.js +4 -3
  110. package/dist/capture/session-end-caller.js +3 -2
  111. package/dist/capture/session-worker.js +22 -10
  112. package/dist/capture/transcript.d.ts +2 -1
  113. package/dist/capture/transcript.js +6 -3
  114. package/dist/capture/working-state.d.ts +4 -2
  115. package/dist/capture/working-state.js +4 -2
  116. package/dist/cli/audit-prune.d.ts +0 -25
  117. package/dist/cli/audit-prune.js +1 -27
  118. package/dist/cli/audit.js +20 -20
  119. package/dist/cli/auth.d.ts +1 -1
  120. package/dist/cli/auth.js +16 -17
  121. package/dist/cli/briefs.d.ts +1 -4
  122. package/dist/cli/briefs.js +37 -292
  123. package/dist/cli/card.js +41 -33
  124. package/dist/cli/client.d.ts +6 -2
  125. package/dist/cli/client.js +37 -21
  126. package/dist/cli/compact-resume-payload.js +1 -1
  127. package/dist/cli/context.d.ts +1 -1
  128. package/dist/cli/context.js +13 -13
  129. package/dist/cli/continuity.d.ts +1 -1
  130. package/dist/cli/continuity.js +36 -39
  131. package/dist/cli/curate.d.ts +2 -2
  132. package/dist/cli/curate.js +32 -36
  133. package/dist/cli/dag.d.ts +1 -1
  134. package/dist/cli/dag.js +23 -8
  135. package/dist/cli/decisions.d.ts +0 -2
  136. package/dist/cli/decisions.js +7 -285
  137. package/dist/cli/eval.d.ts +1 -1
  138. package/dist/cli/eval.js +12 -13
  139. package/dist/cli/explain.d.ts +1 -1
  140. package/dist/cli/explain.js +21 -25
  141. package/dist/cli/flag-values.js +16 -6
  142. package/dist/cli/flags.d.ts +0 -289
  143. package/dist/cli/flags.js +3 -138
  144. package/dist/cli/graph.d.ts +3 -0
  145. package/dist/cli/graph.js +83 -0
  146. package/dist/cli/hook-runtime.d.ts +6 -6
  147. package/dist/cli/hook-runtime.js +9 -36
  148. package/dist/cli/incidents.d.ts +3 -0
  149. package/dist/cli/incidents.js +112 -0
  150. package/dist/cli/init.d.ts +1 -1
  151. package/dist/cli/init.js +28 -19
  152. package/dist/cli/install-steps.d.ts +2 -1
  153. package/dist/cli/install-steps.js +11 -3
  154. package/dist/cli/last-sleep.js +1 -1
  155. package/dist/cli/maintenance.d.ts +3 -3
  156. package/dist/cli/maintenance.js +29 -30
  157. package/dist/cli/notes.d.ts +2 -0
  158. package/dist/cli/notes.js +41 -0
  159. package/dist/cli/object-verbs.d.ts +2 -2
  160. package/dist/cli/object-verbs.js +4 -4
  161. package/dist/cli/open-browser.d.ts +14 -0
  162. package/dist/cli/open-browser.js +26 -0
  163. package/dist/cli/playbooks.d.ts +3 -4
  164. package/dist/cli/playbooks.js +126 -290
  165. package/dist/cli/predictions.d.ts +3 -0
  166. package/dist/cli/predictions.js +174 -0
  167. package/dist/cli/print.js +2 -1
  168. package/dist/cli/projects.js +30 -22
  169. package/dist/cli/quality-repair-once.d.ts +1 -1
  170. package/dist/cli/quality-repair-once.js +6 -5
  171. package/dist/cli/recall.js +13 -7
  172. package/dist/cli/refine-llm.js +18 -17
  173. package/dist/cli/remember.js +17 -17
  174. package/dist/cli/scheduler.js +2 -1
  175. package/dist/cli/serve.js +11 -6
  176. package/dist/cli/session-hooks.d.ts +1 -5
  177. package/dist/cli/session-hooks.js +89 -168
  178. package/dist/cli/setup.d.ts +1 -1
  179. package/dist/cli/setup.js +17 -10
  180. package/dist/cli/shared.d.ts +2 -6
  181. package/dist/cli/shared.js +9 -29
  182. package/dist/cli/sleep-render.js +6 -3
  183. package/dist/cli/sleep.js +6 -37
  184. package/dist/cli/status.d.ts +3 -3
  185. package/dist/cli/status.js +32 -35
  186. package/dist/cli/transfer.d.ts +3 -3
  187. package/dist/cli/transfer.js +64 -43
  188. package/dist/cli/usage.d.ts +1 -80
  189. package/dist/cli/usage.js +2 -696
  190. package/dist/cli/verb-row.d.ts +34 -0
  191. package/dist/cli/verb-row.js +6 -0
  192. package/dist/cli/verbs/health.d.ts +27 -0
  193. package/dist/cli/verbs/health.js +234 -0
  194. package/dist/cli/verbs/memory.d.ts +16 -0
  195. package/dist/cli/verbs/memory.js +236 -0
  196. package/dist/cli/verbs/objects.d.ts +20 -0
  197. package/dist/cli/verbs/objects.js +257 -0
  198. package/dist/cli/verbs/sessions.d.ts +31 -0
  199. package/dist/cli/verbs/sessions.js +314 -0
  200. package/dist/cli/verbs.d.ts +81 -0
  201. package/dist/cli/verbs.js +7 -0
  202. package/dist/cli/versioned-verbs.d.ts +51 -0
  203. package/dist/cli/versioned-verbs.js +79 -0
  204. package/dist/cli.d.ts +1 -272
  205. package/dist/cli.js +24 -315
  206. package/dist/connectors/dlq.d.ts +39 -3
  207. package/dist/connectors/dlq.js +39 -4
  208. package/dist/connectors/github/backfill.js +12 -3
  209. package/dist/connectors/github/deletion.js +2 -1
  210. package/dist/connectors/github/dlq.js +31 -73
  211. package/dist/connectors/github/ingest.d.ts +2 -6
  212. package/dist/connectors/github/ingest.js +5 -29
  213. package/dist/connectors/github/tenant-routing.js +2 -1
  214. package/dist/connectors/github/webhook.d.ts +2 -1
  215. package/dist/connectors/github/webhook.js +35 -2
  216. package/dist/connectors/ingest.d.ts +10 -0
  217. package/dist/connectors/ingest.js +29 -0
  218. package/dist/connectors/slack/deletion.js +2 -1
  219. package/dist/connectors/slack/dlq.js +49 -71
  220. package/dist/connectors/slack/ingest.d.ts +2 -6
  221. package/dist/connectors/slack/ingest.js +4 -28
  222. package/dist/connectors/slack/tenant-routing.js +2 -1
  223. package/dist/connectors/slack/webhook.d.ts +2 -1
  224. package/dist/connectors/slack/webhook.js +2 -1
  225. package/dist/consolidate/conflicts.js +2 -2
  226. package/dist/consolidate/dag.js +29 -18
  227. package/dist/consolidate/dedupe.d.ts +7 -0
  228. package/dist/consolidate/dedupe.js +6 -4
  229. package/dist/consolidate/llm-passes.js +45 -19
  230. package/dist/consolidate/memory-value.js +3 -2
  231. package/dist/consolidate/merge.js +8 -8
  232. package/dist/consolidate/physics-pass.d.ts +6 -1
  233. package/dist/consolidate/physics-pass.js +26 -6
  234. package/dist/consolidate/replay.js +11 -4
  235. package/dist/consolidate/sleep.js +9 -6
  236. package/dist/consolidate/traces.js +5 -5
  237. package/dist/core/ablation.d.ts +10 -0
  238. package/dist/core/ablation.js +12 -0
  239. package/dist/{sharing/scope.d.ts → core/active-scope.d.ts} +1 -1
  240. package/dist/{sharing/scope.js → core/active-scope.js} +1 -1
  241. package/dist/core/ambient.d.ts +4 -0
  242. package/dist/core/ambient.js +39 -18
  243. package/dist/core/api-errors.d.ts +16 -0
  244. package/dist/core/api-errors.js +20 -0
  245. package/dist/core/card.js +3 -1
  246. package/dist/core/compaction-timing.d.ts +7 -0
  247. package/dist/core/compaction-timing.js +9 -0
  248. package/dist/core/config.d.ts +1 -1
  249. package/dist/core/config.js +22 -18
  250. package/dist/core/cosine.d.ts +3 -0
  251. package/dist/core/cosine.js +19 -0
  252. package/dist/{store → core}/markdown.d.ts +1 -1
  253. package/dist/{store → core}/markdown.js +4 -4
  254. package/dist/core/memory-audit.d.ts +17 -0
  255. package/dist/core/memory-audit.js +54 -0
  256. package/dist/core/memory.d.ts +14 -8
  257. package/dist/core/memory.js +35 -23
  258. package/dist/{consolidate → core}/merged-row.d.ts +1 -1
  259. package/dist/{consolidate → core}/merged-row.js +1 -1
  260. package/dist/core/physics.d.ts +3 -1
  261. package/dist/core/physics.js +23 -18
  262. package/dist/core/prediction.d.ts +3 -0
  263. package/dist/core/prediction.js +6 -0
  264. package/dist/core/project-identity.d.ts +3 -0
  265. package/dist/core/project-identity.js +12 -2
  266. package/dist/{store → core}/recall-scope.d.ts +1 -10
  267. package/dist/{store → core}/recall-scope.js +2 -19
  268. package/dist/core/search-types.d.ts +1 -1
  269. package/dist/dashboard/dashboard-actions.d.ts +2 -2
  270. package/dist/dashboard/dashboard-actions.js +5 -5
  271. package/dist/dashboard/dashboard-queries.js +2 -7
  272. package/dist/dashboard/dashboard-snapshot.js +1 -3
  273. package/dist/dashboard/dashboard.js +16 -5
  274. package/dist/db/busy.d.ts +2 -7
  275. package/dist/db/busy.js +21 -26
  276. package/dist/db/connect.d.ts +2 -2
  277. package/dist/db/connect.js +7 -7
  278. package/dist/db/index.d.ts +3 -3
  279. package/dist/db/index.js +3 -3
  280. package/dist/db/migrate.js +14 -7
  281. package/dist/db/migrations/index.d.ts +1 -1
  282. package/dist/db/migrations/index.js +3 -1
  283. package/dist/db/migrations/v11.js +1 -0
  284. package/dist/db/migrations/v14.js +1 -0
  285. package/dist/db/migrations/v55.d.ts +3 -0
  286. package/dist/db/migrations/v55.js +17 -0
  287. package/dist/db/open.d.ts +0 -2
  288. package/dist/db/open.js +6 -10
  289. package/dist/db/physics-state.d.ts +0 -4
  290. package/dist/db/physics-state.js +7 -33
  291. package/dist/db/request-stores.d.ts +1 -0
  292. package/dist/db/request-stores.js +2 -1
  293. package/dist/db/tables.d.ts +2 -0
  294. package/dist/db/tables.js +6 -2
  295. package/dist/db/vector-store.js +19 -5
  296. package/dist/db/wal-checkpointer.js +2 -1
  297. package/dist/doctor.d.ts +3 -0
  298. package/dist/doctor.js +18 -15
  299. package/dist/embeddings/local.d.ts +62 -0
  300. package/dist/embeddings/local.js +146 -0
  301. package/dist/{store/embeddings → embeddings}/provider.d.ts +5 -2
  302. package/dist/{store/embeddings → embeddings}/provider.js +19 -25
  303. package/dist/embeddings/transformers.d.ts +26 -0
  304. package/dist/embeddings/transformers.js +89 -0
  305. package/dist/entry/session-text.d.ts +1 -1
  306. package/dist/entry/session-text.js +1 -1
  307. package/dist/entry/store-kit.d.ts +37 -0
  308. package/dist/entry/store-kit.js +25 -0
  309. package/dist/entry/testing.d.ts +6 -0
  310. package/dist/entry/testing.js +7 -0
  311. package/dist/eval/eval-suite.js +17 -11
  312. package/dist/eval/eval.js +1 -1
  313. package/dist/eval/judgment.js +12 -5
  314. package/dist/graph/extract.js +3 -3
  315. package/dist/graph/recall.js +1 -1
  316. package/dist/graph/view.js +13 -5
  317. package/dist/graph/write.d.ts +1 -1
  318. package/dist/graph/write.js +9 -9
  319. package/dist/hooks/codex-session.js +3 -2
  320. package/dist/hooks/codex-wrapper.js +2 -1
  321. package/dist/hooks/json-hooks.js +10 -4
  322. package/dist/hooks/opencode.js +8 -5
  323. package/dist/hooks/shared.d.ts +0 -6
  324. package/dist/hooks/shared.js +4 -15
  325. package/dist/importers/core.js +4 -4
  326. package/dist/importers/sources.js +5 -4
  327. package/dist/importers/vault.d.ts +1 -1
  328. package/dist/importers/vault.js +25 -23
  329. package/dist/index.d.ts +5 -4
  330. package/dist/index.js +4 -3
  331. package/dist/learn/autolearn.d.ts +15 -3
  332. package/dist/learn/autolearn.js +38 -16
  333. package/dist/learn/churn-git.js +13 -4
  334. package/dist/learn/extract.d.ts +3 -2
  335. package/dist/learn/extract.js +7 -5
  336. package/dist/learn/forward-claim-detector.js +4 -2
  337. package/dist/learn/invalidation.d.ts +2 -1
  338. package/dist/learn/invalidation.js +18 -9
  339. package/dist/mcp/admin-tools.d.ts +2 -2
  340. package/dist/mcp/admin-tools.js +27 -48
  341. package/dist/mcp/format.js +3 -2
  342. package/dist/mcp/framing.d.ts +1 -0
  343. package/dist/mcp/framing.js +1 -1
  344. package/dist/mcp/memory-tools.d.ts +1 -1
  345. package/dist/mcp/memory-tools.js +8 -9
  346. package/dist/mcp/protocol.d.ts +11 -7
  347. package/dist/mcp/protocol.js +25 -12
  348. package/dist/mcp/recall-tools.js +12 -9
  349. package/dist/mcp/request.js +22 -20
  350. package/dist/mcp/stdio.js +37 -16
  351. package/dist/mcp/tool-args.js +2 -1
  352. package/dist/mcp/tools.js +44 -21
  353. package/dist/objects/customer-notes.d.ts +2 -2
  354. package/dist/objects/decisions.d.ts +2 -2
  355. package/dist/objects/descriptor.d.ts +1 -1
  356. package/dist/objects/incidents.d.ts +2 -2
  357. package/dist/objects/lifecycle.d.ts +1 -1
  358. package/dist/objects/lifecycle.js +2 -1
  359. package/dist/objects/policies.d.ts +2 -2
  360. package/dist/objects/policies.js +2 -1
  361. package/dist/objects/processes.d.ts +2 -2
  362. package/dist/objects/project-briefs.d.ts +2 -2
  363. package/dist/objects/project-briefs.js +2 -3
  364. package/dist/objects/skills.d.ts +2 -2
  365. package/dist/postinstall.js +1 -1
  366. package/dist/rerankers/clef.js +3 -6
  367. package/dist/rerankers/cross-encoder.d.ts +31 -2
  368. package/dist/rerankers/cross-encoder.js +71 -98
  369. package/dist/rerankers/index.js +5 -4
  370. package/dist/rerankers/jev.js +2 -4
  371. package/dist/rerankers/outage-warning.d.ts +3 -1
  372. package/dist/rerankers/outage-warning.js +5 -3
  373. package/dist/search/bm25-search.js +1 -1
  374. package/dist/search/boosts.js +1 -1
  375. package/dist/search/embed-memories.d.ts +12 -0
  376. package/dist/search/embed-memories.js +389 -0
  377. package/dist/search/engine.d.ts +8 -0
  378. package/dist/search/engine.js +10 -0
  379. package/dist/search/goal-boost.d.ts +1 -1
  380. package/dist/search/goal-boost.js +6 -3
  381. package/dist/search/hybrid.d.ts +9 -2
  382. package/dist/search/hybrid.js +9 -4
  383. package/dist/search/physics-search.d.ts +6 -3
  384. package/dist/search/physics-search.js +22 -15
  385. package/dist/search/rerank.js +1 -1
  386. package/dist/search/vector.d.ts +8 -6
  387. package/dist/search/vector.js +14 -17
  388. package/dist/server/auth.d.ts +3 -1
  389. package/dist/server/auth.js +24 -15
  390. package/dist/server/boot.d.ts +2 -1
  391. package/dist/server/boot.js +34 -84
  392. package/dist/server/client-ip.d.ts +9 -6
  393. package/dist/server/client-ip.js +36 -17
  394. package/dist/server/deadline.d.ts +0 -2
  395. package/dist/server/deadline.js +1 -7
  396. package/dist/server/defaults.d.ts +1 -0
  397. package/dist/server/defaults.js +1 -0
  398. package/dist/server/mcp-http.js +16 -11
  399. package/dist/server/request.js +7 -2
  400. package/dist/server/route-checks.d.ts +7 -0
  401. package/dist/server/route-checks.js +42 -0
  402. package/dist/server/route-table.d.ts +9 -8
  403. package/dist/server/route-table.js +127 -133
  404. package/dist/server/routes/admin.d.ts +8 -8
  405. package/dist/server/routes/admin.js +13 -21
  406. package/dist/server/routes/customer-notes.d.ts +6 -6
  407. package/dist/server/routes/customer-notes.js +1 -2
  408. package/dist/server/routes/decisions.d.ts +6 -6
  409. package/dist/server/routes/decisions.js +2 -4
  410. package/dist/server/routes/incidents.d.ts +6 -6
  411. package/dist/server/routes/incidents.js +2 -5
  412. package/dist/server/routes/memories.d.ts +9 -9
  413. package/dist/server/routes/memories.js +19 -40
  414. package/dist/server/routes/object-routes.d.ts +6 -6
  415. package/dist/server/routes/object-routes.js +4 -7
  416. package/dist/server/routes/policies.d.ts +7 -7
  417. package/dist/server/routes/policies.js +2 -4
  418. package/dist/server/routes/predictions.d.ts +6 -6
  419. package/dist/server/routes/predictions.js +9 -13
  420. package/dist/server/routes/processes.d.ts +6 -6
  421. package/dist/server/routes/processes.js +1 -2
  422. package/dist/server/routes/project-briefs.d.ts +7 -7
  423. package/dist/server/routes/project-briefs.js +2 -4
  424. package/dist/server/routes/recall.d.ts +5 -5
  425. package/dist/server/routes/recall.js +6 -10
  426. package/dist/server/routes/skills.d.ts +7 -7
  427. package/dist/server/routes/skills.js +2 -4
  428. package/dist/server/server-detect.d.ts +7 -1
  429. package/dist/server/server-detect.js +36 -11
  430. package/dist/server/sleep-child.js +26 -7
  431. package/dist/server/sleep-offload.d.ts +11 -5
  432. package/dist/server/sleep-offload.js +17 -11
  433. package/dist/server/tls.d.ts +7 -2
  434. package/dist/server/tls.js +17 -5
  435. package/dist/server/types.d.ts +29 -12
  436. package/dist/server/validation.d.ts +1 -2
  437. package/dist/server/validation.js +6 -11
  438. package/dist/server.d.ts +4 -40
  439. package/dist/server.js +3 -29
  440. package/dist/sharing/global-store.d.ts +4 -5
  441. package/dist/sharing/global-store.js +8 -13
  442. package/dist/sharing/global-sync.js +38 -45
  443. package/dist/sharing/project-merge.d.ts +7 -14
  444. package/dist/sharing/project-merge.js +57 -121
  445. package/dist/sharing/search-both.js +10 -3
  446. package/dist/sharing/share.d.ts +2 -0
  447. package/dist/sharing/share.js +39 -38
  448. package/dist/store/ambient.js +7 -7
  449. package/dist/store/audit-event.d.ts +1 -1
  450. package/dist/store/audit.d.ts +0 -16
  451. package/dist/store/audit.js +4 -55
  452. package/dist/store/auth.d.ts +4 -2
  453. package/dist/store/auth.js +7 -3
  454. package/dist/store/candidates.d.ts +3 -2
  455. package/dist/store/candidates.js +59 -30
  456. package/dist/store/capture-write.d.ts +17 -0
  457. package/dist/store/capture-write.js +35 -0
  458. package/dist/store/cards.js +6 -5
  459. package/dist/store/compaction-caller.d.ts +18 -0
  460. package/dist/store/compaction-caller.js +29 -0
  461. package/dist/store/compactions-record.d.ts +115 -0
  462. package/dist/store/compactions-record.js +418 -0
  463. package/dist/store/compactions.js +6 -4
  464. package/dist/store/conflicts.d.ts +7 -1
  465. package/dist/store/conflicts.js +13 -9
  466. package/dist/store/connectors/dlq.d.ts +10 -0
  467. package/dist/store/connectors/dlq.js +33 -0
  468. package/dist/store/connectors/event-log.d.ts +13 -0
  469. package/dist/store/connectors/event-log.js +24 -0
  470. package/dist/store/connectors/github.d.ts +7 -9
  471. package/dist/store/connectors/github.js +35 -60
  472. package/dist/store/connectors/slack.d.ts +2 -11
  473. package/dist/store/connectors/slack.js +24 -57
  474. package/dist/store/dashboard-reads.js +8 -6
  475. package/dist/store/delete-and-batch.d.ts +6 -0
  476. package/dist/store/delete-and-batch.js +40 -6
  477. package/dist/store/delivery-recorder.d.ts +12 -5
  478. package/dist/store/delivery-recorder.js +13 -11
  479. package/dist/store/descendant-page.js +8 -4
  480. package/dist/store/dormant.d.ts +1 -1
  481. package/dist/store/dormant.js +10 -4
  482. package/dist/store/embeddings/index.d.ts +2 -12
  483. package/dist/store/embeddings/index.js +5 -374
  484. package/dist/store/entry-reads.d.ts +35 -4
  485. package/dist/store/entry-reads.js +86 -19
  486. package/dist/store/entry-row.js +9 -2
  487. package/dist/store/entry-writes.d.ts +15 -12
  488. package/dist/store/entry-writes.js +54 -22
  489. package/dist/store/failure-log-at.d.ts +8 -0
  490. package/dist/store/failure-log-at.js +16 -0
  491. package/dist/store/failure-log.d.ts +2 -2
  492. package/dist/store/failure-log.js +5 -3
  493. package/dist/{trust → store}/gated-write.d.ts +3 -1
  494. package/dist/{trust → store}/gated-write.js +7 -5
  495. package/dist/store/goals.js +22 -19
  496. package/dist/store/graph-reads.js +9 -6
  497. package/dist/store/graph-view-rows.js +2 -1
  498. package/dist/store/graph-writes.d.ts +6 -6
  499. package/dist/store/graph-writes.js +48 -71
  500. package/dist/store/half-life.js +2 -2
  501. package/dist/store/handoffs.d.ts +10 -4
  502. package/dist/store/handoffs.js +10 -9
  503. package/dist/store/index-and-stats.js +2 -2
  504. package/dist/store/index.d.ts +2 -2
  505. package/dist/store/index.js +1 -1
  506. package/dist/store/key-writes.js +1 -1
  507. package/dist/store/ledger-turn.d.ts +9 -0
  508. package/dist/store/ledger-turn.js +38 -0
  509. package/dist/store/mirrors.js +19 -10
  510. package/dist/store/open.d.ts +2 -5
  511. package/dist/store/open.js +42 -54
  512. package/dist/store/pilot-arm.d.ts +12 -0
  513. package/dist/store/pilot-arm.js +42 -0
  514. package/dist/store/port.d.ts +68 -6
  515. package/dist/store/port.js +8 -0
  516. package/dist/store/predictions.d.ts +1 -2
  517. package/dist/store/predictions.js +3 -6
  518. package/dist/store/project-tags.d.ts +62 -0
  519. package/dist/store/project-tags.js +67 -0
  520. package/dist/store/quality-repair.js +1 -1
  521. package/dist/store/quarantine.js +2 -1
  522. package/dist/store/recall-trace.d.ts +2 -0
  523. package/dist/store/recall-trace.js +20 -3
  524. package/dist/store/rejected-values.js +16 -10
  525. package/dist/store/rejection-probe.d.ts +4 -0
  526. package/dist/store/rejection-probe.js +19 -0
  527. package/dist/store/rejection.d.ts +0 -17
  528. package/dist/store/rejection.js +1 -21
  529. package/dist/store/rows.js +2 -2
  530. package/dist/store/rule-sql.d.ts +11 -0
  531. package/dist/store/rule-sql.js +31 -6
  532. package/dist/store/search-rows.d.ts +5 -5
  533. package/dist/store/search-rows.js +18 -16
  534. package/dist/store/sessions.js +4 -2
  535. package/dist/store/set-aside.d.ts +14 -0
  536. package/dist/store/set-aside.js +23 -0
  537. package/dist/store/sqlite/busy.d.ts +3 -0
  538. package/dist/store/sqlite/busy.js +8 -0
  539. package/dist/store/sqlite/connector-events-group.js +4 -7
  540. package/dist/store/sqlite/connector-writes-group.js +6 -4
  541. package/dist/store/sqlite/entry-writes-group.d.ts +3 -3
  542. package/dist/store/sqlite/entry-writes-group.js +3 -3
  543. package/dist/store/sqlite/error-codec.js +4 -3
  544. package/dist/store/sqlite/executor.js +10 -4
  545. package/dist/store/sqlite/graph-reads-group.js +1 -1
  546. package/dist/store/sqlite/held-connection.d.ts +11 -0
  547. package/dist/store/sqlite/held-connection.js +41 -0
  548. package/dist/store/sqlite/last-recall-group.d.ts +13 -0
  549. package/dist/store/sqlite/last-recall-group.js +57 -0
  550. package/dist/store/sqlite/local.d.ts +0 -17
  551. package/dist/store/sqlite/local.js +0 -31
  552. package/dist/store/sqlite/maintenance-groups.d.ts +6 -0
  553. package/dist/store/sqlite/maintenance-groups.js +60 -0
  554. package/dist/store/sqlite/object-rows.d.ts +1 -1
  555. package/dist/store/sqlite/objects-group.d.ts +1 -1
  556. package/dist/store/sqlite/objects-group.js +15 -7
  557. package/dist/store/sqlite/store.d.ts +1 -8
  558. package/dist/store/sqlite/store.js +13 -22
  559. package/dist/store/summaries.d.ts +10 -3
  560. package/dist/store/summaries.js +54 -11
  561. package/dist/store/token-ledger-rows.js +3 -2
  562. package/dist/store/token-ledger.d.ts +0 -7
  563. package/dist/store/token-ledger.js +0 -35
  564. package/dist/store/vector-index.js +8 -4
  565. package/dist/support-bundle.js +2 -3
  566. package/dist/trust/reject-flow.js +2 -2
  567. package/dist/util/agent-homes.d.ts +15 -0
  568. package/dist/util/agent-homes.js +34 -0
  569. package/dist/util/anthropic-messages.d.ts +6 -1
  570. package/dist/util/anthropic-messages.js +26 -7
  571. package/dist/util/atomic-write.d.ts +5 -2
  572. package/dist/util/atomic-write.js +12 -8
  573. package/dist/util/chunked.d.ts +3 -0
  574. package/dist/util/chunked.js +10 -0
  575. package/dist/util/ellipsize.d.ts +3 -0
  576. package/dist/util/ellipsize.js +5 -0
  577. package/dist/util/env.d.ts +12 -2
  578. package/dist/util/env.js +12 -2
  579. package/dist/util/format.d.ts +5 -0
  580. package/dist/util/format.js +10 -0
  581. package/dist/util/hook-payload.d.ts +8 -0
  582. package/dist/util/hook-payload.js +37 -0
  583. package/dist/util/http-retry.d.ts +6 -0
  584. package/dist/util/http-retry.js +43 -5
  585. package/dist/util/http-util.d.ts +0 -8
  586. package/dist/util/http-util.js +10 -21
  587. package/dist/util/limits.d.ts +7 -0
  588. package/dist/util/limits.js +14 -0
  589. package/dist/util/log.d.ts +4 -0
  590. package/dist/util/log.js +9 -0
  591. package/dist/util/request-scope.d.ts +9 -3
  592. package/dist/util/request-scope.js +18 -9
  593. package/dist/util/secret-detect.js +6 -1
  594. package/dist/util/spawn-argv.d.ts +9 -0
  595. package/dist/util/spawn-argv.js +57 -0
  596. package/dist/util/sql.d.ts +3 -0
  597. package/dist/util/sql.js +6 -0
  598. package/dist/util/sqlite-blocked.d.ts +2 -0
  599. package/dist/util/sqlite-blocked.js +5 -0
  600. package/dist/util/stream-tee.d.ts +3 -0
  601. package/dist/util/stream-tee.js +35 -0
  602. package/dist/util/time.d.ts +4 -0
  603. package/dist/util/time.js +4 -0
  604. package/dist/util/vector-codec.d.ts +4 -0
  605. package/dist/util/vector-codec.js +15 -0
  606. package/dist/util/version.d.ts +1 -1
  607. package/dist/util/version.js +1 -1
  608. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  609. package/extensions/openclaw-plugin/package.json +1 -1
  610. package/openclaw.plugin.json +1 -1
  611. package/package.json +4 -2
  612. package/dist/api/on-store.d.ts +0 -16
  613. package/dist/api/on-store.js +0 -38
  614. package/dist/store/embeddings/local.d.ts +0 -15
  615. package/dist/store/embeddings/local.js +0 -175
  616. /package/dist/{cli → api}/correction-latency.d.ts +0 -0
  617. /package/dist/{cli → api}/correction-latency.js +0 -0
  618. /package/dist/{cli → api}/provenance-coverage.d.ts +0 -0
  619. /package/dist/{cli → api}/provenance-coverage.js +0 -0
  620. /package/dist/{store → core}/object-types.d.ts +0 -0
  621. /package/dist/{store → core}/object-types.js +0 -0
  622. /package/dist/{capture → util}/compaction-items.d.ts +0 -0
  623. /package/dist/{capture → util}/compaction-items.js +0 -0
  624. /package/dist/{store → util}/yaml.d.ts +0 -0
  625. /package/dist/{store → util}/yaml.js +0 -0
@@ -1,30 +1,25 @@
1
1
  // Store health and admin tool handlers: base rates, status, conflicts, resolve, share and peers.
2
2
  import { evalNow } from '../core/ablation.js';
3
- import { loadStrengthTallies } from '../store/candidates.js';
4
- import { countOpenConflicts, listTouchableConflicts, resolveConflict } from '../store/conflicts.js';
5
3
  import { shareMemory, listPeers } from '../sharing/share.js';
6
- import { requireGroup, storeFor } from '../store/index.js';
7
- import { NotFoundError } from '../core/api-errors.js';
8
- import { classifyOriginProject } from '../core/project-identity.js';
9
- import { canTouchScope, passesScopeFilterForRecall, personalScopeOf } from '../store/recall-scope.js';
10
- import { chunked, loadEntriesByIds, readEntry } from '../store/entry-reads.js';
4
+ import { ConflictError, NotFoundError } from '../core/api-errors.js';
5
+ import { predictionBaserate } from '../api/predictions.js';
6
+ import { listOpenConflicts, resolveMemoryConflict } from '../api/conflicts.js';
7
+ import { getMemoryStatus, getTouchableMemory } from '../api/memories.js';
11
8
  import { mcpActor } from './protocol.js';
12
9
  import { isJsonString } from '../util/json.js';
13
10
  import { DATE_PREFIX_CHARS } from '../util/token-text.js';
14
11
  const BASERATE_DECIMALS = 3;
15
12
  const NOT_RESOLVED = 'Could not resolve. Check the conflict ID and --keep value.';
16
- /** The scope of memory `id`, null when it has none or does not exist. */
17
- function memoryScope(hippoRoot, id) {
18
- return readEntry(hippoRoot, id)?.scope ?? null;
19
- }
20
13
  export async function runPredictBaserateTool({ args, ctx, hippoRoot, tenantId }) {
21
14
  // Text-only reply, matching the other MCP tools; the group's read writes the audit row, so no surface can skip it.
22
15
  const classTag = String(args.class_tag || '').trim();
23
16
  if (!classTag)
24
17
  return 'No class_tag provided. Usage: pass class_tag matching a class used in past predictions (e.g. "migration-effort").';
25
- const baserate = await requireGroup(storeFor({ hippoRoot, store: ctx?.store }), 'predictions').predictionBaserate(tenantId, classTag, ctx?.actor ?? 'mcp');
18
+ const baserate = await predictionBaserate({ hippoRoot, tenantId, actor: mcpActor(ctx), store: ctx?.store }, classTag);
26
19
  if (baserate.nClosed === 0) {
27
- return `No closed predictions in class "${classTag}" yet. Create one via hippo_predict (or 'hippo predict ...' CLI) and close it with hippo_predict_close once the actual outcome is known. Base rates need closed predictions with numeric actual_value to compute.`;
20
+ return `No closed predictions in class "${classTag}" yet. Create one via hippo_predict (or 'hippo predict ...' CLI) ` +
21
+ `and close it with hippo_predict_close once the actual outcome is known. ` +
22
+ `Base rates need closed predictions with numeric actual_value to compute.`;
28
23
  }
29
24
  const lines = [baserate.summary, ''];
30
25
  lines.push(`n_closed: ${baserate.nClosed}`);
@@ -41,36 +36,21 @@ export async function runPredictBaserateTool({ args, ctx, hippoRoot, tenantId })
41
36
  lines.push(`mae: ${baserate.mae.toFixed(BASERATE_DECIMALS)}`);
42
37
  return lines.join('\n');
43
38
  }
44
- export function runStatusTool({ hippoRoot, config, tenantId }) {
39
+ export function runStatusTool({ ctx, hippoRoot, config, tenantId }) {
45
40
  // honors HIPPO_FAKE_NOW (eval-only; see ablation.ts)
46
- const tallies = loadStrengthTallies(hippoRoot, tenantId, evalNow(), 0.1);
41
+ const tallies = getMemoryStatus({ hippoRoot, tenantId, actor: mcpActor(ctx), store: ctx?.store }, evalNow(), 0.1);
47
42
  const avgStrength = tallies.total > 0 ? (tallies.strengthSum / tallies.total).toFixed(2) : '0';
48
- const conflicts = countOpenConflicts(hippoRoot, tenantId);
49
43
  return [
50
44
  `Memories: ${tallies.total} (${tallies.pinned} pinned, ${tallies.errors} errors)`,
51
45
  `Avg strength: ${avgStrength}`,
52
46
  `At risk (<0.1): ${tallies.atRisk}`,
53
- `Open conflicts: ${conflicts}`,
47
+ `Open conflicts: ${tallies.openConflicts}`,
54
48
  `Half-life default: ${config.defaultHalfLifeDays}d`,
55
49
  ].join('\n');
56
50
  }
57
- /** Pairs whose two rows the caller could recall: its repo or user-global, and no scope it was not asked for. */
58
- function recallablePairs(call, conflicts, project) {
59
- const own = personalScopeOf(mcpActor(call.ctx));
60
- const ids = [...new Set(conflicts.flatMap((c) => [c.memory_a_id, c.memory_b_id]))];
61
- // loadEntriesByIds reads at most one chunk of ids per call.
62
- const rows = new Map(chunked(ids).flatMap((chunk) => loadEntriesByIds(call.hippoRoot, chunk, call.tenantId)).map((row) => [row.id, row]));
63
- const shown = (id) => {
64
- const row = rows.get(id);
65
- return row !== undefined && classifyOriginProject(row.origin_project, project) !== 'cross-project'
66
- && passesScopeFilterForRecall(row.scope ?? null, undefined, own);
67
- };
68
- return conflicts.filter((c) => shown(c.memory_a_id) && shown(c.memory_b_id));
69
- }
70
51
  export function runConflictsTool(call) {
71
52
  const { ctx, hippoRoot, tenantId } = call;
72
- const touchable = listTouchableConflicts(hippoRoot, 'open', tenantId, mcpActor(ctx));
73
- const conflicts = ctx?.project ? recallablePairs(call, touchable, ctx.project) : touchable;
53
+ const conflicts = listOpenConflicts({ hippoRoot, tenantId, actor: mcpActor(ctx), store: ctx?.store }, ctx?.project);
74
54
  if (conflicts.length === 0)
75
55
  return 'No open conflicts.';
76
56
  return conflicts.map((c) => `conflict_${c.id}: ${c.memory_a_id} <-> ${c.memory_b_id} (score=${c.score.toFixed(2)}) — ${c.reason}`).join('\n');
@@ -79,33 +59,32 @@ export function runResolveTool({ args, ctx, hippoRoot, tenantId }) {
79
59
  const conflictId = Number(args.conflict_id);
80
60
  const keepId = String(args.keep || '');
81
61
  const forget = Boolean(args.forget);
82
- // Optional rejectLoser + reason pass straight to resolveConflict, as the CLI's --reject-loser does.
83
62
  const rejectLoser = Boolean(args.rejectLoser);
84
63
  const reason = isJsonString(args.reason) ? args.reason : undefined;
85
64
  if (isNaN(conflictId) || !keepId)
86
65
  return 'Required: conflict_id and keep.';
87
- if (!listTouchableConflicts(hippoRoot, 'open', tenantId, mcpActor(ctx)).some((c) => c.id === conflictId))
88
- return NOT_RESOLVED;
89
- const result = resolveConflict(hippoRoot, conflictId, keepId, forget, tenantId, {
90
- rejectLoserValue: rejectLoser,
91
- reason,
92
- // rejectedBy defaults to 'cli', so name the real actor for the tombstone and audit:
93
- // ctx.actor for HTTP-MCP, 'mcp' for stdio callers, which pass no ctx.
94
- rejectedBy: ctx?.actor ?? 'mcp',
95
- });
96
- if (!result)
97
- return NOT_RESOLVED;
66
+ let loserId;
67
+ try {
68
+ const apiCtx = { hippoRoot, tenantId, actor: mcpActor(ctx), store: ctx?.store };
69
+ ({ loserId } = resolveMemoryConflict(apiCtx, conflictId, { keepId, forget, rejectLoser, reason }));
70
+ }
71
+ catch (err) {
72
+ // A missing, unreachable or settled conflict answers alike, so the reply says nothing of a pair the caller cannot touch.
73
+ if (err instanceof NotFoundError || err instanceof ConflictError)
74
+ return NOT_RESOLVED;
75
+ throw err;
76
+ }
98
77
  const action = rejectLoser ? 'rejected (tombstoned) and removed' : forget ? 'deleted' : 'weakened';
99
- return `Resolved conflict ${conflictId}: kept ${keepId}, ${action} ${result.loserId}`;
78
+ return `Resolved conflict ${conflictId}: kept ${keepId}, ${action} ${loserId}`;
100
79
  }
101
- export function runShareTool({ args, ctx, hippoRoot, tenantId }) {
80
+ export async function runShareTool({ args, ctx, hippoRoot, tenantId }) {
102
81
  const shareId = String(args.id || '');
103
82
  if (!shareId)
104
83
  return 'Required: id (memory ID to share).';
105
84
  const force = Boolean(args.force);
85
+ const actor = mcpActor(ctx);
106
86
  // Checked before shareMemory, whose personal-row refusal would tell another person the id exists.
107
- if (!canTouchScope(mcpActor(ctx), memoryScope(hippoRoot, shareId)))
108
- throw new NotFoundError(`Memory not found: ${shareId}`);
87
+ await getTouchableMemory({ hippoRoot, tenantId, actor, store: ctx?.store }, shareId);
109
88
  // Pass tenantId so shareMemory's readEntry filters by tenant; otherwise a Bearer for tenant A could share tenant B's id to the global store.
110
89
  // The 'Memory not found' error matches the cross-tenant deny shape elsewhere.
111
90
  const shared = shareMemory(hippoRoot, shareId, { force, tenantId });
@@ -1,4 +1,5 @@
1
1
  // Text the MCP recall and context tools print, and what each printed piece costs the token budget.
2
+ import { truncateWithEllipsis } from '../util/ellipsize.js';
2
3
  import { confidenceLabel } from '../core/memory.js';
3
4
  import { formatHandoffEvidenceLine } from '../core/handoff.js';
4
5
  import { printedTokens } from '../api/context-render.js';
@@ -25,7 +26,7 @@ function handoffLines(h) {
25
26
  }
26
27
  function trailLines(events) {
27
28
  return events.map((e) => {
28
- const preview = e.content.length > MCP_PREVIEW_CHARS ? e.content.slice(0, MCP_PREVIEW_CHARS) + '…' : e.content;
29
+ const preview = truncateWithEllipsis(e.content, MCP_PREVIEW_CHARS, '…');
29
30
  return `- [${e.event_type}] ${preview}`;
30
31
  });
31
32
  }
@@ -111,7 +112,7 @@ export function tailSection(rows) {
111
112
  const lines = ['', '## Fresh tail / substituted summaries'];
112
113
  for (const r of rows) {
113
114
  const tag = r.isSummary ? '[summary]' : '[tail]';
114
- const head = r.content.length > MCP_PREVIEW_CHARS ? r.content.slice(0, MCP_PREVIEW_CHARS) + '…' : r.content;
115
+ const head = truncateWithEllipsis(r.content, MCP_PREVIEW_CHARS, '…');
115
116
  if (r.isSummary && r.substitutedFor && r.substitutedFor.length > 0) {
116
117
  lines.push(`- ${tag} ${r.id} (covers ${r.substitutedFor.length} rows): ${head}`);
117
118
  }
@@ -13,6 +13,7 @@ export type FrameResult = {
13
13
  remainder: FrameRemainder;
14
14
  } | {
15
15
  kind: 'incomplete';
16
+ needs?: number;
16
17
  };
17
18
  export declare function parseFrame(buffer: Buffer): FrameResult;
18
19
  //# sourceMappingURL=framing.d.ts.map
@@ -31,7 +31,7 @@ export function parseFrame(buffer) {
31
31
  return { kind: 'oversize', rest: Buffer.from(trimmed.subarray(Math.min(end, trimmed.length))), remainder: Math.max(0, end - trimmed.length) };
32
32
  }
33
33
  if (trimmed.length < bodyStart + contentLength)
34
- return { kind: 'incomplete' };
34
+ return { kind: 'incomplete', needs: start + bodyStart + contentLength };
35
35
  const body = trimmed.subarray(bodyStart, bodyStart + contentLength).toString('utf-8');
36
36
  return { kind: 'message', body, rest: Buffer.from(trimmed.subarray(bodyStart + contentLength)) };
37
37
  }
@@ -1,5 +1,5 @@
1
1
  import { type ToolCall } from './protocol.js';
2
2
  export declare function runRememberTool({ args, ctx, hippoRoot, config, tenantId }: ToolCall): Promise<string>;
3
3
  export declare function runOutcomeTool({ args, ctx, hippoRoot, tenantId }: ToolCall): Promise<string>;
4
- export declare function runLearnTool({ args, ctx, hippoRoot, tenantId }: ToolCall): string;
4
+ export declare function runLearnTool({ args, ctx, hippoRoot, tenantId }: ToolCall): Promise<string>;
5
5
  //# sourceMappingURL=memory-tools.d.ts.map
@@ -1,5 +1,5 @@
1
1
  // Write-side tool handlers: hippo_remember, hippo_outcome and hippo_learn.
2
- import { storeFor } from '../store/index.js';
2
+ import { hasGroup } from '../store/index.js';
3
3
  import { startAutoSleepIfDue } from '../api/auto-sleep.js';
4
4
  import { remember as apiRemember, outcome as apiOutcome, learn as apiLearn, MCP_LEARN } from '../api/index.js';
5
5
  import { mcpActor } from './protocol.js';
@@ -14,7 +14,6 @@ export async function runRememberTool({ args, ctx, hippoRoot, config, tenantId }
14
14
  if (args.tag)
15
15
  tags.push(String(args.tag));
16
16
  // Route through api/index.ts so audit_log captures the caller identity like CLI/REST: the auth-resolved ctx.actor under HTTP-MCP, 'mcp' for stdio.
17
- // The entry is re-read to surface the half-life used in the MCP response.
18
17
  const apiCtx = {
19
18
  hippoRoot,
20
19
  tenantId,
@@ -27,13 +26,12 @@ export async function runRememberTool({ args, ctx, hippoRoot, config, tenantId }
27
26
  personal: args.personal === true,
28
27
  project: ctx?.project,
29
28
  });
30
- const [entry] = await storeFor(apiCtx).entriesByIds([result.id], tenantId);
31
29
  // Auto-sleep runs on hippo.db, so another store skips it.
32
- startAutoSleepIfDue(hippoRoot, tenantId, config.autoSleep, (ctx?.store === undefined || ctx.store.kind === 'sqlite') && ctx?.autoSleep !== false);
33
- const halfLife = entry?.half_life_days ?? config.defaultHalfLifeDays;
34
- const tagStr = entry?.tags.join(', ') || tags.join(', ') || 'none';
30
+ const allowed = (ctx?.store === undefined || hasGroup(ctx.store, 'localFiles')) && ctx?.autoSleep !== false;
31
+ startAutoSleepIfDue(hippoRoot, tenantId, config.autoSleep, allowed, ctx?.autoSleepRunner);
32
+ const tagStr = result.tags.join(', ') || 'none';
35
33
  const warnings = (result.warnings ?? []).map((w) => `\nWarning: ${w}`).join('');
36
- return `Remembered [${result.id}] (half-life: ${halfLife}d, tags: ${tagStr})${warnings}`;
34
+ return `Remembered [${result.id}] (half-life: ${result.halfLifeDays}d, tags: ${tagStr})${warnings}`;
37
35
  }
38
36
  export async function runOutcomeTool({ args, ctx, hippoRoot, tenantId }) {
39
37
  const good = Boolean(args.good);
@@ -52,9 +50,10 @@ export async function runOutcomeTool({ args, ctx, hippoRoot, tenantId }) {
52
50
  const { applied } = await apiOutcome(apiCtx, ids, good);
53
51
  return `Applied ${good ? 'positive' : 'negative'} outcome to ${applied} memories`;
54
52
  }
55
- export function runLearnTool({ args, ctx, hippoRoot, tenantId }) {
53
+ export async function runLearnTool({ args, ctx, hippoRoot, tenantId }) {
56
54
  const days = Number(args.days) || 7;
57
- const result = apiLearn({ hippoRoot, tenantId, actor: mcpActor(ctx), store: ctx?.store }, { repoPath: process.cwd(), days, profile: MCP_LEARN });
55
+ // process.cwd() is the server's repo, not a remote caller's, so api learn refuses all but stdio and a host admin (mcpActor fails closed).
56
+ const result = await apiLearn({ hippoRoot, tenantId, actor: mcpActor(ctx), store: ctx?.store }, { repoPath: process.cwd(), days, profile: MCP_LEARN });
58
57
  if (result.status === 'not-a-repo')
59
58
  return 'No git history found.';
60
59
  if (result.status === 'no-commits')
@@ -4,6 +4,7 @@ import { type ResolveProjectIdentityOpts } from '../core/project-identity.js';
4
4
  import { type JsonValue } from '../util/json.js';
5
5
  import type { CallerProject } from '../api/prompt-hook.js';
6
6
  import type { HippoStore } from '../store/index.js';
7
+ import type { AutoSleepRunner } from '../api/auto-sleep.js';
7
8
  /** Same bounded walk as the CLI (ends at home, so HIPPO_HOME wins over ~/.hippo); cwd/opts are the test seam. */
8
9
  export declare function findHippoRoot(cwd?: string, opts?: ResolveProjectIdentityOpts): string | null;
9
10
  interface McpRequest {
@@ -25,7 +26,7 @@ interface McpResponse {
25
26
  };
26
27
  }
27
28
  /** JSON-RPC reply for a request that threw: typed API errors keep their text; anything else is logged and answered generically. */
28
- export declare function mcpErrorResponse<E>(id: McpResponse['id'], err: E, requestId?: string): McpResponse;
29
+ export declare function mcpErrorResponse<E>(id: McpResponse['id'], err: E, requestId?: string, call?: string): McpResponse;
29
30
  export type { McpRequest, McpResponse };
30
31
  /** Optional context from a non-stdio transport (the HTTP server's bound hippoRoot and auth-resolved tenantId/actor), so executeTool skips the
31
32
  * findHippoRoot() walk and the env-based resolveTenantId({}) fallback, which would pick the wrong store and tenant for HTTP callers. Stdio passes nothing. */
@@ -33,25 +34,28 @@ export interface McpContext {
33
34
  hippoRoot: string;
34
35
  tenantId: string;
35
36
  actor: string;
36
- /** The caller's role from the HTTP transport's auth; absent for stdio (the local operator, admin). Tools must use this, or a member key over HTTP-MCP
37
- * would act as admin. */
38
- role?: 'admin' | 'member';
37
+ /** The caller's authenticated role. Required so no transport can forget it; only stdio, which passes no context, is admin without one. */
38
+ role: 'admin' | 'member';
39
39
  /** Scope grants for the HTTP-MCP caller's key. Absent for stdio (admin, needs none). */
40
40
  scopes?: readonly string[];
41
41
  viaAuthResolver?: true;
42
- /** Set by the HTTP transport for the host's operator; a context without a role is in-process and implies it. */
42
+ /** Set by the HTTP transport for the host's operator; never implied by the role. */
43
43
  hostAdmin?: true;
44
44
  owner?: string;
45
45
  project?: CallerProject;
46
46
  store?: HippoStore;
47
47
  autoSleep?: false;
48
+ /** Set by the HTTP transport so auto-sleep runs off the server's event loop; stdio consolidates in its own process. */
49
+ autoSleepRunner?: AutoSleepRunner;
48
50
  /** Per-client key for state isolation under HTTP-MCP: 'stdio-${pid}' for stdio, hash(bearer + remoteAddr) for HTTP (built in src/server/mcp-http.ts);
49
51
  * defaults to `${tenantId}:default`. */
50
52
  clientKey?: string;
51
53
  }
52
- /** The api-layer actor for a tool call: stdio (no ctx) is the local operator and runs as admin; over HTTP the authenticated role is used, so a member key
53
- * never acts as admin. */
54
+ /** The api-layer actor for a tool call. Only stdio passes no context: it is the local operator, host admin. Any context runs as its own role, and an
55
+ * untyped caller that sends none runs as a member, so a missing role fails closed. */
54
56
  export declare function mcpActor(ctx: McpContext | undefined): ApiActor;
57
+ /** Stdio or a host admin: the only callers whose server cwd and git history are their own to read. */
58
+ export declare function ownsServerCwd(ctx: McpContext | undefined): boolean;
55
59
  /** One tool call after the store, config and tenant are resolved; every handler reads the same four. */
56
60
  export interface ToolCall {
57
61
  args: Record<string, JsonValue>;
@@ -1,12 +1,14 @@
1
1
  // MCP wire types, the caller context, and the JSON predicates every tool handler narrows its arguments with.
2
2
  import * as fs from 'fs';
3
3
  import { randomUUID } from 'node:crypto';
4
- import { INTERNAL_ERROR_MESSAGE, mapApiError } from '../util/http-util.js';
4
+ import { mapApiError } from '../api/error-reply.js';
5
+ import { INTERNAL_ERROR_MESSAGE } from '../util/http-util.js';
5
6
  import { errorFields, errorMessage, log } from '../util/log.js';
6
7
  import { currentRequestId } from '../util/request-scope.js';
7
8
  import { getGlobalRoot } from '../sharing/global-store.js';
8
9
  import { findHippoStoreDir } from '../core/project-identity.js';
9
- import { isStoreBusy, STORE_BUSY_MESSAGE } from '../db/index.js';
10
+ import { STORE_BUSY_MESSAGE } from '../store/port.js';
11
+ import { isStoreBusy } from '../store/sqlite/busy.js';
10
12
  // ── Find hippo root ──
11
13
  /** Same bounded walk as the CLI (ends at home, so HIPPO_HOME wins over ~/.hippo); cwd/opts are the test seam. */
12
14
  export function findHippoRoot(cwd = process.cwd(), opts) {
@@ -18,29 +20,40 @@ export function findHippoRoot(cwd = process.cwd(), opts) {
18
20
  return fs.existsSync(global) ? global : null;
19
21
  }
20
22
  /** JSON-RPC reply for a request that threw: typed API errors keep their text; anything else is logged and answered generically. */
21
- export function mcpErrorResponse(id, err, requestId = currentRequestId() ?? randomUUID()) {
22
- if (isStoreBusy(err))
23
+ export function mcpErrorResponse(id, err, requestId = currentRequestId() ?? randomUUID(), call) {
24
+ const where = call === undefined ? '' : ` (${call})`;
25
+ if (isStoreBusy(err)) {
26
+ log.warn(`mcp request${where} failed: ${errorMessage(err)}`, { requestId });
23
27
  return { jsonrpc: '2.0', id, error: { code: -32603, message: STORE_BUSY_MESSAGE } };
28
+ }
24
29
  const { status, message } = mapApiError(err);
25
- if (status !== 500)
30
+ if (status !== 500) {
31
+ log.info(`mcp request${where} failed: ${errorMessage(err)}`, { requestId, status });
26
32
  return { jsonrpc: '2.0', id, error: { code: -32603, message } };
27
- log.error(`mcp request failed: ${errorMessage(err)}`, { requestId, ...errorFields(err) });
33
+ }
34
+ log.error(`mcp request${where} failed: ${errorMessage(err)}`, { requestId, ...errorFields(err) });
28
35
  return {
29
36
  jsonrpc: '2.0',
30
37
  id,
31
38
  error: { code: -32603, message: `${INTERNAL_ERROR_MESSAGE} (request id ${requestId})`, data: { requestId } },
32
39
  };
33
40
  }
34
- /** The api-layer actor for a tool call: stdio (no ctx) is the local operator and runs as admin; over HTTP the authenticated role is used, so a member key
35
- * never acts as admin. */
41
+ /** The api-layer actor for a tool call. Only stdio passes no context: it is the local operator, host admin. Any context runs as its own role, and an
42
+ * untyped caller that sends none runs as a member, so a missing role fails closed. */
36
43
  export function mcpActor(ctx) {
37
- const actor = { subject: ctx?.actor ?? 'mcp', role: ctx?.role ?? 'admin', scopes: ctx?.scopes };
38
- if (ctx?.viaAuthResolver)
44
+ if (ctx === undefined)
45
+ return { subject: 'mcp', role: 'admin', hostAdmin: true };
46
+ const actor = { subject: ctx.actor ?? 'mcp', role: ctx.role === 'admin' ? 'admin' : 'member', scopes: ctx.scopes };
47
+ if (ctx.viaAuthResolver)
39
48
  actor.viaAuthResolver = true;
40
- if (ctx?.role === undefined || ctx.hostAdmin)
49
+ if (ctx.hostAdmin)
41
50
  actor.hostAdmin = true;
42
- if (ctx?.owner !== undefined)
51
+ if (ctx.owner !== undefined)
43
52
  actor.owner = ctx.owner;
44
53
  return actor;
45
54
  }
55
+ /** Stdio or a host admin: the only callers whose server cwd and git history are their own to read. */
56
+ export function ownsServerCwd(ctx) {
57
+ return mcpActor(ctx).hostAdmin === true;
58
+ }
46
59
  //# sourceMappingURL=protocol.js.map
@@ -2,7 +2,7 @@
2
2
  import * as path from 'path';
3
3
  import { fitBudget } from '../search/finalize.js';
4
4
  import { dropHeldCopies, duplicateKey, storedTextKeys } from '../util/same-text.js';
5
- import { retrieve as apiRetrieve, drillDown as apiDrillDown, assemble as apiAssemble, getContext as apiGetContext } from '../api/index.js';
5
+ import { retrieve as apiRetrieve, drillDown as apiDrillDown, assemble as apiAssemble, getContext as apiGetContext, } from '../api/index.js';
6
6
  import { autoDetectContext } from '../api/context-auto.js';
7
7
  import { resolveProjectIdentity } from '../core/project-identity.js';
8
8
  import { isSharedStore } from '../core/config.js';
@@ -10,12 +10,14 @@ import { detectAnchoring, hashQueryText, biasHintEnabled, snapshotRing } from '.
10
10
  import { detectAvailabilityBias } from '../api/availability.js';
11
11
  import { estimateTokens } from '../util/token-text.js';
12
12
  import { assembleCost, assembleText, drillCost, drillText } from '../api/context-render.js';
13
- import { mcpActor } from './protocol.js';
13
+ import { mcpActor, ownsServerCwd } from './protocol.js';
14
14
  import { lastRecalledIds, resolveClientKey } from './session-state.js';
15
15
  import { formatContinuityBlock, formatMemories, memoryCost, memoriesReserve, snapshotPiece, handoffPiece, trailPiece, contextCost, tailSection, planningSection, } from './format.js';
16
16
  import { isJsonString } from '../util/json.js';
17
17
  import { parseContextRequest, parseRecallRequest, toolParams } from '../api/recall-request.js';
18
18
  import { noteRecall, sessionRing, shownRecallRows } from '../api/recall-record.js';
19
+ // Candidate pool the MCP recall ranks before the token budget trims what is shown.
20
+ const MCP_RECALL_LIMIT = 50;
19
21
  function detectBiasHints(list, pool, anchorRing, queryHash) {
20
22
  const anchoring = anchorRing ? detectAnchoring(snapshotRing(anchorRing), queryHash, list[0]?.entry.id ?? null) : null;
21
23
  const availability = biasHintEnabled('availability')
@@ -126,7 +128,7 @@ export async function runRecallTool(call) {
126
128
  // RecallContractError throws reach the MCP caller raw, as mcp-recall-fresh-tail-policy.test.ts pins.
127
129
  await apiRetrieve(apiCtx, {
128
130
  ...recallOpts,
129
- limit: 50,
131
+ limit: MCP_RECALL_LIMIT,
130
132
  mode: config.physics?.enabled !== false ? 'physics' : 'hybrid',
131
133
  // The hint is computed below over the list MCP shows; the window band's copy would emit its audit row twice.
132
134
  suppressAvailabilityHint: true,
@@ -211,10 +213,10 @@ export async function runDrillTool({ args, ctx, hippoRoot, tenantId }) {
211
213
  }
212
214
  return drillText(r);
213
215
  }
214
- // The served store names the project (an HTTP daemon runs from anywhere); the global root names none, so stdio falls back to its launch cwd.
215
- function localProject(hippoRoot) {
216
+ // The served store names the project (an HTTP daemon runs from anywhere); the global root names none, so only an owner of the server cwd falls back to it.
217
+ function localProject(hippoRoot, ownsCwd) {
216
218
  const storeProject = resolveProjectIdentity(path.dirname(path.resolve(hippoRoot)));
217
- return storeProject.name !== '' ? storeProject : resolveProjectIdentity(process.cwd());
219
+ return storeProject.name !== '' || !ownsCwd ? storeProject : resolveProjectIdentity(process.cwd());
218
220
  }
219
221
  export async function runContextTool({ args, ctx, hippoRoot, config, tenantId }) {
220
222
  // Stdio names no project, and the store's folder or the launch cwd is no caller's; HTTP sends X-Hippo-Project.
@@ -226,12 +228,13 @@ export async function runContextTool({ args, ctx, hippoRoot, config, tenantId })
226
228
  return '';
227
229
  if (budget < memoriesReserve(budget))
228
230
  return ''; // not even the heading fits, so nothing prints, as at budget 0
231
+ const ownsCwd = ownsServerCwd(ctx);
229
232
  const result = await apiGetContext({ hippoRoot, tenantId, actor: mcpActor(ctx), store: ctx?.store }, {
230
- // The server's git state is no caller's, so a shared-store caller gets its project's rows without a query.
231
- q: ctx?.project ? undefined : autoDetectContext(),
233
+ // The server's git state is no remote caller's, so a project or remote caller gets rows by strength without a query.
234
+ q: ctx?.project || !ownsCwd ? undefined : autoDetectContext(),
232
235
  budget,
233
236
  exactScope,
234
- currentProject: ctx?.project ?? localProject(hippoRoot),
237
+ currentProject: ctx?.project ?? localProject(hippoRoot, ownsCwd),
235
238
  cost: contextCost,
236
239
  });
237
240
  lastRecalledIds.set(resolveClientKey(ctx), result.entries.map((r) => r.entry.id));
@@ -1,11 +1,14 @@
1
1
  // Transport-agnostic request handling: tool dispatch table, tool execution and the JSON-RPC method switch.
2
2
  import { errorMessage, log } from '../util/log.js';
3
+ import { currentSignal } from '../util/request-scope.js';
3
4
  import { STORE_NOT_PORTED_MESSAGE } from '../util/http-util.js';
4
5
  import { getGlobalRoot, initGlobal } from '../sharing/global-store.js';
5
6
  import { loadConfig } from '../core/config.js';
6
7
  import { resolveTenantId } from '../store/tenant.js';
7
- import { rethrowIfSqliteBlocked, runWithRequestStores } from '../db/index.js';
8
- import { hasGroup, storeFor } from '../store/index.js';
8
+ import { runWithRequestStores } from '../db/index.js';
9
+ import { rethrowIfSqliteBlocked } from '../util/sqlite-blocked.js';
10
+ import { hasGroup } from '../store/index.js';
11
+ import { storeFor } from '../store/sqlite/store.js';
9
12
  import { estimateTokens } from '../util/token-text.js';
10
13
  import { PACKAGE_VERSION } from '../util/version.js';
11
14
  import { validateToolArgs } from './tool-args.js';
@@ -61,20 +64,15 @@ const TOOL_HANDLERS = new Map([
61
64
  ['hippo_outcome', { handler: runOutcomeTool, storeReady: 'entryWrites' }],
62
65
  ['hippo_context', { handler: runContextTool, storeReady: 'contextReads' }],
63
66
  ['hippo_status', { handler: runStatusTool }],
64
- ['hippo_learn', { handler: runLearnTool }],
67
+ ['hippo_learn', { handler: runLearnTool, storeReady: 'lessonWrites' }],
65
68
  ['hippo_conflicts', { handler: runConflictsTool }],
66
69
  ['hippo_resolve', { handler: runResolveTool }],
67
70
  ['hippo_share', { handler: runShareTool }],
68
71
  ['hippo_peers', { handler: runPeersTool }],
69
72
  ]);
70
- /** The served store when it is not hippo.db, else null. */
71
- function otherStore(ctx) {
72
- const store = ctx?.store;
73
- return store !== undefined && store.kind !== 'sqlite' ? store : null;
74
- }
73
+ /** Stdio serves no store and runs every tool on the folder's hippo.db. */
75
74
  function runsOn(store, toolName) {
76
- const group = TOOL_HANDLERS.get(toolName)?.storeReady;
77
- return group !== undefined && hasGroup(store, group);
75
+ return store === undefined || hasGroup(store, TOOL_HANDLERS.get(toolName)?.storeReady ?? 'localFiles');
78
76
  }
79
77
  async function executeTool(name, args, ctx) {
80
78
  // With a transport context (HTTP), trust it: hippoRoot comes from the server's bound opts and tenantId from the Bearer (or loopback fallback).
@@ -99,6 +97,17 @@ function invalidArgs(id, toolName, problems) {
99
97
  result: { content: [{ type: 'text', text: `Invalid arguments for ${toolName}: ${problems.join('; ')}` }], isError: true },
100
98
  };
101
99
  }
100
+ /** One handle per store for the tool and its ledger row; stdio interleaves calls, so each gets its own scope.
101
+ * SHORTCUT: the deadline stops a call only between these steps, not a store read or fetch already running; thread the signal into them. */
102
+ function runToolScoped(toolName, args, ctx) {
103
+ return runWithRequestStores(async () => {
104
+ currentSignal()?.throwIfAborted();
105
+ const text = await executeTool(toolName, args, ctx);
106
+ currentSignal()?.throwIfAborted();
107
+ await recordMcpTokens(toolName, text, ctx);
108
+ return text;
109
+ });
110
+ }
102
111
  async function callTool(id, params, ctx) {
103
112
  const nameValue = params?.name;
104
113
  const toolName = isJsonString(nameValue) ? nameValue : '';
@@ -107,8 +116,7 @@ async function callTool(id, params, ctx) {
107
116
  return { jsonrpc: '2.0', id, error: { code: -32602, message: `Unknown tool: ${toolName.slice(0, 128)}` } };
108
117
  }
109
118
  // The same refusal a ported tool gives when it reaches hippo.db, so a client handles one shape.
110
- const other = otherStore(ctx);
111
- if (other && !runsOn(other, toolName)) {
119
+ if (!runsOn(ctx?.store, toolName)) {
112
120
  return { jsonrpc: '2.0', id, error: { code: -32603, message: STORE_NOT_PORTED_MESSAGE } };
113
121
  }
114
122
  const refusal = sharedStoreRefusal(toolName, ctx);
@@ -124,12 +132,7 @@ async function callTool(id, params, ctx) {
124
132
  return invalidArgs(id, toolName, problems);
125
133
  let output;
126
134
  try {
127
- // One handle per store for the tool and its ledger row; stdio interleaves calls, so each gets its own scope.
128
- output = await runWithRequestStores(async () => {
129
- const text = await executeTool(toolName, toolArgs, ctx);
130
- await recordMcpTokens(toolName, text, ctx);
131
- return text;
132
- });
135
+ output = await runToolScoped(toolName, toolArgs, ctx);
133
136
  }
134
137
  catch (err) {
135
138
  if (!(err instanceof RecallRequestError))
@@ -162,8 +165,7 @@ export async function handleMcpRequest(req, ctx) {
162
165
  case 'notifications/initialized':
163
166
  return null;
164
167
  case 'tools/list': {
165
- const other = otherStore(ctx);
166
- return { jsonrpc: '2.0', id, result: { tools: other ? TOOLS.filter((t) => runsOn(other, t.name)) : TOOLS } };
168
+ return { jsonrpc: '2.0', id, result: { tools: TOOLS.filter((t) => runsOn(ctx?.store, t.name)) } };
167
169
  }
168
170
  case 'tools/call':
169
171
  return callTool(id, params, ctx);
package/dist/mcp/stdio.js CHANGED
@@ -1,7 +1,8 @@
1
1
  // Stdio transport: newline-delimited JSON-RPC frames in on stdin, replies out on stdout.
2
2
  import { exitAfterFlush, installCrashHandlers } from '../util/crash-handlers.js';
3
3
  import { errorFields, errorMessage, log } from '../util/log.js';
4
- import { requestDeadlineMs } from '../server/deadline.js';
4
+ import { requestDeadlineMs } from '../util/request-scope.js';
5
+ import { MAX_BODY_BYTES } from '../util/http-util.js';
5
6
  import { parseFrame } from './framing.js';
6
7
  import { mcpErrorResponse } from './protocol.js';
7
8
  import { handleMcpRequest } from './request.js';
@@ -14,7 +15,10 @@ function send(msg) {
14
15
  process.stdout.write(JSON.stringify(msg) + '\n');
15
16
  }
16
17
  // ── Stdio transport ──
17
- let buffer = Buffer.alloc(0);
18
+ let pending = [];
19
+ let pendingBytes = 0;
20
+ // Set when the pending bytes hold a Content-Length header whose body has not fully arrived.
21
+ let needs;
18
22
  // The part of a refused frame still on its way, dropped as it arrives so it is never buffered or read as a new frame.
19
23
  let refused = 0;
20
24
  function withoutRefused(chunk) {
@@ -69,8 +73,9 @@ function callName(req) {
69
73
  const tool = req.params?.name;
70
74
  return isJsonString(tool) ? `${req.method} ${tool.slice(0, 128)}` : req.method;
71
75
  }
72
- /** Answers `req` with one timeout error at the deadline a served request has. The returned function stops the timer and says whether that answer went out, so a later reply is dropped. */
73
- function watchDeadline(req, owedReply) {
76
+ /** Answers `req` with one timeout error at the deadline a served request has. The returned function stops the timer and says whether that
77
+ * answer went out, so a later reply is dropped. */
78
+ function watchDeadline(req, owedReply, abort) {
74
79
  const ms = requestDeadlineMs();
75
80
  if (ms === 0)
76
81
  return () => false;
@@ -78,7 +83,8 @@ function watchDeadline(req, owedReply) {
78
83
  const timer = setTimeout(() => {
79
84
  expired = true;
80
85
  const name = callName(req);
81
- log.warn(`mcp: ${name} did not answer within ${ms} ms; it keeps running and its reply will be dropped`, { method: req.method });
86
+ log.warn(`mcp: ${name} did not answer within ${ms} ms; its next step is cancelled and its reply will be dropped`, { method: req.method });
87
+ abort.abort(new DOMException(`${name} did not finish within ${ms} ms`, 'TimeoutError'));
82
88
  if (!owedReply)
83
89
  return;
84
90
  const requestId = currentRequestId();
@@ -95,7 +101,7 @@ function watchDeadline(req, owedReply) {
95
101
  return expired;
96
102
  };
97
103
  }
98
- function dispatch(body) {
104
+ function dispatch(body, abort) {
99
105
  let parsed;
100
106
  try {
101
107
  parsed = JSON.parse(body);
@@ -111,31 +117,42 @@ function dispatch(body) {
111
117
  }
112
118
  const req = parsed;
113
119
  if (req.method.startsWith('notifications/')) {
114
- const stopWatching = watchDeadline(req, false);
120
+ const stopWatching = watchDeadline(req, false, abort);
115
121
  track(req.method, handleMcpRequest(req).then(() => { }).catch((err) => {
116
122
  log.error(`mcp notification ${req.method} failed: ${errorMessage(err)}`, errorFields(err));
117
123
  }).finally(stopWatching));
118
124
  return;
119
125
  }
120
126
  // A frame with no id is a notification under any method name, and JSON-RPC forbids answering one.
121
- const timedOut = watchDeadline(req, isJsonString(req.id) || isJsonNumber(req.id));
127
+ const timedOut = watchDeadline(req, isJsonString(req.id) || isJsonNumber(req.id), abort);
122
128
  track(req.method, handleMcpRequest(req).then((resp) => { if (!timedOut() && resp)
123
129
  send(resp); }).catch((err) => {
124
- if (timedOut())
125
- log.debug(`mcp: ${callName(req)} failed after its timeout reply: ${errorMessage(err)}`);
130
+ if (timedOut() && err instanceof DOMException && err.name === 'TimeoutError')
131
+ log.debug(`mcp: ${callName(req)} stopped at its deadline`);
132
+ else if (timedOut())
133
+ log.error(`mcp: ${callName(req)} failed after its timeout reply: ${errorMessage(err)}`, errorFields(err));
126
134
  else
127
- send(mcpErrorResponse(req.id, err));
135
+ send(mcpErrorResponse(req.id, err, undefined, callName(req)));
128
136
  }));
129
137
  }
130
138
  /** Wires stdin/stdout to the dispatcher; idempotent. Only the entrypoint (cli.ts `hippo mcp`, or this file run directly) should call it, so the HTTP daemon
131
139
  * (which imports `handleMcpRequest`) does not steal stdin or exit when its parent closes a pipe. */
132
140
  export function startStdioLoop() {
133
- process.stdin.on('data', (chunk) => {
134
- buffer = Buffer.concat([buffer, withoutRefused(chunk)]);
141
+ process.stdin.on('data', (raw) => {
142
+ const chunk = withoutRefused(raw);
143
+ pending.push(chunk);
144
+ pendingBytes += chunk.length;
145
+ // Joining on every chunk would copy the whole pending frame each time; wait until a frame can have ended.
146
+ if (needs === undefined ? !chunk.includes(0x0a) && pendingBytes <= MAX_BODY_BYTES : pendingBytes < needs)
147
+ return;
148
+ let buffer = Buffer.concat(pending, pendingBytes);
149
+ needs = undefined;
135
150
  while (true) {
136
151
  const result = parseFrame(buffer);
137
- if (result.kind === 'incomplete')
152
+ if (result.kind === 'incomplete') {
153
+ needs = result.needs;
138
154
  break;
155
+ }
139
156
  buffer = result.rest;
140
157
  if (result.kind === 'oversize') {
141
158
  refused = result.remainder;
@@ -144,9 +161,13 @@ export function startStdioLoop() {
144
161
  continue;
145
162
  }
146
163
  // One id per frame, so the error reply and every line logged for the call name the same request.
147
- if (result.kind === 'message')
148
- runWithRequestId(randomUUID(), () => dispatch(result.body));
164
+ if (result.kind === 'message') {
165
+ const abort = new AbortController();
166
+ runWithRequestId(randomUUID(), () => dispatch(result.body, abort), undefined, abort.signal);
167
+ }
149
168
  }
169
+ pending = buffer.length > 0 ? [buffer] : [];
170
+ pendingBytes = buffer.length;
150
171
  });
151
172
  process.stdin.on('end', () => { void exitWhenDrained(); });
152
173
  installCrashHandlers('mcp');