nlm-memory 0.5.0 → 0.5.2

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 (257) hide show
  1. package/README.md +89 -34
  2. package/dist/cli/digest.d.ts +20 -0
  3. package/dist/cli/digest.js +142 -0
  4. package/dist/cli/digest.js.map +1 -0
  5. package/dist/cli/nlm.d.ts +1 -0
  6. package/dist/cli/nlm.js +25 -1
  7. package/dist/cli/nlm.js.map +1 -1
  8. package/dist/core/digest/compose.d.ts +38 -0
  9. package/dist/core/digest/compose.js +93 -0
  10. package/dist/core/digest/compose.js.map +1 -0
  11. package/dist/core/digest/hook-liveness.d.ts +32 -0
  12. package/dist/core/digest/hook-liveness.js +54 -0
  13. package/dist/core/digest/hook-liveness.js.map +1 -0
  14. package/dist/http/app.js +2 -1
  15. package/dist/http/app.js.map +1 -1
  16. package/dist/mcp/server.js +20 -1
  17. package/dist/mcp/server.js.map +1 -1
  18. package/dist/ui/assets/{index-C8cpwbYJ.css → index-Beo8psd-.css} +1 -1
  19. package/dist/ui/assets/{index-CB50QnL-.js → index-CSPTTeeM.js} +8 -8
  20. package/dist/ui/index.html +2 -2
  21. package/package.json +26 -1
  22. package/.agents/plugins/marketplace.json +0 -20
  23. package/.github/workflows/ci.yml +0 -30
  24. package/docs/methodology/re-derivation-rate.md +0 -112
  25. package/docs/methodology/useful-hit-rate.md +0 -79
  26. package/docs/plans/2026-05-20-fts5-lexical-recall.md +0 -1088
  27. package/docs/plans/2026-05-20-recall-daemon-wedge-fix.md +0 -662
  28. package/docs/plans/2026-05-20-recall-hook-design.md +0 -131
  29. package/docs/plans/2026-05-20-recall-hook-implementation.md +0 -1222
  30. package/docs/plans/desktop-product.md +0 -69
  31. package/docs/plans/factstore-design.md +0 -236
  32. package/logs/CHANGELOG/CHANGELOG-2026.md +0 -1575
  33. package/logs/CHANGELOG/CHANGELOG.md +0 -209
  34. package/migrations/000_initial_schema.sql +0 -174
  35. package/migrations/001_entity_type_rename.sql +0 -17
  36. package/migrations/002_adapter_state_extend.sql +0 -12
  37. package/migrations/003_session_embeddings.sql +0 -11
  38. package/migrations/004_facts.sql +0 -46
  39. package/migrations/005_sources.sql +0 -31
  40. package/migrations/006_providers.sql +0 -33
  41. package/migrations/007_source_tokens.sql +0 -17
  42. package/migrations/008_fts_rebuild.sql +0 -9
  43. package/migrations/009_session_embedding_chunks.sql +0 -46
  44. package/migrations/010_sources_opencode.sql +0 -30
  45. package/migrations/011_sources_hermes_agent.sql +0 -30
  46. package/migrations/012_sources_aider.sql +0 -30
  47. package/migrations/013_adapter_state_failure_count.sql +0 -12
  48. package/migrations/014_sources_cursor.sql +0 -30
  49. package/migrations/015_sources_windsurf.sql +0 -30
  50. package/plugin-hermes-agent/README.md +0 -49
  51. package/plugin-hermes-agent/__init__.py +0 -75
  52. package/plugin-hermes-agent/plugin.yaml +0 -15
  53. package/scripts/backfill-citations.mjs +0 -0
  54. package/scripts/build-codex-plugin.mjs +0 -61
  55. package/scripts/deepseek-probe.mjs +0 -67
  56. package/scripts/extract-triples.mjs +0 -207
  57. package/scripts/longmemeval/embedding-cache.ts +0 -77
  58. package/scripts/longmemeval/fetch-dataset.sh +0 -25
  59. package/scripts/longmemeval/run-harness.ts +0 -315
  60. package/scripts/longmemeval/scorer.ts +0 -99
  61. package/scripts/longmemeval/tsconfig.json +0 -9
  62. package/scripts/longmemeval/types.ts +0 -35
  63. package/scripts/nlm-daily-digest.py +0 -239
  64. package/scripts/nlm-daily-digest.sh +0 -28
  65. package/src/cli/classify-parity.ts +0 -257
  66. package/src/cli/launchctl-helpers.ts +0 -49
  67. package/src/cli/nlm.ts +0 -1078
  68. package/src/core/actions/actions-log.ts +0 -118
  69. package/src/core/actions/overlay.ts +0 -117
  70. package/src/core/adapters/aider.ts +0 -205
  71. package/src/core/adapters/claude-code.ts +0 -293
  72. package/src/core/adapters/common.ts +0 -54
  73. package/src/core/adapters/cursor.ts +0 -486
  74. package/src/core/adapters/from-source.ts +0 -67
  75. package/src/core/adapters/hermes-agent.ts +0 -240
  76. package/src/core/adapters/hermes.ts +0 -277
  77. package/src/core/adapters/jsonl-generic.ts +0 -208
  78. package/src/core/adapters/opencode.ts +0 -281
  79. package/src/core/adapters/pi.ts +0 -264
  80. package/src/core/adapters/windsurf.ts +0 -386
  81. package/src/core/classifier/prompt.ts +0 -200
  82. package/src/core/dataset/build-dataset.ts +0 -463
  83. package/src/core/embedding/chunk-body.ts +0 -76
  84. package/src/core/embedding/embed-backfill.ts +0 -210
  85. package/src/core/embedding/embed-normalize.ts +0 -135
  86. package/src/core/facts/backfill-facts.ts +0 -254
  87. package/src/core/facts/extract-facts.ts +0 -50
  88. package/src/core/hook/citation-detect.ts +0 -124
  89. package/src/core/hook/cite-memo.ts +0 -68
  90. package/src/core/hook/claude-settings.ts +0 -187
  91. package/src/core/hook/gate.ts +0 -25
  92. package/src/core/hook/hook-log.ts +0 -41
  93. package/src/core/hook/memo-sweep.ts +0 -164
  94. package/src/core/hook/memo.ts +0 -67
  95. package/src/core/hook/pointer-block.ts +0 -26
  96. package/src/core/hook/select.ts +0 -32
  97. package/src/core/hook/transcript.ts +0 -121
  98. package/src/core/ingest/ingest-session.ts +0 -111
  99. package/src/core/providers/provider-models.ts +0 -100
  100. package/src/core/providers/provider-registry.ts +0 -196
  101. package/src/core/recall/citation-log.ts +0 -108
  102. package/src/core/recall/filter.ts +0 -27
  103. package/src/core/recall/index.ts +0 -6
  104. package/src/core/recall/match-fields.ts +0 -40
  105. package/src/core/recall/query-log.ts +0 -149
  106. package/src/core/recall/query-shape.ts +0 -66
  107. package/src/core/recall/recall-service.ts +0 -320
  108. package/src/core/recall/recent-log.ts +0 -59
  109. package/src/core/recall/tokenize.ts +0 -18
  110. package/src/core/recall/useful-scan.ts +0 -336
  111. package/src/core/recall-facts/fact-query-log.ts +0 -150
  112. package/src/core/recall-facts/fact-recall-service.ts +0 -327
  113. package/src/core/scheduler/scan-once.ts +0 -142
  114. package/src/core/scheduler/scheduler.ts +0 -225
  115. package/src/core/sources/source-registry.ts +0 -278
  116. package/src/core/storage/db-restore.ts +0 -133
  117. package/src/core/storage/live-status.ts +0 -45
  118. package/src/core/storage/migrate.ts +0 -72
  119. package/src/core/storage/sqlite-fact-store.ts +0 -304
  120. package/src/core/storage/sqlite-session-store.ts +0 -810
  121. package/src/hook/hook-auth.ts +0 -18
  122. package/src/hook/prompt-recall-hook.ts +0 -180
  123. package/src/hook/session-end-hook.ts +0 -81
  124. package/src/hook/session-start-hook.ts +0 -168
  125. package/src/hook/stop-hook.ts +0 -239
  126. package/src/http/app.ts +0 -1215
  127. package/src/install/claude-code.ts +0 -128
  128. package/src/install/codex.ts +0 -367
  129. package/src/install/cursor.ts +0 -68
  130. package/src/install/hermes-agent.ts +0 -76
  131. package/src/install/hermes.ts +0 -78
  132. package/src/install/nlm-dir-perms.ts +0 -55
  133. package/src/install/ollama.ts +0 -284
  134. package/src/install/setup.ts +0 -489
  135. package/src/install/windsurf.ts +0 -68
  136. package/src/llm/classifier-box.ts +0 -64
  137. package/src/llm/deepseek-client.ts +0 -150
  138. package/src/llm/env-autoload.ts +0 -55
  139. package/src/llm/ollama-client.ts +0 -189
  140. package/src/mcp/server.ts +0 -534
  141. package/src/ports/fact-store.ts +0 -102
  142. package/src/ports/llm-client.ts +0 -52
  143. package/src/ports/logger.ts +0 -16
  144. package/src/ports/session-store.ts +0 -45
  145. package/src/ports/transcript-adapter.ts +0 -55
  146. package/src/shared/types.ts +0 -149
  147. package/src/ui/App.tsx +0 -58
  148. package/src/ui/components/PromoteOpenButton.tsx +0 -65
  149. package/src/ui/components/SessionDrawer.tsx +0 -199
  150. package/src/ui/components/SideNav.tsx +0 -162
  151. package/src/ui/components/Skeleton.tsx +0 -107
  152. package/src/ui/index.html +0 -13
  153. package/src/ui/lib/actions.ts +0 -30
  154. package/src/ui/lib/api.ts +0 -92
  155. package/src/ui/lib/dataset.ts +0 -141
  156. package/src/ui/lib/registries.ts +0 -155
  157. package/src/ui/lib/view-settings.ts +0 -41
  158. package/src/ui/main.tsx +0 -15
  159. package/src/ui/pages/Live.tsx +0 -229
  160. package/src/ui/pages/Pulse.tsx +0 -415
  161. package/src/ui/pages/Recall.tsx +0 -190
  162. package/src/ui/pages/River.tsx +0 -354
  163. package/src/ui/pages/Search.tsx +0 -386
  164. package/src/ui/pages/Stub.tsx +0 -9
  165. package/src/ui/pages/Thread.tsx +0 -473
  166. package/src/ui/pages/settings/Classifier.tsx +0 -227
  167. package/src/ui/pages/settings/Data.tsx +0 -190
  168. package/src/ui/pages/settings/Index.tsx +0 -65
  169. package/src/ui/pages/settings/Labels.tsx +0 -224
  170. package/src/ui/pages/settings/Providers.tsx +0 -305
  171. package/src/ui/pages/settings/SettingsSubnav.tsx +0 -28
  172. package/src/ui/pages/settings/Sources.tsx +0 -326
  173. package/src/ui/pages/settings/Views.tsx +0 -96
  174. package/src/ui/styles.css +0 -1890
  175. package/src/ui/tsconfig.json +0 -21
  176. package/src/ui/vite.config.ts +0 -19
  177. package/tests/fixtures/claude_code/short_session.jsonl +0 -2
  178. package/tests/fixtures/claude_code/standard_iso.jsonl +0 -4
  179. package/tests/fixtures/claude_code/tool_heavy.jsonl +0 -8
  180. package/tests/fixtures/claude_code/with_subagent.jsonl +0 -7
  181. package/tests/fixtures/facts.ts +0 -17
  182. package/tests/fixtures/golden-corpus.ts +0 -85
  183. package/tests/fixtures/hermes/paired_request_dump.json +0 -24
  184. package/tests/fixtures/hermes/paired_session.json +0 -23
  185. package/tests/fixtures/hermes/request_dump.json +0 -28
  186. package/tests/fixtures/hermes/session_iso.json +0 -38
  187. package/tests/fixtures/hermes/session_unix.json +0 -38
  188. package/tests/fixtures/hermes/system_only.json +0 -18
  189. package/tests/fixtures/pi/error-connection-abort.jsonl +0 -8
  190. package/tests/fixtures/pi/short-successful.jsonl +0 -5
  191. package/tests/fixtures/pi/with-custom-message.jsonl +0 -6
  192. package/tests/fixtures/sessions.ts +0 -22
  193. package/tests/integration/backfill-facts.test.ts +0 -362
  194. package/tests/integration/citation-explicit.test.ts +0 -111
  195. package/tests/integration/cite-event.test.ts +0 -169
  196. package/tests/integration/cite-memo.test.ts +0 -87
  197. package/tests/integration/db-restore.test.ts +0 -153
  198. package/tests/integration/embed-backfill.test.ts +0 -176
  199. package/tests/integration/fact-supersedence.test.ts +0 -313
  200. package/tests/integration/fts-index.test.ts +0 -60
  201. package/tests/integration/getbyids-sqlite.test.ts +0 -100
  202. package/tests/integration/hermes-agent-hooks.test.ts +0 -248
  203. package/tests/integration/hook-claude-settings.test.ts +0 -218
  204. package/tests/integration/hook-log.test.ts +0 -54
  205. package/tests/integration/hook-memo.test.ts +0 -68
  206. package/tests/integration/hook-pre-compact.test.ts +0 -105
  207. package/tests/integration/hook-subagent-start.test.ts +0 -102
  208. package/tests/integration/http.test.ts +0 -401
  209. package/tests/integration/keyword-search-fts.test.ts +0 -66
  210. package/tests/integration/mcp-recall-logging.test.ts +0 -88
  211. package/tests/integration/mcp.test.ts +0 -260
  212. package/tests/integration/memo-sweep.test.ts +0 -91
  213. package/tests/integration/prompt-recall-hook.test.ts +0 -88
  214. package/tests/integration/provider-registry.test.ts +0 -107
  215. package/tests/integration/recall-golden.test.ts +0 -59
  216. package/tests/integration/recall-sqlite.test.ts +0 -169
  217. package/tests/integration/scheduler.test.ts +0 -391
  218. package/tests/integration/session-end-hook.test.ts +0 -48
  219. package/tests/integration/session-start-hook.test.ts +0 -126
  220. package/tests/integration/source-registry.test.ts +0 -122
  221. package/tests/integration/sqlite-fact-store.test.ts +0 -346
  222. package/tests/integration/stop-hook.test.ts +0 -560
  223. package/tests/integration/wal-checkpoint.test.ts +0 -49
  224. package/tests/unit/cli/launchctl-helpers.test.ts +0 -60
  225. package/tests/unit/core/adapters/aider.test.ts +0 -230
  226. package/tests/unit/core/adapters/claude-code.test.ts +0 -118
  227. package/tests/unit/core/adapters/cursor.test.ts +0 -485
  228. package/tests/unit/core/adapters/hermes-agent.test.ts +0 -329
  229. package/tests/unit/core/adapters/hermes.test.ts +0 -81
  230. package/tests/unit/core/adapters/jsonl-generic.test.ts +0 -142
  231. package/tests/unit/core/adapters/opencode.test.ts +0 -354
  232. package/tests/unit/core/adapters/pi.test.ts +0 -110
  233. package/tests/unit/core/adapters/windsurf.test.ts +0 -416
  234. package/tests/unit/core/classifier/prompt.test.ts +0 -126
  235. package/tests/unit/core/embedding/chunk-body.test.ts +0 -100
  236. package/tests/unit/core/facts/extract-facts.test.ts +0 -117
  237. package/tests/unit/core/filter.test.ts +0 -40
  238. package/tests/unit/core/hook/citation-detect-cite-session.test.ts +0 -96
  239. package/tests/unit/core/hook/citation-detect.test.ts +0 -124
  240. package/tests/unit/core/hook/gate.test.ts +0 -29
  241. package/tests/unit/core/hook/pointer-block.test.ts +0 -22
  242. package/tests/unit/core/hook/select.test.ts +0 -66
  243. package/tests/unit/core/match-fields.test.ts +0 -39
  244. package/tests/unit/core/mcp-cite-session.test.ts +0 -51
  245. package/tests/unit/core/providers/provider-models.test.ts +0 -101
  246. package/tests/unit/core/query-shape.test.ts +0 -92
  247. package/tests/unit/core/recall-facts/fact-recall-service.test.ts +0 -258
  248. package/tests/unit/core/recall-service.test.ts +0 -200
  249. package/tests/unit/core/storage/live-status.test.ts +0 -54
  250. package/tests/unit/core/tokenize.test.ts +0 -32
  251. package/tests/unit/core/useful-scan.test.ts +0 -537
  252. package/tests/unit/llm/embed.test.ts +0 -93
  253. package/tests/unit/llm/ollama-client.test.ts +0 -124
  254. package/tests/unit/scripts/longmemeval-scorer.test.ts +0 -114
  255. package/tsconfig.json +0 -31
  256. package/tsconfig.test.json +0 -11
  257. package/vitest.config.ts +0 -22
