nlm-memory 0.4.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 (472) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/.github/workflows/ci.yml +30 -0
  3. package/LICENSE +151 -0
  4. package/README.md +119 -0
  5. package/dist/cli/classify-parity.d.ts +48 -0
  6. package/dist/cli/classify-parity.js +182 -0
  7. package/dist/cli/classify-parity.js.map +1 -0
  8. package/dist/cli/launchctl-helpers.d.ts +26 -0
  9. package/dist/cli/launchctl-helpers.js +42 -0
  10. package/dist/cli/launchctl-helpers.js.map +1 -0
  11. package/dist/cli/nlm.d.ts +25 -0
  12. package/dist/cli/nlm.js +832 -0
  13. package/dist/cli/nlm.js.map +1 -0
  14. package/dist/core/actions/actions-log.d.ts +40 -0
  15. package/dist/core/actions/actions-log.js +72 -0
  16. package/dist/core/actions/actions-log.js.map +1 -0
  17. package/dist/core/actions/overlay.d.ts +30 -0
  18. package/dist/core/actions/overlay.js +101 -0
  19. package/dist/core/actions/overlay.js.map +1 -0
  20. package/dist/core/adapters/aider.d.ts +33 -0
  21. package/dist/core/adapters/aider.js +167 -0
  22. package/dist/core/adapters/aider.js.map +1 -0
  23. package/dist/core/adapters/claude-code.d.ts +32 -0
  24. package/dist/core/adapters/claude-code.js +270 -0
  25. package/dist/core/adapters/claude-code.js.map +1 -0
  26. package/dist/core/adapters/common.d.ts +20 -0
  27. package/dist/core/adapters/common.js +60 -0
  28. package/dist/core/adapters/common.js.map +1 -0
  29. package/dist/core/adapters/from-source.d.ts +11 -0
  30. package/dist/core/adapters/from-source.js +55 -0
  31. package/dist/core/adapters/from-source.js.map +1 -0
  32. package/dist/core/adapters/hermes-agent.d.ts +34 -0
  33. package/dist/core/adapters/hermes-agent.js +192 -0
  34. package/dist/core/adapters/hermes-agent.js.map +1 -0
  35. package/dist/core/adapters/hermes.d.ts +31 -0
  36. package/dist/core/adapters/hermes.js +247 -0
  37. package/dist/core/adapters/hermes.js.map +1 -0
  38. package/dist/core/adapters/jsonl-generic.d.ts +56 -0
  39. package/dist/core/adapters/jsonl-generic.js +185 -0
  40. package/dist/core/adapters/jsonl-generic.js.map +1 -0
  41. package/dist/core/adapters/opencode.d.ts +36 -0
  42. package/dist/core/adapters/opencode.js +213 -0
  43. package/dist/core/adapters/opencode.js.map +1 -0
  44. package/dist/core/adapters/pi.d.ts +32 -0
  45. package/dist/core/adapters/pi.js +233 -0
  46. package/dist/core/adapters/pi.js.map +1 -0
  47. package/dist/core/classifier/prompt.d.ts +60 -0
  48. package/dist/core/classifier/prompt.js +178 -0
  49. package/dist/core/classifier/prompt.js.map +1 -0
  50. package/dist/core/dataset/build-dataset.d.ts +87 -0
  51. package/dist/core/dataset/build-dataset.js +335 -0
  52. package/dist/core/dataset/build-dataset.js.map +1 -0
  53. package/dist/core/embedding/chunk-body.d.ts +30 -0
  54. package/dist/core/embedding/chunk-body.js +60 -0
  55. package/dist/core/embedding/chunk-body.js.map +1 -0
  56. package/dist/core/embedding/embed-backfill.d.ts +36 -0
  57. package/dist/core/embedding/embed-backfill.js +168 -0
  58. package/dist/core/embedding/embed-backfill.js.map +1 -0
  59. package/dist/core/embedding/embed-normalize.d.ts +28 -0
  60. package/dist/core/embedding/embed-normalize.js +98 -0
  61. package/dist/core/embedding/embed-normalize.js.map +1 -0
  62. package/dist/core/facts/backfill-facts.d.ts +58 -0
  63. package/dist/core/facts/backfill-facts.js +169 -0
  64. package/dist/core/facts/backfill-facts.js.map +1 -0
  65. package/dist/core/facts/extract-facts.d.ts +20 -0
  66. package/dist/core/facts/extract-facts.js +37 -0
  67. package/dist/core/facts/extract-facts.js.map +1 -0
  68. package/dist/core/hook/citation-detect.d.ts +32 -0
  69. package/dist/core/hook/citation-detect.js +105 -0
  70. package/dist/core/hook/citation-detect.js.map +1 -0
  71. package/dist/core/hook/cite-memo.d.ts +20 -0
  72. package/dist/core/hook/cite-memo.js +68 -0
  73. package/dist/core/hook/cite-memo.js.map +1 -0
  74. package/dist/core/hook/claude-settings.d.ts +34 -0
  75. package/dist/core/hook/claude-settings.js +117 -0
  76. package/dist/core/hook/claude-settings.js.map +1 -0
  77. package/dist/core/hook/gate.d.ts +11 -0
  78. package/dist/core/hook/gate.js +19 -0
  79. package/dist/core/hook/gate.js.map +1 -0
  80. package/dist/core/hook/hook-log.d.ts +25 -0
  81. package/dist/core/hook/hook-log.js +28 -0
  82. package/dist/core/hook/hook-log.js.map +1 -0
  83. package/dist/core/hook/memo-sweep.d.ts +55 -0
  84. package/dist/core/hook/memo-sweep.js +134 -0
  85. package/dist/core/hook/memo-sweep.js.map +1 -0
  86. package/dist/core/hook/memo.d.ts +20 -0
  87. package/dist/core/hook/memo.js +66 -0
  88. package/dist/core/hook/memo.js.map +1 -0
  89. package/dist/core/hook/pointer-block.d.ts +14 -0
  90. package/dist/core/hook/pointer-block.js +19 -0
  91. package/dist/core/hook/pointer-block.js.map +1 -0
  92. package/dist/core/hook/select.d.ts +21 -0
  93. package/dist/core/hook/select.js +15 -0
  94. package/dist/core/hook/select.js.map +1 -0
  95. package/dist/core/hook/transcript.d.ts +31 -0
  96. package/dist/core/hook/transcript.js +103 -0
  97. package/dist/core/hook/transcript.js.map +1 -0
  98. package/dist/core/ingest/ingest-session.d.ts +40 -0
  99. package/dist/core/ingest/ingest-session.js +71 -0
  100. package/dist/core/ingest/ingest-session.js.map +1 -0
  101. package/dist/core/providers/provider-models.d.ts +24 -0
  102. package/dist/core/providers/provider-models.js +72 -0
  103. package/dist/core/providers/provider-models.js.map +1 -0
  104. package/dist/core/providers/provider-registry.d.ts +62 -0
  105. package/dist/core/providers/provider-registry.js +143 -0
  106. package/dist/core/providers/provider-registry.js.map +1 -0
  107. package/dist/core/recall/citation-log.d.ts +28 -0
  108. package/dist/core/recall/citation-log.js +90 -0
  109. package/dist/core/recall/citation-log.js.map +1 -0
  110. package/dist/core/recall/filter.d.ts +11 -0
  111. package/dist/core/recall/filter.js +20 -0
  112. package/dist/core/recall/filter.js.map +1 -0
  113. package/dist/core/recall/index.d.ts +6 -0
  114. package/dist/core/recall/index.js +5 -0
  115. package/dist/core/recall/index.js.map +1 -0
  116. package/dist/core/recall/match-fields.d.ts +10 -0
  117. package/dist/core/recall/match-fields.js +37 -0
  118. package/dist/core/recall/match-fields.js.map +1 -0
  119. package/dist/core/recall/query-log.d.ts +36 -0
  120. package/dist/core/recall/query-log.js +112 -0
  121. package/dist/core/recall/query-log.js.map +1 -0
  122. package/dist/core/recall/query-shape.d.ts +22 -0
  123. package/dist/core/recall/query-shape.js +64 -0
  124. package/dist/core/recall/query-shape.js.map +1 -0
  125. package/dist/core/recall/recall-service.d.ts +19 -0
  126. package/dist/core/recall/recall-service.js +252 -0
  127. package/dist/core/recall/recall-service.js.map +1 -0
  128. package/dist/core/recall/recent-log.d.ts +16 -0
  129. package/dist/core/recall/recent-log.js +46 -0
  130. package/dist/core/recall/recent-log.js.map +1 -0
  131. package/dist/core/recall/tokenize.d.ts +7 -0
  132. package/dist/core/recall/tokenize.js +18 -0
  133. package/dist/core/recall/tokenize.js.map +1 -0
  134. package/dist/core/recall/useful-scan.d.ts +52 -0
  135. package/dist/core/recall/useful-scan.js +300 -0
  136. package/dist/core/recall/useful-scan.js.map +1 -0
  137. package/dist/core/recall-facts/fact-query-log.d.ts +42 -0
  138. package/dist/core/recall-facts/fact-query-log.js +115 -0
  139. package/dist/core/recall-facts/fact-query-log.js.map +1 -0
  140. package/dist/core/recall-facts/fact-recall-service.d.ts +34 -0
  141. package/dist/core/recall-facts/fact-recall-service.js +246 -0
  142. package/dist/core/recall-facts/fact-recall-service.js.map +1 -0
  143. package/dist/core/scheduler/scan-once.d.ts +32 -0
  144. package/dist/core/scheduler/scan-once.js +100 -0
  145. package/dist/core/scheduler/scan-once.js.map +1 -0
  146. package/dist/core/scheduler/scheduler.d.ts +59 -0
  147. package/dist/core/scheduler/scheduler.js +158 -0
  148. package/dist/core/scheduler/scheduler.js.map +1 -0
  149. package/dist/core/sources/source-registry.d.ts +68 -0
  150. package/dist/core/sources/source-registry.js +208 -0
  151. package/dist/core/sources/source-registry.js.map +1 -0
  152. package/dist/core/storage/db-restore.d.ts +53 -0
  153. package/dist/core/storage/db-restore.js +113 -0
  154. package/dist/core/storage/db-restore.js.map +1 -0
  155. package/dist/core/storage/live-status.d.ts +15 -0
  156. package/dist/core/storage/live-status.js +43 -0
  157. package/dist/core/storage/live-status.js.map +1 -0
  158. package/dist/core/storage/migrate.d.ts +14 -0
  159. package/dist/core/storage/migrate.js +52 -0
  160. package/dist/core/storage/migrate.js.map +1 -0
  161. package/dist/core/storage/sqlite-fact-store.d.ts +50 -0
  162. package/dist/core/storage/sqlite-fact-store.js +256 -0
  163. package/dist/core/storage/sqlite-fact-store.js.map +1 -0
  164. package/dist/core/storage/sqlite-session-store.d.ts +152 -0
  165. package/dist/core/storage/sqlite-session-store.js +587 -0
  166. package/dist/core/storage/sqlite-session-store.js.map +1 -0
  167. package/dist/hook/pre-compact-hook.d.ts +26 -0
  168. package/dist/hook/pre-compact-hook.js +94 -0
  169. package/dist/hook/pre-compact-hook.js.map +1 -0
  170. package/dist/hook/prompt-recall-hook.d.ts +23 -0
  171. package/dist/hook/prompt-recall-hook.js +141 -0
  172. package/dist/hook/prompt-recall-hook.js.map +1 -0
  173. package/dist/hook/session-end-hook.d.ts +18 -0
  174. package/dist/hook/session-end-hook.js +67 -0
  175. package/dist/hook/session-end-hook.js.map +1 -0
  176. package/dist/hook/session-start-hook.d.ts +25 -0
  177. package/dist/hook/session-start-hook.js +129 -0
  178. package/dist/hook/session-start-hook.js.map +1 -0
  179. package/dist/hook/stop-hook.d.ts +38 -0
  180. package/dist/hook/stop-hook.js +171 -0
  181. package/dist/hook/stop-hook.js.map +1 -0
  182. package/dist/hook/subagent-start-hook.d.ts +30 -0
  183. package/dist/hook/subagent-start-hook.js +108 -0
  184. package/dist/hook/subagent-start-hook.js.map +1 -0
  185. package/dist/http/app.d.ts +65 -0
  186. package/dist/http/app.js +1009 -0
  187. package/dist/http/app.js.map +1 -0
  188. package/dist/install/claude-code.d.ts +57 -0
  189. package/dist/install/claude-code.js +76 -0
  190. package/dist/install/claude-code.js.map +1 -0
  191. package/dist/install/codex.d.ts +82 -0
  192. package/dist/install/codex.js +277 -0
  193. package/dist/install/codex.js.map +1 -0
  194. package/dist/install/hermes-agent.d.ts +35 -0
  195. package/dist/install/hermes-agent.js +48 -0
  196. package/dist/install/hermes-agent.js.map +1 -0
  197. package/dist/install/hermes.d.ts +29 -0
  198. package/dist/install/hermes.js +52 -0
  199. package/dist/install/hermes.js.map +1 -0
  200. package/dist/install/ollama.d.ts +54 -0
  201. package/dist/install/ollama.js +178 -0
  202. package/dist/install/ollama.js.map +1 -0
  203. package/dist/install/setup.d.ts +37 -0
  204. package/dist/install/setup.js +339 -0
  205. package/dist/install/setup.js.map +1 -0
  206. package/dist/llm/classifier-box.d.ts +29 -0
  207. package/dist/llm/classifier-box.js +43 -0
  208. package/dist/llm/classifier-box.js.map +1 -0
  209. package/dist/llm/deepseek-client.d.ts +40 -0
  210. package/dist/llm/deepseek-client.js +114 -0
  211. package/dist/llm/deepseek-client.js.map +1 -0
  212. package/dist/llm/env-autoload.d.ts +8 -0
  213. package/dist/llm/env-autoload.js +54 -0
  214. package/dist/llm/env-autoload.js.map +1 -0
  215. package/dist/llm/ollama-client.d.ts +47 -0
  216. package/dist/llm/ollama-client.js +156 -0
  217. package/dist/llm/ollama-client.js.map +1 -0
  218. package/dist/mcp/server.d.ts +64 -0
  219. package/dist/mcp/server.js +430 -0
  220. package/dist/mcp/server.js.map +1 -0
  221. package/dist/ports/fact-store.d.ts +82 -0
  222. package/dist/ports/fact-store.js +16 -0
  223. package/dist/ports/fact-store.js.map +1 -0
  224. package/dist/ports/llm-client.d.ts +42 -0
  225. package/dist/ports/llm-client.js +14 -0
  226. package/dist/ports/llm-client.js.map +1 -0
  227. package/dist/ports/logger.d.ts +13 -0
  228. package/dist/ports/logger.js +8 -0
  229. package/dist/ports/logger.js.map +1 -0
  230. package/dist/ports/session-store.d.ts +29 -0
  231. package/dist/ports/session-store.js +9 -0
  232. package/dist/ports/session-store.js.map +1 -0
  233. package/dist/ports/transcript-adapter.d.ts +48 -0
  234. package/dist/ports/transcript-adapter.js +15 -0
  235. package/dist/ports/transcript-adapter.js.map +1 -0
  236. package/dist/shared/types.d.ts +129 -0
  237. package/dist/shared/types.js +6 -0
  238. package/dist/shared/types.js.map +1 -0
  239. package/dist/ui/assets/index-BA6IpU8g.css +1 -0
  240. package/dist/ui/assets/index-B_qIVV0k.js +69 -0
  241. package/dist/ui/index.html +13 -0
  242. package/docs/methodology/re-derivation-rate.md +112 -0
  243. package/docs/methodology/useful-hit-rate.md +79 -0
  244. package/docs/plans/2026-05-20-fts5-lexical-recall.md +1088 -0
  245. package/docs/plans/2026-05-20-recall-daemon-wedge-fix.md +662 -0
  246. package/docs/plans/2026-05-20-recall-hook-design.md +131 -0
  247. package/docs/plans/2026-05-20-recall-hook-implementation.md +1222 -0
  248. package/docs/plans/desktop-product.md +69 -0
  249. package/docs/plans/factstore-design.md +236 -0
  250. package/logs/CHANGELOG/CHANGELOG-2026.md +1389 -0
  251. package/logs/CHANGELOG/CHANGELOG.md +320 -0
  252. package/migrations/000_initial_schema.sql +174 -0
  253. package/migrations/001_entity_type_rename.sql +17 -0
  254. package/migrations/002_adapter_state_extend.sql +12 -0
  255. package/migrations/003_session_embeddings.sql +11 -0
  256. package/migrations/004_facts.sql +46 -0
  257. package/migrations/005_sources.sql +31 -0
  258. package/migrations/006_providers.sql +33 -0
  259. package/migrations/007_source_tokens.sql +17 -0
  260. package/migrations/008_fts_rebuild.sql +9 -0
  261. package/migrations/009_session_embedding_chunks.sql +46 -0
  262. package/migrations/010_sources_opencode.sql +30 -0
  263. package/migrations/011_sources_hermes_agent.sql +30 -0
  264. package/migrations/012_sources_aider.sql +30 -0
  265. package/migrations/013_adapter_state_failure_count.sql +12 -0
  266. package/package.json +56 -0
  267. package/plugin/.codex-plugin/plugin.json +22 -0
  268. package/plugin/.mcp.json +8 -0
  269. package/plugin/README.md +51 -0
  270. package/plugin/hooks/hooks.json +25 -0
  271. package/plugin/scripts/prompt-recall-hook.mjs +202 -0
  272. package/plugin/scripts/stop-hook.mjs +306 -0
  273. package/plugin-hermes-agent/README.md +49 -0
  274. package/plugin-hermes-agent/__init__.py +75 -0
  275. package/plugin-hermes-agent/plugin.yaml +15 -0
  276. package/scripts/backfill-citations.mjs +0 -0
  277. package/scripts/build-codex-plugin.mjs +61 -0
  278. package/scripts/deepseek-probe.mjs +67 -0
  279. package/scripts/extract-triples.mjs +207 -0
  280. package/scripts/longmemeval/embedding-cache.ts +77 -0
  281. package/scripts/longmemeval/fetch-dataset.sh +25 -0
  282. package/scripts/longmemeval/run-harness.ts +315 -0
  283. package/scripts/longmemeval/scorer.ts +99 -0
  284. package/scripts/longmemeval/tsconfig.json +9 -0
  285. package/scripts/longmemeval/types.ts +35 -0
  286. package/scripts/nlm-daily-digest.py +239 -0
  287. package/scripts/nlm-daily-digest.sh +28 -0
  288. package/src/cli/classify-parity.ts +257 -0
  289. package/src/cli/launchctl-helpers.ts +49 -0
  290. package/src/cli/nlm.ts +885 -0
  291. package/src/core/actions/actions-log.ts +118 -0
  292. package/src/core/actions/overlay.ts +117 -0
  293. package/src/core/adapters/aider.ts +205 -0
  294. package/src/core/adapters/claude-code.ts +293 -0
  295. package/src/core/adapters/common.ts +54 -0
  296. package/src/core/adapters/from-source.ts +57 -0
  297. package/src/core/adapters/hermes-agent.ts +240 -0
  298. package/src/core/adapters/hermes.ts +277 -0
  299. package/src/core/adapters/jsonl-generic.ts +208 -0
  300. package/src/core/adapters/opencode.ts +281 -0
  301. package/src/core/adapters/pi.ts +264 -0
  302. package/src/core/classifier/prompt.ts +200 -0
  303. package/src/core/dataset/build-dataset.ts +463 -0
  304. package/src/core/embedding/chunk-body.ts +76 -0
  305. package/src/core/embedding/embed-backfill.ts +210 -0
  306. package/src/core/embedding/embed-normalize.ts +135 -0
  307. package/src/core/facts/backfill-facts.ts +254 -0
  308. package/src/core/facts/extract-facts.ts +50 -0
  309. package/src/core/hook/citation-detect.ts +124 -0
  310. package/src/core/hook/cite-memo.ts +68 -0
  311. package/src/core/hook/claude-settings.ts +166 -0
  312. package/src/core/hook/gate.ts +25 -0
  313. package/src/core/hook/hook-log.ts +41 -0
  314. package/src/core/hook/memo-sweep.ts +164 -0
  315. package/src/core/hook/memo.ts +67 -0
  316. package/src/core/hook/pointer-block.ts +26 -0
  317. package/src/core/hook/select.ts +32 -0
  318. package/src/core/hook/transcript.ts +121 -0
  319. package/src/core/ingest/ingest-session.ts +111 -0
  320. package/src/core/providers/provider-models.ts +100 -0
  321. package/src/core/providers/provider-registry.ts +196 -0
  322. package/src/core/recall/citation-log.ts +108 -0
  323. package/src/core/recall/filter.ts +27 -0
  324. package/src/core/recall/index.ts +6 -0
  325. package/src/core/recall/match-fields.ts +40 -0
  326. package/src/core/recall/query-log.ts +149 -0
  327. package/src/core/recall/query-shape.ts +66 -0
  328. package/src/core/recall/recall-service.ts +320 -0
  329. package/src/core/recall/recent-log.ts +59 -0
  330. package/src/core/recall/tokenize.ts +18 -0
  331. package/src/core/recall/useful-scan.ts +336 -0
  332. package/src/core/recall-facts/fact-query-log.ts +150 -0
  333. package/src/core/recall-facts/fact-recall-service.ts +327 -0
  334. package/src/core/scheduler/scan-once.ts +142 -0
  335. package/src/core/scheduler/scheduler.ts +225 -0
  336. package/src/core/sources/source-registry.ts +260 -0
  337. package/src/core/storage/db-restore.ts +133 -0
  338. package/src/core/storage/live-status.ts +45 -0
  339. package/src/core/storage/migrate.ts +72 -0
  340. package/src/core/storage/sqlite-fact-store.ts +304 -0
  341. package/src/core/storage/sqlite-session-store.ts +765 -0
  342. package/src/hook/prompt-recall-hook.ts +174 -0
  343. package/src/hook/session-end-hook.ts +81 -0
  344. package/src/hook/session-start-hook.ts +165 -0
  345. package/src/hook/stop-hook.ts +236 -0
  346. package/src/http/app.ts +1114 -0
  347. package/src/install/claude-code.ts +128 -0
  348. package/src/install/codex.ts +367 -0
  349. package/src/install/hermes-agent.ts +76 -0
  350. package/src/install/hermes.ts +78 -0
  351. package/src/install/ollama.ts +208 -0
  352. package/src/install/setup.ts +368 -0
  353. package/src/llm/classifier-box.ts +64 -0
  354. package/src/llm/deepseek-client.ts +150 -0
  355. package/src/llm/env-autoload.ts +55 -0
  356. package/src/llm/ollama-client.ts +189 -0
  357. package/src/mcp/server.ts +534 -0
  358. package/src/ports/fact-store.ts +102 -0
  359. package/src/ports/llm-client.ts +52 -0
  360. package/src/ports/logger.ts +16 -0
  361. package/src/ports/session-store.ts +45 -0
  362. package/src/ports/transcript-adapter.ts +55 -0
  363. package/src/shared/types.ts +145 -0
  364. package/src/ui/App.tsx +58 -0
  365. package/src/ui/components/PromoteOpenButton.tsx +65 -0
  366. package/src/ui/components/SessionDrawer.tsx +136 -0
  367. package/src/ui/components/SideNav.tsx +162 -0
  368. package/src/ui/components/Skeleton.tsx +107 -0
  369. package/src/ui/index.html +13 -0
  370. package/src/ui/lib/actions.ts +30 -0
  371. package/src/ui/lib/api.ts +92 -0
  372. package/src/ui/lib/dataset.ts +141 -0
  373. package/src/ui/lib/registries.ts +155 -0
  374. package/src/ui/lib/view-settings.ts +41 -0
  375. package/src/ui/main.tsx +15 -0
  376. package/src/ui/pages/Live.tsx +229 -0
  377. package/src/ui/pages/Pulse.tsx +415 -0
  378. package/src/ui/pages/Recall.tsx +190 -0
  379. package/src/ui/pages/River.tsx +308 -0
  380. package/src/ui/pages/Search.tsx +93 -0
  381. package/src/ui/pages/Stub.tsx +9 -0
  382. package/src/ui/pages/Thread.tsx +262 -0
  383. package/src/ui/pages/settings/Classifier.tsx +227 -0
  384. package/src/ui/pages/settings/Data.tsx +190 -0
  385. package/src/ui/pages/settings/Index.tsx +65 -0
  386. package/src/ui/pages/settings/Labels.tsx +224 -0
  387. package/src/ui/pages/settings/Providers.tsx +305 -0
  388. package/src/ui/pages/settings/SettingsSubnav.tsx +28 -0
  389. package/src/ui/pages/settings/Sources.tsx +326 -0
  390. package/src/ui/pages/settings/Views.tsx +96 -0
  391. package/src/ui/styles.css +1766 -0
  392. package/src/ui/tsconfig.json +21 -0
  393. package/src/ui/vite.config.ts +19 -0
  394. package/tests/fixtures/claude_code/short_session.jsonl +2 -0
  395. package/tests/fixtures/claude_code/standard_iso.jsonl +4 -0
  396. package/tests/fixtures/claude_code/tool_heavy.jsonl +8 -0
  397. package/tests/fixtures/claude_code/with_subagent.jsonl +7 -0
  398. package/tests/fixtures/facts.ts +17 -0
  399. package/tests/fixtures/golden-corpus.ts +85 -0
  400. package/tests/fixtures/hermes/paired_request_dump.json +24 -0
  401. package/tests/fixtures/hermes/paired_session.json +23 -0
  402. package/tests/fixtures/hermes/request_dump.json +28 -0
  403. package/tests/fixtures/hermes/session_iso.json +38 -0
  404. package/tests/fixtures/hermes/session_unix.json +38 -0
  405. package/tests/fixtures/hermes/system_only.json +18 -0
  406. package/tests/fixtures/pi/error-connection-abort.jsonl +8 -0
  407. package/tests/fixtures/pi/short-successful.jsonl +5 -0
  408. package/tests/fixtures/pi/with-custom-message.jsonl +6 -0
  409. package/tests/fixtures/sessions.ts +22 -0
  410. package/tests/integration/backfill-facts.test.ts +362 -0
  411. package/tests/integration/citation-explicit.test.ts +111 -0
  412. package/tests/integration/cite-event.test.ts +169 -0
  413. package/tests/integration/cite-memo.test.ts +87 -0
  414. package/tests/integration/db-restore.test.ts +153 -0
  415. package/tests/integration/embed-backfill.test.ts +176 -0
  416. package/tests/integration/fact-supersedence.test.ts +313 -0
  417. package/tests/integration/fts-index.test.ts +60 -0
  418. package/tests/integration/getbyids-sqlite.test.ts +60 -0
  419. package/tests/integration/hermes-agent-hooks.test.ts +248 -0
  420. package/tests/integration/hook-claude-settings.test.ts +205 -0
  421. package/tests/integration/hook-log.test.ts +54 -0
  422. package/tests/integration/hook-memo.test.ts +68 -0
  423. package/tests/integration/hook-pre-compact.test.ts +105 -0
  424. package/tests/integration/hook-subagent-start.test.ts +102 -0
  425. package/tests/integration/http.test.ts +401 -0
  426. package/tests/integration/keyword-search-fts.test.ts +66 -0
  427. package/tests/integration/mcp-recall-logging.test.ts +88 -0
  428. package/tests/integration/mcp.test.ts +248 -0
  429. package/tests/integration/memo-sweep.test.ts +91 -0
  430. package/tests/integration/prompt-recall-hook.test.ts +88 -0
  431. package/tests/integration/provider-registry.test.ts +107 -0
  432. package/tests/integration/recall-golden.test.ts +59 -0
  433. package/tests/integration/recall-sqlite.test.ts +169 -0
  434. package/tests/integration/scheduler.test.ts +391 -0
  435. package/tests/integration/session-end-hook.test.ts +48 -0
  436. package/tests/integration/session-start-hook.test.ts +126 -0
  437. package/tests/integration/source-registry.test.ts +120 -0
  438. package/tests/integration/sqlite-fact-store.test.ts +346 -0
  439. package/tests/integration/stop-hook.test.ts +560 -0
  440. package/tests/integration/wal-checkpoint.test.ts +49 -0
  441. package/tests/unit/cli/launchctl-helpers.test.ts +60 -0
  442. package/tests/unit/core/adapters/aider.test.ts +230 -0
  443. package/tests/unit/core/adapters/claude-code.test.ts +118 -0
  444. package/tests/unit/core/adapters/hermes-agent.test.ts +329 -0
  445. package/tests/unit/core/adapters/hermes.test.ts +81 -0
  446. package/tests/unit/core/adapters/jsonl-generic.test.ts +142 -0
  447. package/tests/unit/core/adapters/opencode.test.ts +354 -0
  448. package/tests/unit/core/adapters/pi.test.ts +110 -0
  449. package/tests/unit/core/classifier/prompt.test.ts +126 -0
  450. package/tests/unit/core/embedding/chunk-body.test.ts +100 -0
  451. package/tests/unit/core/facts/extract-facts.test.ts +117 -0
  452. package/tests/unit/core/filter.test.ts +40 -0
  453. package/tests/unit/core/hook/citation-detect-cite-session.test.ts +96 -0
  454. package/tests/unit/core/hook/citation-detect.test.ts +124 -0
  455. package/tests/unit/core/hook/gate.test.ts +29 -0
  456. package/tests/unit/core/hook/pointer-block.test.ts +22 -0
  457. package/tests/unit/core/hook/select.test.ts +66 -0
  458. package/tests/unit/core/match-fields.test.ts +39 -0
  459. package/tests/unit/core/mcp-cite-session.test.ts +51 -0
  460. package/tests/unit/core/providers/provider-models.test.ts +101 -0
  461. package/tests/unit/core/query-shape.test.ts +92 -0
  462. package/tests/unit/core/recall-facts/fact-recall-service.test.ts +258 -0
  463. package/tests/unit/core/recall-service.test.ts +200 -0
  464. package/tests/unit/core/storage/live-status.test.ts +54 -0
  465. package/tests/unit/core/tokenize.test.ts +32 -0
  466. package/tests/unit/core/useful-scan.test.ts +537 -0
  467. package/tests/unit/llm/embed.test.ts +93 -0
  468. package/tests/unit/llm/ollama-client.test.ts +124 -0
  469. package/tests/unit/scripts/longmemeval-scorer.test.ts +114 -0
  470. package/tsconfig.json +31 -0
  471. package/tsconfig.test.json +11 -0
  472. package/vitest.config.ts +22 -0