@@ -1,278 +0,0 @@
1
- /**
2
- * SourceRegistry — CRUD over the `sources` table.
3
- *
4
- * A "source" is any transcript origin the daemon scans (Claude Code's
5
- * projects dir, Hermes's sessions dir, pi.dev, a user-defined JSONL
6
- * directory, or a webhook).
7
- *
8
- * The three legacy adapters (claude-code, hermes, pi) seed as preset rows
9
- * pointing at fixed `path_or_url` values. The generic JSONL adapter and
10
- * webhook ingest piggy-back on this same table — the scheduler chooses
11
- * which adapter to dispatch by reading `kind`.
12
- *
13
- * See docs/plans/desktop-product.md (Phase 0).
14
- */
15
-
16
- import { randomBytes } from "node:crypto";
17
- import { existsSync } from "node:fs";
18
- import { homedir } from "node:os";
19
- import { join } from "node:path";
20
- import type Database from "better-sqlite3";
21
- import { defaultHistoryFile as defaultAiderHistoryFile } from "../adapters/aider.js";
22
- import { defaultDbPath as defaultCursorDbPath } from "../adapters/cursor.js";
23
- import { defaultDbPath as defaultHermesAgentDbPath } from "../adapters/hermes-agent.js";
24
- import { defaultDbPath as defaultOpenCodeDbPath } from "../adapters/opencode.js";
25
- import { defaultUserDir as defaultWindsurfUserDir } from "../adapters/windsurf.js";
26
-
27
- export type SourceKind = "claude-code" | "hermes" | "hermes-agent" | "aider" | "cursor" | "windsurf" | "opencode" | "pi" | "jsonl-generic" | "webhook";
28
-
29
- export interface SourceRow {
30
- readonly id: number;
31
- readonly kind: SourceKind;
32
- readonly name: string;
33
- readonly pathOrUrl: string | null;
34
- readonly runtimeLabel: string;
35
- readonly parseConfig: Record<string, unknown>;
36
- readonly enabled: boolean;
37
- /** Only populated on the response from `insert()` for webhook sources.
38
- * Always `null` from `list()` / `get()`. Use `getToken()` inside the daemon. */
39
- readonly token: string | null;
40
- readonly hasToken: boolean;
41
- readonly createdAt: string;
42
- readonly updatedAt: string;
43
- }
44
-
45
- export interface SourceInsert {
46
- readonly kind: SourceKind;
47
- readonly name: string;
48
- readonly pathOrUrl?: string | null;
49
- readonly runtimeLabel: string;
50
- readonly parseConfig?: Record<string, unknown>;
51
- readonly enabled?: boolean;
52
- }
53
-
54
- export interface SourceUpdate {
55
- readonly name?: string;
56
- readonly pathOrUrl?: string | null;
57
- readonly runtimeLabel?: string;
58
- readonly parseConfig?: Record<string, unknown>;
59
- readonly enabled?: boolean;
60
- }
61
-
62
- interface SourceDbRow {
63
- id: number;
64
- kind: string;
65
- name: string;
66
- path_or_url: string | null;
67
- runtime_label: string;
68
- parse_config: string;
69
- enabled: number;
70
- token: string | null;
71
- created_at: string;
72
- updated_at: string;
73
- }
74
-
75
- function rowFromDb(r: SourceDbRow, revealedToken: string | null = null): SourceRow {
76
- let parsed: Record<string, unknown> = {};
77
- try {
78
- parsed = r.parse_config ? (JSON.parse(r.parse_config) as Record<string, unknown>) : {};
79
- } catch {
80
- parsed = {};
81
- }
82
- return {
83
- id: r.id,
84
- kind: r.kind as SourceKind,
85
- name: r.name,
86
- pathOrUrl: r.path_or_url,
87
- runtimeLabel: r.runtime_label,
88
- parseConfig: parsed,
89
- enabled: r.enabled === 1,
90
- token: revealedToken,
91
- hasToken: r.token !== null && r.token.length > 0,
92
- createdAt: r.created_at,
93
- updatedAt: r.updated_at,
94
- };
95
- }
96
-
97
- function mintToken(): string {
98
- return `nlm_${randomBytes(24).toString("hex")}`;
99
- }
100
-
101
- export class SourceRegistry {
102
- constructor(private readonly db: Database.Database) {}
103
-
104
- list(): SourceRow[] {
105
- const rows = this.db.prepare<[], SourceDbRow>(
106
- `SELECT * FROM sources ORDER BY id ASC`,
107
- ).all();
108
- return rows.map((r) => rowFromDb(r));
109
- }
110
-
111
- get(id: number): SourceRow | null {
112
- const row = this.db.prepare<[number], SourceDbRow>(
113
- `SELECT * FROM sources WHERE id = ?`,
114
- ).get(id);
115
- return row ? rowFromDb(row) : null;
116
- }
117
-
118
- getByName(name: string): SourceRow | null {
119
- const row = this.db.prepare<[string], SourceDbRow>(
120
- `SELECT * FROM sources WHERE name = ?`,
121
- ).get(name);
122
- return row ? rowFromDb(row) : null;
123
- }
124
-
125
- insert(input: SourceInsert): SourceRow {
126
- const token = input.kind === "webhook" ? mintToken() : null;
127
- const stmt = this.db.prepare(`
128
- INSERT INTO sources (kind, name, path_or_url, runtime_label, parse_config, enabled, token)
129
- VALUES (@kind, @name, @path_or_url, @runtime_label, @parse_config, @enabled, @token)
130
- `);
131
- const result = stmt.run({
132
- kind: input.kind,
133
- name: input.name,
134
- path_or_url: input.pathOrUrl ?? null,
135
- runtime_label: input.runtimeLabel,
136
- parse_config: JSON.stringify(input.parseConfig ?? {}),
137
- enabled: input.enabled === false ? 0 : 1,
138
- token,
139
- });
140
- const id = Number(result.lastInsertRowid);
141
- const dbRow = this.db.prepare<[number], SourceDbRow>(
142
- `SELECT * FROM sources WHERE id = ?`,
143
- ).get(id);
144
- if (!dbRow) throw new Error(`SourceRegistry.insert: row ${id} not found after insert`);
145
- // Reveal the token on the insert response only — this is the user's
146
- // one chance to copy it. Subsequent list/get redact.
147
- return rowFromDb(dbRow, token);
148
- }
149
-
150
- /** Daemon-internal: resolve a bearer token to its owning source. */
151
- findByToken(token: string): SourceRow | null {
152
- if (!token) return null;
153
- const row = this.db.prepare<[string], SourceDbRow>(
154
- `SELECT * FROM sources WHERE token = ?`,
155
- ).get(token);
156
- return row ? rowFromDb(row) : null;
157
- }
158
-
159
- /** Daemon-internal: returns the raw token. Never echo to HTTP responses. */
160
- getToken(id: number): string | null {
161
- const row = this.db.prepare<[number], SourceDbRow>(
162
- `SELECT token FROM sources WHERE id = ?`,
163
- ).get(id);
164
- return row?.token ?? null;
165
- }
166
-
167
- /** Mint a fresh token, invalidating any previous one. */
168
- regenerateToken(id: number): string | null {
169
- const current = this.get(id);
170
- if (!current || current.kind !== "webhook") return null;
171
- const token = mintToken();
172
- this.db.prepare(`UPDATE sources SET token = ?, updated_at = datetime('now') WHERE id = ?`)
173
- .run(token, id);
174
- return token;
175
- }
176
-
177
- update(id: number, patch: SourceUpdate): SourceRow | null {
178
- const fields: string[] = [];
179
- const params: Record<string, unknown> = { id };
180
- if (patch.name !== undefined) { fields.push("name = @name"); params["name"] = patch.name; }
181
- if (patch.pathOrUrl !== undefined) { fields.push("path_or_url = @path"); params["path"] = patch.pathOrUrl; }
182
- if (patch.runtimeLabel !== undefined) { fields.push("runtime_label = @rt"); params["rt"] = patch.runtimeLabel; }
183
- if (patch.parseConfig !== undefined) { fields.push("parse_config = @cfg"); params["cfg"] = JSON.stringify(patch.parseConfig); }
184
- if (patch.enabled !== undefined) { fields.push("enabled = @en"); params["en"] = patch.enabled ? 1 : 0; }
185
- if (fields.length === 0) return this.get(id);
186
- fields.push("updated_at = datetime('now')");
187
- this.db.prepare(`UPDATE sources SET ${fields.join(", ")} WHERE id = @id`).run(params);
188
- return this.get(id);
189
- }
190
-
191
- delete(id: number): boolean {
192
- const result = this.db.prepare(`DELETE FROM sources WHERE id = ?`).run(id);
193
- return result.changes > 0;
194
- }
195
-
196
- /**
197
- * Seed the three legacy adapter presets on first boot of an empty
198
- * registry. Subsequent boots are no-ops. Respects per-runtime env
199
- * overrides so existing installs don't lose their custom paths.
200
- */
201
- seedDefaults(): void {
202
- const count = this.db.prepare<[], { c: number }>(`SELECT COUNT(*) AS c FROM sources`).get();
203
- if ((count?.c ?? 0) > 0) return;
204
-
205
- const claudePath = process.env["NLM_CLAUDE_PROJECTS_PATH"]
206
- ?? join(homedir(), ".claude", "projects");
207
- const hermesPath = process.env["NLM_HERMES_SESSIONS_PATH"]
208
- ?? join(homedir(), ".hermes", "sessions");
209
- const piPath = process.env["PI_SESSIONS_PATH"]
210
- ?? join(homedir(), ".pi", "agent", "sessions");
211
-
212
- const openCodeDbPath = defaultOpenCodeDbPath();
213
- const hermesAgentDbPath = defaultHermesAgentDbPath();
214
- const aiderHistoryFile = defaultAiderHistoryFile();
215
- const cursorDbPath = defaultCursorDbPath();
216
- const windsurfUserDir = defaultWindsurfUserDir();
217
-
218
- const presets: SourceInsert[] = [
219
- {
220
- kind: "claude-code",
221
- name: "Claude Code",
222
- pathOrUrl: claudePath,
223
- runtimeLabel: "claude-code/1.0",
224
- enabled: existsSync(claudePath),
225
- },
226
- {
227
- kind: "hermes",
228
- name: "Hermes",
229
- pathOrUrl: hermesPath,
230
- runtimeLabel: "hermes/1.0",
231
- enabled: existsSync(hermesPath),
232
- },
233
- {
234
- kind: "hermes-agent",
235
- name: "Hermes Agent",
236
- pathOrUrl: hermesAgentDbPath,
237
- runtimeLabel: "hermes-agent/1.0",
238
- enabled: existsSync(hermesAgentDbPath),
239
- },
240
- {
241
- kind: "aider",
242
- name: "Aider",
243
- pathOrUrl: aiderHistoryFile,
244
- runtimeLabel: "aider/1.0",
245
- enabled: existsSync(aiderHistoryFile),
246
- },
247
- {
248
- kind: "cursor",
249
- name: "Cursor",
250
- pathOrUrl: cursorDbPath,
251
- runtimeLabel: "cursor/1.0",
252
- enabled: existsSync(cursorDbPath),
253
- },
254
- {
255
- kind: "windsurf",
256
- name: "Windsurf",
257
- pathOrUrl: windsurfUserDir,
258
- runtimeLabel: "windsurf/1.0",
259
- enabled: existsSync(windsurfUserDir),
260
- },
261
- {
262
- kind: "opencode",
263
- name: "OpenCode",
264
- pathOrUrl: openCodeDbPath,
265
- runtimeLabel: "opencode/1.0",
266
- enabled: existsSync(openCodeDbPath),
267
- },
268
- {
269
- kind: "pi",
270
- name: "pi.dev",
271
- pathOrUrl: piPath,
272
- runtimeLabel: "pi/1.0",
273
- enabled: existsSync(piPath),
274
- },
275
- ];
276
- for (const p of presets) this.insert(p);
277
- }
278
- }
@@ -1,133 +0,0 @@
1
- /**
2
- * Backup + restore for the canonical SQLite store.
3
- *
4
- * Backup is live-safe: `VACUUM INTO` takes a read lock and writes a clean,
5
- * defragmented, single-file snapshot — no WAL sidecars, consistent even
6
- * while the daemon is ingesting.
7
- *
8
- * Restore cannot swap a file the daemon holds open. Instead the uploaded
9
- * DB is validated and parked at `<dbPath>.restore-pending`; the next daemon
10
- * boot calls `applyPendingRestore()` before opening the store, moving the
11
- * current DB aside to `<dbPath>.pre-restore-<ts>` and promoting the pending
12
- * file. The desktop shell turns "restart required" into one click.
13
- */
14
-
15
- import Database from "better-sqlite3";
16
- import { existsSync, renameSync, rmSync, statSync } from "node:fs";
17
- import { dirname, join } from "node:path";
18
-
19
- export const PENDING_SUFFIX = ".restore-pending";
20
-
21
- export interface RestoreValidation {
22
- ok: boolean;
23
- error?: string;
24
- sessions?: number;
25
- schemaVersion?: number;
26
- }
27
-
28
- /**
29
- * Validate that `filePath` is a usable canonical store: passes integrity
30
- * check and carries the `sessions` + `schema_migrations` tables. Opened
31
- * read-only and without the sqlite-vec extension — we never touch the
32
- * vec virtual tables here, so the extension isn't needed.
33
- */
34
- export function validateRestoreCandidate(filePath: string): RestoreValidation {
35
- let db: Database.Database | null = null;
36
- try {
37
- db = new Database(filePath, { readonly: true, fileMustExist: true });
38
- const integrity = db.pragma("integrity_check", { simple: true });
39
- if (integrity !== "ok") {
40
- return { ok: false, error: `integrity check failed: ${String(integrity)}` };
41
- }
42
- const tables = db
43
- .prepare<[], { name: string }>(
44
- "SELECT name FROM sqlite_master WHERE type='table' AND name IN ('sessions','schema_migrations')",
45
- )
46
- .all()
47
- .map((r) => r.name);
48
- if (!tables.includes("sessions") || !tables.includes("schema_migrations")) {
49
- return { ok: false, error: "not an nlm-memory database (missing sessions/schema_migrations)" };
50
- }
51
- const sessions = db
52
- .prepare<[], { n: number }>("SELECT COUNT(*) AS n FROM sessions")
53
- .get();
54
- const version = db
55
- .prepare<[], { v: number | null }>("SELECT MAX(version) AS v FROM schema_migrations")
56
- .get();
57
- return {
58
- ok: true,
59
- sessions: sessions?.n ?? 0,
60
- schemaVersion: version?.v ?? 0,
61
- };
62
- } catch (e) {
63
- return { ok: false, error: e instanceof Error ? e.message : String(e) };
64
- } finally {
65
- db?.close();
66
- }
67
- }
68
-
69
- /**
70
- * Park an already-written candidate file as the pending restore for
71
- * `dbPath`. Validates first; on failure the candidate is removed and the
72
- * validation error returned. On success the candidate is renamed to
73
- * `<dbPath>.restore-pending` (same directory, so the rename is atomic).
74
- */
75
- export function stageRestore(dbPath: string, candidatePath: string): RestoreValidation {
76
- const validation = validateRestoreCandidate(candidatePath);
77
- if (!validation.ok) {
78
- rmSync(candidatePath, { force: true });
79
- return validation;
80
- }
81
- const pending = dbPath + PENDING_SUFFIX;
82
- rmSync(pending, { force: true });
83
- renameSync(candidatePath, pending);
84
- return validation;
85
- }
86
-
87
- export interface PendingRestoreResult {
88
- applied: boolean;
89
- archivedTo?: string;
90
- }
91
-
92
- /**
93
- * If a pending restore exists for `dbPath`, promote it: move the current
94
- * DB (and its WAL/SHM sidecars) aside, then rename the pending file into
95
- * place. Call once at boot, before the store is opened.
96
- */
97
- export function applyPendingRestore(dbPath: string): PendingRestoreResult {
98
- const pending = dbPath + PENDING_SUFFIX;
99
- if (!existsSync(pending)) return { applied: false };
100
-
101
- const stamp = new Date().toISOString().replace(/[:.]/g, "-");
102
- const archived = `${dbPath}.pre-restore-${stamp}`;
103
-
104
- if (existsSync(dbPath)) {
105
- renameSync(dbPath, archived);
106
- }
107
- // The archived DB's WAL/SHM belong to it — drop the live sidecars so the
108
- // promoted file isn't paired with a stale WAL.
109
- for (const sidecar of [`${dbPath}-wal`, `${dbPath}-shm`]) {
110
- rmSync(sidecar, { force: true });
111
- }
112
- renameSync(pending, dbPath);
113
-
114
- return existsSync(archived)
115
- ? { applied: true, archivedTo: archived }
116
- : { applied: true };
117
- }
118
-
119
- /**
120
- * Write a live-consistent snapshot of `db` to a fresh file via
121
- * `VACUUM INTO`. The destination must not already exist. Returns the
122
- * snapshot's size in bytes.
123
- */
124
- export function vacuumSnapshot(db: Database.Database, destPath: string): number {
125
- rmSync(destPath, { force: true });
126
- db.prepare("VACUUM INTO ?").run(destPath);
127
- return statSync(destPath).size;
128
- }
129
-
130
- /** Scratch path for a backup snapshot, alongside the DB so rename stays atomic. */
131
- export function snapshotScratchPath(dbPath: string): string {
132
- return join(dirname(dbPath), `.backup-${process.pid}-${Date.now()}.sqlite`);
133
- }
@@ -1,45 +0,0 @@
1
- /**
2
- * live-status — derive the three-tier session status (active / idle / closed)
3
- * from a transcript file's mtime. Mirrors the Python daemon's
4
- * live_session_status(): explicit supersedence wins; missing file → closed;
5
- * otherwise bucketed by age.
6
- *
7
- * Thresholds match Python exactly:
8
- * < 15 min → active
9
- * 15 min – 24 h → idle
10
- * ≥ 24 h → closed
11
- *
12
- * Pure function over filesystem mtime. Tested with synthetic file ages.
13
- */
14
-
15
- import { statSync } from "node:fs";
16
- import { join } from "node:path";
17
- import { homedir } from "node:os";
18
- import type { SessionStatus } from "@shared/types.js";
19
-
20
- const ACTIVE_THRESHOLD_MS = 15 * 60 * 1000;
21
- const IDLE_THRESHOLD_MS = 24 * 60 * 60 * 1000;
22
-
23
- function expandHome(path: string): string {
24
- if (path.startsWith("~/")) return join(homedir(), path.slice(2));
25
- return path;
26
- }
27
-
28
- export function liveSessionStatus(
29
- transcriptPath: string | null,
30
- persistedStatus: SessionStatus | "active" | "closed" | "superseded",
31
- now: number = Date.now(),
32
- ): SessionStatus {
33
- if (persistedStatus === "superseded") return "superseded";
34
- if (!transcriptPath) return "closed";
35
- try {
36
- const expanded = expandHome(transcriptPath);
37
- const st = statSync(expanded);
38
- const ageMs = now - st.mtimeMs;
39
- if (ageMs < ACTIVE_THRESHOLD_MS) return "active";
40
- if (ageMs < IDLE_THRESHOLD_MS) return "idle";
41
- return "closed";
42
- } catch {
43
- return "closed";
44
- }
45
- }
@@ -1,72 +0,0 @@
1
- /**
2
- * Migration runner. Reads versioned *.sql files from a directory, applies any
3
- * whose integer prefix is not yet in schema_migrations, and returns the list
4
- * of newly applied versions. Idempotent: re-running on an up-to-date database
5
- * is a no-op. Each migration file is expected to end with its own
6
- * `INSERT OR IGNORE INTO schema_migrations (...) VALUES (...)`; the runner
7
- * also defensively upserts the row in case a file forgets.
8
- */
9
-
10
- import { readFileSync, readdirSync } from "node:fs";
11
- import { join } from "node:path";
12
- import type Database from "better-sqlite3";
13
-
14
- export interface AppliedMigration {
15
- readonly version: number;
16
- readonly name: string;
17
- }
18
-
19
- const FILE_PATTERN = /^(\d+)_([a-z0-9_-]+)\.sql$/i;
20
-
21
- export function runMigrations(
22
- db: Database.Database,
23
- migrationsDir: string,
24
- ): ReadonlyArray<AppliedMigration> {
25
- db.exec(`
26
- CREATE TABLE IF NOT EXISTS schema_migrations (
27
- version INTEGER PRIMARY KEY,
28
- name TEXT NOT NULL,
29
- applied_at TEXT NOT NULL DEFAULT (datetime('now'))
30
- );
31
- `);
32
-
33
- const applied = new Set<number>(
34
- db
35
- .prepare<[], { version: number }>("SELECT version FROM schema_migrations")
36
- .all()
37
- .map((r) => r.version),
38
- );
39
-
40
- const files = readdirSync(migrationsDir)
41
- .filter((f) => FILE_PATTERN.test(f))
42
- .sort();
43
-
44
- const newlyApplied: AppliedMigration[] = [];
45
- const upsert = db.prepare(
46
- "INSERT OR IGNORE INTO schema_migrations (version, name) VALUES (?, ?)",
47
- );
48
-
49
- for (const file of files) {
50
- const match = FILE_PATTERN.exec(file);
51
- if (!match) continue;
52
- const version = Number(match[1]);
53
- const name = match[2] ?? file;
54
- if (applied.has(version)) continue;
55
-
56
- const sql = readFileSync(join(migrationsDir, file), "utf8");
57
- db.exec("BEGIN");
58
- try {
59
- db.exec(sql);
60
- upsert.run(version, name);
61
- db.exec("COMMIT");
62
- } catch (err) {
63
- db.exec("ROLLBACK");
64
- throw new Error(
65
- `Migration ${file} failed: ${err instanceof Error ? err.message : String(err)}`,
66
- );
67
- }
68
- newlyApplied.push({ version, name });
69
- }
70
-
71
- return newlyApplied;
72
- }