@@ -0,0 +1,587 @@
1
+ /**
2
+ * SqliteSessionStore — the canonical SessionStore implementation backed by
3
+ * better-sqlite3 with the sqlite-vec extension loaded for KNN search.
4
+ *
5
+ * Layering note: core/ imports this concrete class only at the composition
6
+ * root (CLI / server bootstrap). The recall use case and every other piece
7
+ * of core depends on the SessionStore *port*, never on this file.
8
+ *
9
+ * Schema parity with the Python daemon: sessions row + session_entities +
10
+ * markers + session_embeddings (vec0). Idle-status overlay (computed from
11
+ * transcript mtime) is deferred to a later phase — A.2 returns the persisted
12
+ * status verbatim.
13
+ */
14
+ import { existsSync, mkdirSync } from "node:fs";
15
+ import { dirname, resolve } from "node:path";
16
+ import Database from "better-sqlite3";
17
+ import * as sqliteVec from "sqlite-vec";
18
+ import { liveSessionStatus } from "./live-status.js";
19
+ import { loadActionOverlay, openQuestionId } from "../actions/overlay.js";
20
+ import { runMigrations } from "./migrate.js";
21
+ import { tokenize } from "../recall/tokenize.js";
22
+ import { chunkSessionText } from "../embedding/chunk-body.js";
23
+ export class SqliteSessionStore {
24
+ db;
25
+ constructor(opts) {
26
+ const dbPath = resolve(opts.dbPath);
27
+ const parent = dirname(dbPath);
28
+ if (!existsSync(parent))
29
+ mkdirSync(parent, { recursive: true });
30
+ this.db = new Database(dbPath, opts.readonly ? { readonly: true } : {});
31
+ this.db.pragma("foreign_keys = ON");
32
+ this.db.pragma("journal_mode = WAL");
33
+ sqliteVec.load(this.db);
34
+ if (!opts.readonly) {
35
+ runMigrations(this.db, opts.migrationsDir);
36
+ }
37
+ }
38
+ close() {
39
+ this.db.close();
40
+ }
41
+ /**
42
+ * Drains the WAL into the main database and truncates the -wal file.
43
+ * WAL mode is on but nothing else checkpoints, so the file grows
44
+ * unbounded under continuous readers. The daemon calls this on an
45
+ * interval. Synchronous — keep the WAL small so each call is cheap.
46
+ */
47
+ checkpoint() {
48
+ this.db.pragma("wal_checkpoint(TRUNCATE)");
49
+ }
50
+ /** Raw db handle for ingest helpers (Scheduler, scanOnce). Avoid using
51
+ * directly from the recall path — it bypasses the SessionStore port. */
52
+ rawDb() {
53
+ return this.db;
54
+ }
55
+ /** Recently-written sessions ordered by created_at desc. Powers /live Writes column. */
56
+ recentWrites(limit) {
57
+ return this.db
58
+ .prepare(`SELECT id, runtime, label, summary, created_at AS createdAt
59
+ FROM sessions
60
+ ORDER BY created_at DESC
61
+ LIMIT ?`)
62
+ .all(limit);
63
+ }
64
+ /** Recently-extracted markers ordered by session created_at desc. Powers /live Decisions column. */
65
+ recentMarkers(limit) {
66
+ return this.db
67
+ .prepare(`SELECT m.session_id AS sessionId, m.kind, m.text, s.label, s.created_at AS createdAt
68
+ FROM markers m
69
+ JOIN sessions s ON s.id = m.session_id
70
+ ORDER BY s.created_at DESC, m.position ASC
71
+ LIMIT ?`)
72
+ .all(limit);
73
+ }
74
+ /**
75
+ * Atomic ingest: writes the session row, markers, entity rows + links,
76
+ * supersedes edge (if any), and the embedding (best-effort) in one
77
+ * transaction. Idempotent on re-ingest — ON CONFLICT updates the session
78
+ * in place; markers are deleted and rewritten; entity links use INSERT OR
79
+ * IGNORE; embedding row is DELETE+INSERT (vec0 doesn't UPDATE).
80
+ *
81
+ * Mirrors Python's SQLiteStore.insert_session. Markdown projection is not
82
+ * yet ported and skipped here.
83
+ */
84
+ async insertSession(record, embedder = null, supersedes = null, factSink = null) {
85
+ const db = this.db;
86
+ const txn = db.transaction(() => {
87
+ db.prepare(`
88
+ INSERT INTO sessions (
89
+ id, runtime, runtime_session_id, started_at, ended_at, duration_min,
90
+ label, summary, body, status,
91
+ transcript_kind, transcript_path, transcript_offset, transcript_length
92
+ ) VALUES (@id, @runtime, @runtimeSessionId, @startedAt, @endedAt, @durationMin,
93
+ @label, @summary, @body, @status,
94
+ @transcriptKind, @transcriptPath, @transcriptOffset, @transcriptLength)
95
+ ON CONFLICT(id) DO UPDATE SET
96
+ ended_at = excluded.ended_at,
97
+ duration_min = excluded.duration_min,
98
+ label = excluded.label,
99
+ summary = excluded.summary,
100
+ body = excluded.body,
101
+ status = excluded.status,
102
+ updated_at = datetime('now')
103
+ `).run({
104
+ id: record.id,
105
+ runtime: record.runtime,
106
+ runtimeSessionId: record.runtimeSessionId,
107
+ startedAt: record.startedAt,
108
+ endedAt: record.endedAt,
109
+ durationMin: record.durationMin,
110
+ label: record.label,
111
+ summary: record.summary,
112
+ body: record.body,
113
+ status: record.status === "idle" ? "active" : record.status,
114
+ transcriptKind: record.transcriptKind,
115
+ transcriptPath: record.transcriptPath,
116
+ transcriptOffset: record.transcriptOffset,
117
+ transcriptLength: record.transcriptLength,
118
+ });
119
+ db.prepare("DELETE FROM markers WHERE session_id = ?").run(record.id);
120
+ const markerStmt = db.prepare("INSERT INTO markers (session_id, kind, text, position) VALUES (?, ?, ?, ?)");
121
+ record.decisions.forEach((d, i) => markerStmt.run(record.id, "decision", d.trim(), i));
122
+ record.openQuestions.forEach((q, i) => markerStmt.run(record.id, "open", q.trim(), i));
123
+ const insertEnt = db.prepare(`
124
+ INSERT OR IGNORE INTO entities
125
+ (canonical, type, status, source, first_seen_session, last_seen_session, session_count)
126
+ VALUES (?, 'candidate', 'candidate', 'auto-detected', ?, ?, 0)
127
+ `);
128
+ const touchEnt = db.prepare(`
129
+ UPDATE entities
130
+ SET last_seen_session = ?, session_count = session_count + 1, updated_at = datetime('now')
131
+ WHERE canonical = ?
132
+ `);
133
+ const linkEnt = db.prepare("INSERT OR IGNORE INTO session_entities (session_id, entity_canonical) VALUES (?, ?)");
134
+ for (const raw of record.entities) {
135
+ const name = raw.trim();
136
+ if (!name)
137
+ continue;
138
+ insertEnt.run(name, record.id, record.id);
139
+ touchEnt.run(record.id, name);
140
+ linkEnt.run(record.id, name);
141
+ }
142
+ if (supersedes) {
143
+ db.prepare(`INSERT OR IGNORE INTO session_edges (from_session, to_session, kind)
144
+ VALUES (?, ?, 'supersedes')`).run(record.id, supersedes);
145
+ db.prepare("UPDATE sessions SET status = 'superseded', updated_at = datetime('now') WHERE id = ?").run(supersedes);
146
+ }
147
+ // Facts ingest is part of the session txn — either both commit or both
148
+ // roll back. On re-ingest (ON CONFLICT updates the session above), we
149
+ // delete prior facts for this source_session_id before re-inserting so
150
+ // the row count matches the latest classifier output. Without this,
151
+ // re-ingest accumulates duplicates.
152
+ //
153
+ // Phase B.4 — deterministic supersedence on (subject, predicate)
154
+ // collision. For each new fact, after insert, look up any OTHER
155
+ // non-superseded fact with the same (subject, predicate). Mark the
156
+ // older one as superseded by the new fact's id. Always-supersede
157
+ // policy applies even when value is unchanged — same-value re-assertion
158
+ // carries new provenance (new source_session_id) and is informative
159
+ // history. See Section 2 of factstore-design.md.
160
+ //
161
+ // Ordering note: insertManyInTxn FIRST so the new fact id exists in
162
+ // facts(id) before any UPDATE sets superseded_by = newId (the FK
163
+ // would reject otherwise). The DELETE above plus the CASCADE-SET-NULL
164
+ // on superseded_by means re-ingest naturally repairs chains: if an
165
+ // earlier ingest of this session superseded a fact from another
166
+ // session, deleting our prior fact unlinks the chain; the loop below
167
+ // re-establishes it with the freshly-inserted row.
168
+ if (factSink !== null) {
169
+ this.applyFactsInTxn(record.id, factSink.factStore, factSink.facts);
170
+ }
171
+ });
172
+ txn();
173
+ // Embedding is best-effort and lives outside the txn so a slow Ollama
174
+ // doesn't block the row commit. Body is chunked into ≤MAX_CHUNK_CHARS
175
+ // windows (see chunk-body.ts) and each chunk embedded independently.
176
+ // Per-chunk embedder failures are tolerated; the chunks that did embed
177
+ // still contribute to recall.
178
+ if (embedder) {
179
+ const chunks = chunkSessionText({
180
+ label: record.label,
181
+ summary: record.summary,
182
+ body: record.body,
183
+ });
184
+ this.deleteSessionChunks(record.id);
185
+ for (let chunkIdx = 0; chunkIdx < chunks.length; chunkIdx++) {
186
+ const text = chunks[chunkIdx];
187
+ if (!text)
188
+ continue;
189
+ try {
190
+ const { vector } = await embedder.embed(text, "document");
191
+ this.insertChunkEmbedding(record.id, chunkIdx, vector);
192
+ }
193
+ catch {
194
+ // Per-chunk embedder failure must not roll the ingest back or
195
+ // abort subsequent chunks.
196
+ }
197
+ }
198
+ if (factSink !== null) {
199
+ await this.embedFacts(factSink.factStore, factSink.facts, embedder);
200
+ }
201
+ }
202
+ }
203
+ deleteSessionChunks(sessionId) {
204
+ const db = this.db;
205
+ const rows = db
206
+ .prepare("SELECT chunk_id FROM session_chunk_map WHERE session_id = ?")
207
+ .all(sessionId);
208
+ if (rows.length === 0)
209
+ return;
210
+ const placeholders = rows.map(() => "?").join(",");
211
+ const ids = rows.map((r) => r.chunk_id);
212
+ db.prepare(`DELETE FROM session_embedding_chunks WHERE chunk_id IN (${placeholders})`).run(...ids);
213
+ db.prepare("DELETE FROM session_chunk_map WHERE session_id = ?").run(sessionId);
214
+ }
215
+ insertChunkEmbedding(sessionId, chunkIdx, vector) {
216
+ const db = this.db;
217
+ const blob = Buffer.from(vector.buffer, vector.byteOffset, vector.byteLength);
218
+ // vec0 enforces strict integer typing on aux columns; better-sqlite3 binds
219
+ // JS numbers as FLOAT, so cast chunk_idx via BigInt to bind as INTEGER.
220
+ const idxInt = BigInt(chunkIdx);
221
+ const info = db
222
+ .prepare("INSERT INTO session_embedding_chunks (embedding, session_id, chunk_idx) VALUES (?, ?, ?)")
223
+ .run(blob, sessionId, idxInt);
224
+ const chunkId = Number(info.lastInsertRowid);
225
+ db.prepare("INSERT INTO session_chunk_map (chunk_id, session_id, chunk_idx) VALUES (?, ?, ?)").run(chunkId, sessionId, chunkIdx);
226
+ }
227
+ /**
228
+ * Phase B.5 — backfill entry point. Writes facts (with deterministic
229
+ * supersedence + best-effort embeddings) for an EXISTING session row
230
+ * without touching it. Opens its own transaction; callers must not be
231
+ * inside one. The session row must already exist in `sessions` or the
232
+ * FK on facts.source_session_id rejects.
233
+ *
234
+ * Use this when ingesting facts after the fact — e.g. running the
235
+ * classifier across a historical corpus that predates the B.2 ingest
236
+ * write path. The live ingest path (`insertSession`) keeps using the
237
+ * internal helpers directly so session+facts commit together.
238
+ */
239
+ async insertFactsForSession(sessionId, factStore, facts, embedder = null) {
240
+ const db = this.db;
241
+ const txn = db.transaction(() => {
242
+ this.applyFactsInTxn(sessionId, factStore, facts);
243
+ });
244
+ txn();
245
+ if (embedder) {
246
+ await this.embedFacts(factStore, facts, embedder);
247
+ }
248
+ }
249
+ /**
250
+ * Sync core of the fact-ingest block. Runs inside an EXISTING transaction
251
+ * — opens no txn of its own. Used by both `insertSession` (Phase B.2
252
+ * atomic ingest) and `insertFactsForSession` (Phase B.5 backfill).
253
+ *
254
+ * Behavior (mirrored across both callers):
255
+ * 1. DELETE prior facts attributed to this session (idempotent on
256
+ * backfill, drops stale rows on re-ingest).
257
+ * 2. Insert all new facts atomically.
258
+ * 3. For each, mark the prior current (subject, predicate) fact as
259
+ * superseded — Phase B.4 deterministic supersedence policy.
260
+ *
261
+ * Ordering: inserts before updates so the supersedence FK target exists.
262
+ * CASCADE-SET-NULL on `superseded_by` handles chain repair on re-ingest.
263
+ */
264
+ applyFactsInTxn(sessionId, factStore, facts) {
265
+ const db = this.db;
266
+ db.prepare("DELETE FROM facts WHERE source_session_id = ?").run(sessionId);
267
+ factStore.insertManyInTxn(facts);
268
+ if (facts.length === 0)
269
+ return;
270
+ const findCollisionStmt = db.prepare(`
271
+ SELECT id
272
+ FROM facts
273
+ WHERE subject = ?
274
+ AND predicate = ?
275
+ AND superseded_by IS NULL
276
+ AND id != ?
277
+ ORDER BY created_at DESC
278
+ LIMIT 1
279
+ `);
280
+ const markSupersededStmt = db.prepare("UPDATE facts SET superseded_by = ? WHERE id = ?");
281
+ for (const fact of facts) {
282
+ const prior = findCollisionStmt.get(fact.subject, fact.predicate, fact.id);
283
+ if (prior)
284
+ markSupersededStmt.run(fact.id, prior.id);
285
+ }
286
+ }
287
+ /**
288
+ * Best-effort per-fact embedding. Writes `${subject} ${predicate} ${value}`
289
+ * embeddings to fact_embeddings via FactStore.upsertEmbedding. Per-fact
290
+ * failures don't abort the batch, and never affect committed fact rows.
291
+ */
292
+ async embedFacts(factStore, facts, embedder) {
293
+ for (const fact of facts) {
294
+ const factText = `${fact.subject} ${fact.predicate} ${fact.value}`.trim();
295
+ if (!factText)
296
+ continue;
297
+ try {
298
+ const { vector } = await embedder.embed(factText, "document");
299
+ factStore.upsertEmbedding(fact.id, vector);
300
+ }
301
+ catch {
302
+ // Per-fact embedding failure must not abort embedding of subsequent
303
+ // facts. The fact row stays current; semantic recall just misses it
304
+ // until a future re-ingest.
305
+ }
306
+ }
307
+ }
308
+ async list(filter) {
309
+ const rows = this.db
310
+ .prepare(`
311
+ SELECT id, runtime, runtime_session_id, started_at, ended_at, duration_min,
312
+ label, summary, status, transcript_kind, transcript_path, body
313
+ FROM sessions
314
+ ORDER BY started_at ASC
315
+ `)
316
+ .all();
317
+ if (rows.length === 0)
318
+ return [];
319
+ const ids = rows.map((r) => r.id);
320
+ const entitiesByIdMap = this.loadEntities(ids);
321
+ const markersByIdMap = this.loadMarkers(ids);
322
+ const overlay = loadActionOverlay(this.db);
323
+ const sessions = rows.map((r) => this.rowToSession(r, entitiesByIdMap, markersByIdMap, overlay));
324
+ if (!filter)
325
+ return sessions;
326
+ return sessions.filter((s) => {
327
+ if (filter.entity !== undefined && !s.entities.includes(filter.entity)) {
328
+ return false;
329
+ }
330
+ if (filter.hasDecisions === true && s.decisions.length === 0)
331
+ return false;
332
+ if (filter.hasOpenQuestions === true && s.open.length === 0)
333
+ return false;
334
+ return true;
335
+ });
336
+ }
337
+ async getById(sessionId) {
338
+ const row = this.db
339
+ .prepare(`
340
+ SELECT id, runtime, runtime_session_id, started_at, ended_at, duration_min,
341
+ label, summary, status, transcript_kind, transcript_path, body
342
+ FROM sessions
343
+ WHERE id = ?
344
+ `)
345
+ .get(sessionId);
346
+ if (!row)
347
+ return null;
348
+ const entities = this.loadEntities([sessionId]);
349
+ const markers = this.loadMarkers([sessionId]);
350
+ const overlay = loadActionOverlay(this.db);
351
+ return this.rowToSession(row, entities, markers, overlay);
352
+ }
353
+ /**
354
+ * Batched session fetch for the recall path. Deliberately omits the
355
+ * `body` column — body is ~48KB/row of session markdown that recall
356
+ * never reads, and SELECTing it for the corpus is what wedged the
357
+ * daemon. Resolved sessions carry `body: ""`.
358
+ */
359
+ async getByIds(ids) {
360
+ if (ids.length === 0)
361
+ return [];
362
+ const placeholders = ids.map(() => "?").join(",");
363
+ const rows = this.db
364
+ .prepare(`
365
+ SELECT id, runtime, runtime_session_id, started_at, ended_at, duration_min,
366
+ label, summary, status, transcript_kind, transcript_path
367
+ FROM sessions
368
+ WHERE id IN (${placeholders})
369
+ `)
370
+ .all(...ids);
371
+ if (rows.length === 0)
372
+ return [];
373
+ const foundIds = rows.map((r) => r.id);
374
+ const entitiesByIdMap = this.loadEntities(foundIds);
375
+ const markersByIdMap = this.loadMarkers(foundIds);
376
+ const overlay = loadActionOverlay(this.db);
377
+ return rows.map((r) => this.rowToSession({ ...r, body: null }, entitiesByIdMap, markersByIdMap, overlay));
378
+ }
379
+ async semanticSearch(queryVector, limit) {
380
+ const k = Math.max(1, Math.trunc(limit));
381
+ const blob = Buffer.from(queryVector.buffer, queryVector.byteOffset, queryVector.byteLength);
382
+ // Overfetch chunks so the max-pool grouping has enough unique sessions
383
+ // even when several top chunks come from the same session. Default 4
384
+ // ≈ average chunks per session on the LongMemEval-S benchmark. Env-
385
+ // tunable via NLM_CHUNK_OVERFETCH for per-type ablation against the
386
+ // preference/assistant regressions where displacement is hypothesized.
387
+ const envOverfetch = Number.parseInt(process.env["NLM_CHUNK_OVERFETCH"] ?? "", 10);
388
+ const CHUNK_OVERFETCH = Number.isFinite(envOverfetch) && envOverfetch > 0 ? envOverfetch : 4;
389
+ const chunkK = k * CHUNK_OVERFETCH;
390
+ const rows = this.db
391
+ .prepare(`
392
+ SELECT session_id, distance
393
+ FROM session_embedding_chunks
394
+ WHERE embedding MATCH ?
395
+ AND k = ?
396
+ ORDER BY distance
397
+ `)
398
+ .all(blob, chunkK);
399
+ // Max-pool: keep the smallest distance (highest cosine) per session.
400
+ const best = new Map();
401
+ for (const r of rows) {
402
+ const cur = best.get(r.session_id);
403
+ if (cur === undefined || r.distance < cur) {
404
+ best.set(r.session_id, r.distance);
405
+ }
406
+ }
407
+ return [...best.entries()]
408
+ .map(([sessionId, distance]) => ({ sessionId, distance }))
409
+ .sort((a, b) => a.distance - b.distance)
410
+ .slice(0, k);
411
+ }
412
+ /**
413
+ * Lexical recall via the sessions_fts FTS5 index. BM25 column weights
414
+ * favour label over summary over body. Returns sessions ranked best-first
415
+ * with a positive score (the negated bm25() value — bm25 is more negative
416
+ * for better matches). User input is tokenized and rebuilt into a quoted
417
+ * OR query so FTS5 metacharacters cannot reach the MATCH parser.
418
+ */
419
+ async keywordSearch(query, limit) {
420
+ const matchExpr = toMatchExpression(query);
421
+ if (!matchExpr)
422
+ return [];
423
+ const k = Math.max(1, Math.trunc(limit));
424
+ const rows = this.db
425
+ .prepare(`
426
+ SELECT s.id AS session_id,
427
+ -bm25(sessions_fts, 10.0, 4.0, 1.0) AS score
428
+ FROM sessions_fts
429
+ JOIN sessions s ON s.rowid = sessions_fts.rowid
430
+ WHERE sessions_fts MATCH ?
431
+ ORDER BY score DESC
432
+ LIMIT ?
433
+ `)
434
+ .all(matchExpr, k);
435
+ return rows.map((r) => ({ sessionId: r.session_id, score: r.score }));
436
+ }
437
+ async updateStatus(sessionId, status) {
438
+ if (status === "idle") {
439
+ throw new Error("Cannot persist derived status 'idle' — only active/closed/superseded");
440
+ }
441
+ this.db
442
+ .prepare("UPDATE sessions SET status = ?, updated_at = datetime('now') WHERE id = ?")
443
+ .run(status, sessionId);
444
+ }
445
+ // ── insert helpers used by tests / future ingest path ─────────────────
446
+ insertSessionForTest(session) {
447
+ const stmt = this.db.prepare(`
448
+ INSERT INTO sessions (
449
+ id, runtime, runtime_session_id, started_at, ended_at, duration_min,
450
+ label, summary, body, status, transcript_kind, transcript_path
451
+ ) VALUES (
452
+ @id, @runtime, @runtimeSessionId, @startedAt, @endedAt, @durationMin,
453
+ @label, @summary, @body, @status, @transcriptKind, @transcriptPath
454
+ )
455
+ `);
456
+ const status = session.status === "idle" ? "active" : session.status;
457
+ stmt.run({
458
+ id: session.id,
459
+ runtime: session.runtime,
460
+ runtimeSessionId: session.runtimeSessionId,
461
+ startedAt: session.startedAt,
462
+ endedAt: session.endedAt,
463
+ durationMin: session.durationMin,
464
+ label: session.label,
465
+ summary: session.summary,
466
+ body: session.body,
467
+ status,
468
+ transcriptKind: session.transcriptKind,
469
+ transcriptPath: session.transcriptPath,
470
+ });
471
+ const entStmt = this.db.prepare(`
472
+ INSERT OR IGNORE INTO entities (canonical, type, status)
473
+ VALUES (?, 'candidate', 'active')
474
+ `);
475
+ const linkStmt = this.db.prepare("INSERT OR IGNORE INTO session_entities (session_id, entity_canonical) VALUES (?, ?)");
476
+ for (const e of session.entities) {
477
+ entStmt.run(e);
478
+ linkStmt.run(session.id, e);
479
+ }
480
+ const markerStmt = this.db.prepare("INSERT INTO markers (session_id, kind, text, position) VALUES (?, ?, ?, ?)");
481
+ session.decisions.forEach((d, i) => markerStmt.run(session.id, "decision", d, i));
482
+ session.open.forEach((q, i) => markerStmt.run(session.id, "open", q, i));
483
+ }
484
+ insertEmbeddingForTest(sessionId, vector) {
485
+ this.insertChunkEmbeddingForTest(sessionId, 0, vector);
486
+ }
487
+ insertChunkEmbeddingForTest(sessionId, chunkIdx, vector) {
488
+ this.insertChunkEmbedding(sessionId, chunkIdx, vector);
489
+ }
490
+ // ── internal ──────────────────────────────────────────────────────────
491
+ loadEntities(ids) {
492
+ if (ids.length === 0)
493
+ return new Map();
494
+ const placeholders = ids.map(() => "?").join(",");
495
+ const rows = this.db
496
+ .prepare(`
497
+ SELECT session_id, entity_canonical
498
+ FROM session_entities
499
+ WHERE session_id IN (${placeholders})
500
+ ORDER BY session_id
501
+ `)
502
+ .all(...ids);
503
+ const out = new Map();
504
+ for (const r of rows) {
505
+ const list = out.get(r.session_id);
506
+ if (list)
507
+ list.push(r.entity_canonical);
508
+ else
509
+ out.set(r.session_id, [r.entity_canonical]);
510
+ }
511
+ return out;
512
+ }
513
+ loadMarkers(ids) {
514
+ if (ids.length === 0)
515
+ return new Map();
516
+ const placeholders = ids.map(() => "?").join(",");
517
+ const rows = this.db
518
+ .prepare(`
519
+ SELECT session_id, kind, text
520
+ FROM markers
521
+ WHERE session_id IN (${placeholders})
522
+ ORDER BY session_id, position
523
+ `)
524
+ .all(...ids);
525
+ const out = new Map();
526
+ for (const r of rows) {
527
+ let bucket = out.get(r.session_id);
528
+ if (!bucket) {
529
+ bucket = { decisions: [], open: [] };
530
+ out.set(r.session_id, bucket);
531
+ }
532
+ if (r.kind === "decision")
533
+ bucket.decisions.push(r.text);
534
+ else
535
+ bucket.open.push(r.text);
536
+ }
537
+ return out;
538
+ }
539
+ rowToSession(row, entitiesById, markersById, overlay) {
540
+ const m = markersById.get(row.id);
541
+ const rawDecisions = m?.decisions ?? [];
542
+ const rawOpen = m?.open ?? [];
543
+ const activeOpen = [];
544
+ const promotedDecisions = [];
545
+ for (const text of rawOpen) {
546
+ const id = openQuestionId(row.id, text);
547
+ if (overlay.resolvedOpens.has(id))
548
+ continue;
549
+ const resolution = overlay.promotedOpens.get(id);
550
+ if (resolution !== undefined) {
551
+ promotedDecisions.push(resolution);
552
+ continue;
553
+ }
554
+ activeOpen.push(text);
555
+ }
556
+ return {
557
+ id: row.id,
558
+ runtime: row.runtime,
559
+ runtimeSessionId: row.runtime_session_id ?? "",
560
+ startedAt: row.started_at,
561
+ endedAt: row.ended_at,
562
+ durationMin: row.duration_min,
563
+ label: row.label,
564
+ summary: row.summary,
565
+ status: liveSessionStatus(row.transcript_path, row.status),
566
+ transcriptKind: row.transcript_kind ?? "",
567
+ transcriptPath: row.transcript_path,
568
+ body: row.body ?? "",
569
+ entities: entitiesById.get(row.id) ?? [],
570
+ decisions: [...rawDecisions, ...promotedDecisions],
571
+ open: activeOpen,
572
+ };
573
+ }
574
+ }
575
+ /**
576
+ * Builds a safe FTS5 MATCH expression from raw user input. Each indexable
577
+ * token becomes a double-quoted string literal; literals are OR-joined.
578
+ * Quoting neutralizes FTS5 operators (AND, OR, NEAR, *, parentheses, colon).
579
+ * Returns null when the query has no indexable tokens.
580
+ */
581
+ function toMatchExpression(query) {
582
+ const terms = tokenize(query);
583
+ if (terms.length === 0)
584
+ return null;
585
+ return terms.map((t) => `"${t.replace(/"/g, '""')}"`).join(" OR ");
586
+ }
587
+ //# sourceMappingURL=sqlite-session-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-session-store.js","sourceRoot":"","sources":["../../../src/core/storage/sqlite-session-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,KAAK,SAAS,MAAM,YAAY,CAAC;AAWxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAG7E,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAiEjE,MAAM,OAAO,kBAAkB;IACZ,EAAE,CAAoB;IAEvC,YAAY,IAA+B;QACzC,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QAC/B,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAEhE,IAAI,CAAC,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACxE,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;QACpC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;QAErC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAExB,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,aAAa,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IAED,KAAK;QACH,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC;IAED;;;;;OAKG;IACH,UAAU;QACR,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,0BAA0B,CAAC,CAAC;IAC7C,CAAC;IAED;6EACyE;IACzE,KAAK;QACH,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;IAED,wFAAwF;IACxF,YAAY,CAAC,KAAa;QACxB,OAAO,IAAI,CAAC,EAAE;aACX,OAAO,CACN;;;iBAGS,CACV;aACA,GAAG,CAAC,KAAK,CAAC,CAAC;IAChB,CAAC;IAED,oGAAoG;IACpG,aAAa,CAAC,KAAa;QACzB,OAAO,IAAI,CAAC,EAAE;aACX,OAAO,CACN;;;;iBAIS,CACV;aACA,GAAG,CAAC,KAAK,CAAC,CAAC;IAChB,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,aAAa,CACjB,MAAoB,EACpB,WAA4D,IAAI,EAChE,aAA4B,IAAI,EAChC,WAA8E,IAAI;QAElF,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QACnB,MAAM,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;YAC9B,EAAE,CAAC,OAAO,CAAC;;;;;;;;;;;;;;;;OAgBV,CAAC,CAAC,GAAG,CAAC;gBACL,EAAE,EAAE,MAAM,CAAC,EAAE;gBACb,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;gBACzC,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,MAAM,EAAE,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM;gBAC3D,cAAc,EAAE,MAAM,CAAC,cAAc;gBACrC,cAAc,EAAE,MAAM,CAAC,cAAc;gBACrC,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;gBACzC,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;aAC1C,CAAC,CAAC;YAEH,EAAE,CAAC,OAAO,CAAC,0CAA0C,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACtE,MAAM,UAAU,GAAG,EAAE,CAAC,OAAO,CAC3B,4EAA4E,CAC7E,CAAC;YACF,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;YACvF,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;YAEvF,MAAM,SAAS,GAAG,EAAE,CAAC,OAAO,CAAC;;;;OAI5B,CAAC,CAAC;YACH,MAAM,QAAQ,GAAG,EAAE,CAAC,OAAO,CAAC;;;;OAI3B,CAAC,CAAC;YACH,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CACxB,qFAAqF,CACtF,CAAC;YACF,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;gBAClC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;gBACxB,IAAI,CAAC,IAAI;oBAAE,SAAS;gBACpB,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;gBAC1C,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;gBAC9B,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YAC/B,CAAC;YAED,IAAI,UAAU,EAAE,CAAC;gBACf,EAAE,CAAC,OAAO,CACR;uCAC6B,CAC9B,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;gBAC7B,EAAE,CAAC,OAAO,CACR,sFAAsF,CACvF,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACpB,CAAC;YAED,uEAAuE;YACvE,sEAAsE;YACtE,uEAAuE;YACvE,oEAAoE;YACpE,oCAAoC;YACpC,EAAE;YACF,iEAAiE;YACjE,gEAAgE;YAChE,mEAAmE;YACnE,iEAAiE;YACjE,wEAAwE;YACxE,oEAAoE;YACpE,iDAAiD;YACjD,EAAE;YACF,oEAAoE;YACpE,iEAAiE;YACjE,sEAAsE;YACtE,mEAAmE;YACnE,gEAAgE;YAChE,qEAAqE;YACrE,mDAAmD;YACnD,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACtB,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;YACtE,CAAC;QACH,CAAC,CAAC,CAAC;QACH,GAAG,EAAE,CAAC;QAEN,sEAAsE;QACtE,sEAAsE;QACtE,qEAAqE;QACrE,uEAAuE;QACvE,8BAA8B;QAC9B,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,gBAAgB,CAAC;gBAC9B,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,IAAI,EAAE,MAAM,CAAC,IAAI;aAClB,CAAC,CAAC;YACH,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACpC,KAAK,IAAI,QAAQ,GAAG,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,CAAC;gBAC5D,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAE,CAAC;gBAC/B,IAAI,CAAC,IAAI;oBAAE,SAAS;gBACpB,IAAI,CAAC;oBACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;oBAC1D,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;gBACzD,CAAC;gBAAC,MAAM,CAAC;oBACP,8DAA8D;oBAC9D,2BAA2B;gBAC7B,CAAC;YACH,CAAC;YAED,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACtB,MAAM,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YACtE,CAAC;QACH,CAAC;IACH,CAAC;IAEO,mBAAmB,CAAC,SAAiB;QAC3C,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,GAAG,EAAE;aACZ,OAAO,CACN,6DAA6D,CAC9D;aACA,GAAG,CAAC,SAAS,CAAC,CAAC;QAClB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC9B,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACxC,EAAE,CAAC,OAAO,CACR,2DAA2D,YAAY,GAAG,CAC3E,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;QACd,EAAE,CAAC,OAAO,CAAC,oDAAoD,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAClF,CAAC;IAEO,oBAAoB,CAC1B,SAAiB,EACjB,QAAgB,EAChB,MAAoB;QAEpB,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CACtB,MAAM,CAAC,MAAM,EACb,MAAM,CAAC,UAAU,EACjB,MAAM,CAAC,UAAU,CAClB,CAAC;QACF,2EAA2E;QAC3E,wEAAwE;QACxE,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;QAChC,MAAM,IAAI,GAAG,EAAE;aACZ,OAAO,CACN,0FAA0F,CAC3F;aACA,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QAChC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC7C,EAAE,CAAC,OAAO,CACR,kFAAkF,CACnF,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IACtC,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,qBAAqB,CACzB,SAAiB,EACjB,SAA0B,EAC1B,KAA0B,EAC1B,WAA4D,IAAI;QAEhE,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QACnB,MAAM,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;YAC9B,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QACH,GAAG,EAAE,CAAC;QACN,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACK,eAAe,CACrB,SAAiB,EACjB,SAA0B,EAC1B,KAA0B;QAE1B,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QACnB,EAAE,CAAC,OAAO,CAAC,+CAA+C,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC3E,SAAS,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAE/B,MAAM,iBAAiB,GAAG,EAAE,CAAC,OAAO,CAGlC;;;;;;;;;KASD,CAAC,CAAC;QACH,MAAM,kBAAkB,GAAG,EAAE,CAAC,OAAO,CACnC,iDAAiD,CAClD,CAAC;QACF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;YAC3E,IAAI,KAAK;gBAAE,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,UAAU,CACtB,SAA0B,EAC1B,KAA0B,EAC1B,QAAkD;QAElD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC;YAC1E,IAAI,CAAC,QAAQ;gBAAE,SAAS;YACxB,IAAI,CAAC;gBACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;gBAC9D,SAAS,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;YAC7C,CAAC;YAAC,MAAM,CAAC;gBACP,oEAAoE;gBACpE,oEAAoE;gBACpE,4BAA4B;YAC9B,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAsB;QAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE;aACjB,OAAO,CAAiB;;;;;OAKxB,CAAC;aACD,GAAG,EAAE,CAAC;QAET,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAEjC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAClC,MAAM,eAAe,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QAC/C,MAAM,cAAc,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAE3C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,eAAe,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC;QAEjG,IAAI,CAAC,MAAM;YAAE,OAAO,QAAQ,CAAC;QAC7B,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;YAC3B,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;gBACvE,OAAO,KAAK,CAAC;YACf,CAAC;YACD,IAAI,MAAM,CAAC,YAAY,KAAK,IAAI,IAAI,CAAC,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC;YAC3E,IAAI,MAAM,CAAC,gBAAgB,KAAK,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC;YAC1E,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,SAAiB;QAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE;aAChB,OAAO,CAAuB;;;;;OAK9B,CAAC;aACD,GAAG,CAAC,SAAS,CAAC,CAAC;QAElB,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5D,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,QAAQ,CAAC,GAA0B;QACvC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAChC,MAAM,YAAY,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE;aACjB,OAAO,CAAqC;;;;uBAI5B,YAAY;OAC5B,CAAC;aACD,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;QAEf,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACvC,MAAM,eAAe,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QACpD,MAAM,cAAc,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAClD,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACpB,IAAI,CAAC,YAAY,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,eAAe,EAAE,cAAc,EAAE,OAAO,CAAC,CAClF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,cAAc,CAClB,WAAyB,EACzB,KAAa;QAEb,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CACtB,WAAW,CAAC,MAAM,EAClB,WAAW,CAAC,UAAU,EACtB,WAAW,CAAC,UAAU,CACvB,CAAC;QACF,uEAAuE;QACvE,qEAAqE;QACrE,oEAAoE;QACpE,oEAAoE;QACpE,uEAAuE;QACvE,MAAM,YAAY,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QACnF,MAAM,eAAe,GAAG,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7F,MAAM,MAAM,GAAG,CAAC,GAAG,eAAe,CAAC;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE;aACjB,OAAO,CAAgC;;;;;;OAMvC,CAAC;aACD,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAErB,qEAAqE;QACrE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;QACvC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;YACnC,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,GAAG,GAAG,EAAE,CAAC;gBAC1C,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;QACD,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;aACvB,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;aACzD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC;aACvC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACjB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,aAAa,CACjB,KAAa,EACb,KAAa;QAEb,MAAM,SAAS,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,CAAC,SAAS;YAAE,OAAO,EAAE,CAAC;QAC1B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE;aACjB,OAAO,CAA+B;;;;;;;;OAQtC,CAAC;aACD,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACxE,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,SAAiB,EAAE,MAAqB;QACzD,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;QAC1F,CAAC;QACD,IAAI,CAAC,EAAE;aACJ,OAAO,CACN,2EAA2E,CAC5E;aACA,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC5B,CAAC;IAED,yEAAyE;IACzE,oBAAoB,CAAC,OAAgB;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;;;;KAQ5B,CAAC,CAAC;QACH,MAAM,MAAM,GAAkB,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;QACpF,IAAI,CAAC,GAAG,CAAC;YACP,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;YAC1C,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM;YACN,cAAc,EAAE,OAAO,CAAC,cAAc;YACtC,cAAc,EAAE,OAAO,CAAC,cAAc;SACvC,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;KAG/B,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAC9B,qFAAqF,CACtF,CAAC;QACF,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACf,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QAC9B,CAAC;QAED,MAAM,UAAU,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAChC,4EAA4E,CAC7E,CAAC;QACF,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAClF,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3E,CAAC;IAED,sBAAsB,CAAC,SAAiB,EAAE,MAAoB;QAC5D,IAAI,CAAC,2BAA2B,CAAC,SAAS,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;IACzD,CAAC;IAED,2BAA2B,CACzB,SAAiB,EACjB,QAAgB,EAChB,MAAoB;QAEpB,IAAI,CAAC,oBAAoB,CAAC,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACzD,CAAC;IAED,yEAAyE;IACjE,YAAY,CAAC,GAA0B;QAC7C,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,GAAG,EAAE,CAAC;QACvC,MAAM,YAAY,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE;aACjB,OAAO,CAAsB;;;+BAGL,YAAY;;OAEpC,CAAC;aACD,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;QAEf,MAAM,GAAG,GAAG,IAAI,GAAG,EAAoB,CAAC;QACxC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;YACnC,IAAI,IAAI;gBAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC;;gBACnC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAEO,WAAW,CACjB,GAA0B;QAE1B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,GAAG,EAAE,CAAC;QACvC,MAAM,YAAY,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE;aACjB,OAAO,CAAsB;;;+BAGL,YAAY;;OAEpC,CAAC;aACD,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;QAEf,MAAM,GAAG,GAAG,IAAI,GAAG,EAAmD,CAAC;QACvE,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;YACnC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;gBACrC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;YAChC,CAAC;YACD,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU;gBAAE,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;;gBACpD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAEO,YAAY,CAClB,GAAe,EACf,YAAmC,EACnC,WAAiE,EACjE,OAAsB;QAEtB,MAAM,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClC,MAAM,YAAY,GAAG,CAAC,EAAE,SAAS,IAAI,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;QAC9B,MAAM,UAAU,GAAa,EAAE,CAAC;QAChC,MAAM,iBAAiB,GAAa,EAAE,CAAC;QACvC,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YAC3B,MAAM,EAAE,GAAG,cAAc,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YACxC,IAAI,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,SAAS;YAC5C,MAAM,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACjD,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC7B,iBAAiB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;gBACnC,SAAS;YACX,CAAC;YACD,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;QACD,OAAO;YACL,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,gBAAgB,EAAE,GAAG,CAAC,kBAAkB,IAAI,EAAE;YAC9C,SAAS,EAAE,GAAG,CAAC,UAAU;YACzB,OAAO,EAAE,GAAG,CAAC,QAAQ;YACrB,WAAW,EAAE,GAAG,CAAC,YAAY;YAC7B,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,MAAM,EAAE,iBAAiB,CAAC,GAAG,CAAC,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC;YAC1D,cAAc,EAAE,GAAG,CAAC,eAAe,IAAI,EAAE;YACzC,cAAc,EAAE,GAAG,CAAC,eAAe;YACnC,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE;YACpB,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE;YACxC,SAAS,EAAE,CAAC,GAAG,YAAY,EAAE,GAAG,iBAAiB,CAAC;YAClD,IAAI,EAAE,UAAU;SACjB,CAAC;IACJ,CAAC;CACF;AAED;;;;;GAKG;AACH,SAAS,iBAAiB,CAAC,KAAa;IACtC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACrE,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Claude Code PreCompact hook entrypoint for NLM.
3
+ *
4
+ * Fires before Claude Code compacts a long transcript. At compaction time the
5
+ * in-flight surfaced-IDs memo and any pending citation state would be lost.
6
+ * This hook POSTs a compaction event to the daemon so it can do a final
7
+ * synchronous citation scan of the transcript before the shape is lost.
8
+ *
9
+ * Daemon endpoint: POST localhost:3940/api/hook/pre-compact
10
+ * This endpoint does NOT exist yet in the daemon — the hook ships fail-soft
11
+ * (swallows errors) so it won't block compaction when the endpoint is absent.
12
+ * The daemon-side handler is a follow-up task.
13
+ *
14
+ * Payload: { conversation_id, transcript_path, surfaced_set, ts }
15
+ *
16
+ * Fail-open by design: any error yields a clean exit with no output.
17
+ */
18
+ export interface PreCompactInput {
19
+ readonly conversationId: string;
20
+ readonly transcriptPath: string;
21
+ }
22
+ export interface PreCompactResult {
23
+ readonly conversationId: string;
24
+ readonly posted: boolean;
25
+ }
26
+ export declare function runPreCompact(input: PreCompactInput, portValue?: string): Promise<PreCompactResult>;