@llmnesia/mcp 0.2.11 → 0.2.13

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.
@@ -14,20 +14,21 @@ import {
14
14
  openSearchIndex,
15
15
  readAutomaticMaintenanceState,
16
16
  resolveCorpusDir,
17
+ retryQuarantine,
17
18
  runtimeBinDir,
18
19
  staleIndexFiles,
19
20
  uninstallRuntime
20
- } from "./chunk-7ISC4REX.js";
21
+ } from "./chunk-QIRYUQIU.js";
21
22
  import {
22
23
  VERSION,
23
24
  __commonJS,
24
25
  __require,
25
26
  __toESM
26
- } from "./chunk-ZYE6VY6A.js";
27
+ } from "./chunk-GHKXNM7R.js";
27
28
 
28
- // ../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/isexe/windows.js
29
+ // ../node_modules/isexe/windows.js
29
30
  var require_windows = __commonJS({
30
- "../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/isexe/windows.js"(exports, module) {
31
+ "../node_modules/isexe/windows.js"(exports, module) {
31
32
  "use strict";
32
33
  module.exports = isexe;
33
34
  isexe.sync = sync;
@@ -66,9 +67,9 @@ var require_windows = __commonJS({
66
67
  }
67
68
  });
68
69
 
69
- // ../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/isexe/mode.js
70
+ // ../node_modules/isexe/mode.js
70
71
  var require_mode = __commonJS({
71
- "../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/isexe/mode.js"(exports, module) {
72
+ "../node_modules/isexe/mode.js"(exports, module) {
72
73
  "use strict";
73
74
  module.exports = isexe;
74
75
  isexe.sync = sync;
@@ -100,9 +101,9 @@ var require_mode = __commonJS({
100
101
  }
101
102
  });
102
103
 
103
- // ../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/isexe/index.js
104
+ // ../node_modules/isexe/index.js
104
105
  var require_isexe = __commonJS({
105
- "../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/isexe/index.js"(exports, module) {
106
+ "../node_modules/isexe/index.js"(exports, module) {
106
107
  "use strict";
107
108
  var fs = __require("fs");
108
109
  var core;
@@ -156,9 +157,9 @@ var require_isexe = __commonJS({
156
157
  }
157
158
  });
158
159
 
159
- // ../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/which/which.js
160
+ // ../node_modules/which/which.js
160
161
  var require_which = __commonJS({
161
- "../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/which/which.js"(exports, module) {
162
+ "../node_modules/which/which.js"(exports, module) {
162
163
  "use strict";
163
164
  var isWindows2 = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
164
165
  var path = __require("path");
@@ -253,9 +254,9 @@ var require_which = __commonJS({
253
254
  }
254
255
  });
255
256
 
256
- // ../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/path-key/index.js
257
+ // ../node_modules/path-key/index.js
257
258
  var require_path_key = __commonJS({
258
- "../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/path-key/index.js"(exports, module) {
259
+ "../node_modules/path-key/index.js"(exports, module) {
259
260
  "use strict";
260
261
  var pathKey = (options = {}) => {
261
262
  const environment = options.env || process.env;
@@ -270,9 +271,9 @@ var require_path_key = __commonJS({
270
271
  }
271
272
  });
272
273
 
273
- // ../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/cross-spawn/lib/util/resolveCommand.js
274
+ // ../node_modules/cross-spawn/lib/util/resolveCommand.js
274
275
  var require_resolveCommand = __commonJS({
275
- "../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/cross-spawn/lib/util/resolveCommand.js"(exports, module) {
276
+ "../node_modules/cross-spawn/lib/util/resolveCommand.js"(exports, module) {
276
277
  "use strict";
277
278
  var path = __require("path");
278
279
  var which = require_which();
@@ -312,9 +313,9 @@ var require_resolveCommand = __commonJS({
312
313
  }
313
314
  });
314
315
 
315
- // ../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/cross-spawn/lib/util/escape.js
316
+ // ../node_modules/cross-spawn/lib/util/escape.js
316
317
  var require_escape = __commonJS({
317
- "../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/cross-spawn/lib/util/escape.js"(exports, module) {
318
+ "../node_modules/cross-spawn/lib/util/escape.js"(exports, module) {
318
319
  "use strict";
319
320
  var metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g;
320
321
  function escapeCommand(arg) {
@@ -337,17 +338,17 @@ var require_escape = __commonJS({
337
338
  }
338
339
  });
339
340
 
340
- // ../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/shebang-regex/index.js
341
+ // ../node_modules/shebang-regex/index.js
341
342
  var require_shebang_regex = __commonJS({
342
- "../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/shebang-regex/index.js"(exports, module) {
343
+ "../node_modules/shebang-regex/index.js"(exports, module) {
343
344
  "use strict";
344
345
  module.exports = /^#!(.*)/;
345
346
  }
346
347
  });
347
348
 
348
- // ../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/shebang-command/index.js
349
+ // ../node_modules/shebang-command/index.js
349
350
  var require_shebang_command = __commonJS({
350
- "../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/shebang-command/index.js"(exports, module) {
351
+ "../node_modules/shebang-command/index.js"(exports, module) {
351
352
  "use strict";
352
353
  var shebangRegex = require_shebang_regex();
353
354
  module.exports = (string = "") => {
@@ -365,9 +366,9 @@ var require_shebang_command = __commonJS({
365
366
  }
366
367
  });
367
368
 
368
- // ../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/cross-spawn/lib/util/readShebang.js
369
+ // ../node_modules/cross-spawn/lib/util/readShebang.js
369
370
  var require_readShebang = __commonJS({
370
- "../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/cross-spawn/lib/util/readShebang.js"(exports, module) {
371
+ "../node_modules/cross-spawn/lib/util/readShebang.js"(exports, module) {
371
372
  "use strict";
372
373
  var fs = __require("fs");
373
374
  var shebangCommand = require_shebang_command();
@@ -387,9 +388,9 @@ var require_readShebang = __commonJS({
387
388
  }
388
389
  });
389
390
 
390
- // ../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/cross-spawn/lib/parse.js
391
+ // ../node_modules/cross-spawn/lib/parse.js
391
392
  var require_parse = __commonJS({
392
- "../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/cross-spawn/lib/parse.js"(exports, module) {
393
+ "../node_modules/cross-spawn/lib/parse.js"(exports, module) {
393
394
  "use strict";
394
395
  var path = __require("path");
395
396
  var resolveCommand = require_resolveCommand();
@@ -449,9 +450,9 @@ var require_parse = __commonJS({
449
450
  }
450
451
  });
451
452
 
452
- // ../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/cross-spawn/lib/enoent.js
453
+ // ../node_modules/cross-spawn/lib/enoent.js
453
454
  var require_enoent = __commonJS({
454
- "../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/cross-spawn/lib/enoent.js"(exports, module) {
455
+ "../node_modules/cross-spawn/lib/enoent.js"(exports, module) {
455
456
  "use strict";
456
457
  var isWin = process.platform === "win32";
457
458
  function notFoundError(original, syscall) {
@@ -499,9 +500,9 @@ var require_enoent = __commonJS({
499
500
  }
500
501
  });
501
502
 
502
- // ../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/cross-spawn/index.js
503
+ // ../node_modules/cross-spawn/index.js
503
504
  var require_cross_spawn = __commonJS({
504
- "../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/cross-spawn/index.js"(exports, module) {
505
+ "../node_modules/cross-spawn/index.js"(exports, module) {
505
506
  "use strict";
506
507
  var cp = __require("child_process");
507
508
  var parse = require_parse();
@@ -888,13 +889,13 @@ function parseArgs(argv) {
888
889
  return { command: positionals[0], positionals: positionals.slice(1), options, flags };
889
890
  }
890
891
 
891
- // ../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/chokidar/esm/index.js
892
+ // ../node_modules/chokidar/esm/index.js
892
893
  import { stat as statcb } from "fs";
893
894
  import { stat as stat4, readdir as readdir2 } from "fs/promises";
894
895
  import { EventEmitter } from "events";
895
896
  import * as sysPath2 from "path";
896
897
 
897
- // ../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/readdirp/esm/index.js
898
+ // ../node_modules/readdirp/esm/index.js
898
899
  import { stat as stat2, lstat, readdir, realpath } from "fs/promises";
899
900
  import { Readable } from "stream";
900
901
  import { resolve as presolve, relative as prelative, join as pjoin, sep as psep } from "path";
@@ -1113,7 +1114,7 @@ function readdirp(root, options = {}) {
1113
1114
  return new ReaddirpStream(options);
1114
1115
  }
1115
1116
 
1116
- // ../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/chokidar/esm/handler.js
1117
+ // ../node_modules/chokidar/esm/handler.js
1117
1118
  import { watchFile, unwatchFile, watch as fs_watch } from "fs";
1118
1119
  import { open, stat as stat3, lstat as lstat2, realpath as fsrealpath } from "fs/promises";
1119
1120
  import * as sysPath from "path";
@@ -1869,7 +1870,7 @@ var NodeFsHandler = class {
1869
1870
  }
1870
1871
  };
1871
1872
 
1872
- // ../../../../Users/Kdog/Code-Projects/LLMnesia/node_modules/chokidar/esm/index.js
1873
+ // ../node_modules/chokidar/esm/index.js
1873
1874
  var SLASH = "/";
1874
1875
  var SLASH_SLASH = "//";
1875
1876
  var ONE_DOT = ".";
@@ -3829,6 +3830,8 @@ Power-user / debugging:
3829
3830
  ingest <file...> Ingest one or more NDJSON backup files into the corpus
3830
3831
  drain Ingest every file in <corpus>/inbox; discard clean
3831
3832
  payloads and quarantine malformed ones
3833
+ retry-quarantine Retry quarantined files; retain repaired originals in
3834
+ <corpus>/inbox/recovered (close MCP clients first)
3832
3835
  watch Watch <corpus>/inbox and ingest new deltas live
3833
3836
  stats Print corpus counts (conversations, messages, platforms)
3834
3837
  compact Losslessly compress conversations and remove redundant
@@ -4058,6 +4061,20 @@ async function main() {
4058
4061
  }
4059
4062
  return 0;
4060
4063
  }
4064
+ case "retry-quarantine": {
4065
+ const index = await openSearchIndex(corpus);
4066
+ try {
4067
+ const report = await retryQuarantine(corpus, index);
4068
+ process.stdout.write(
4069
+ `Quarantine recovery: ${report.recovered} recovered, ${report.remaining} remaining, ${report.failed} failed.
4070
+ ` + (report.archiveDir ? `Original files retained at ${report.archiveDir}
4071
+ ` : "")
4072
+ );
4073
+ return report.remaining > 0 || report.failed > 0 ? 1 : 0;
4074
+ } finally {
4075
+ index.close();
4076
+ }
4077
+ }
4061
4078
  case "watch": {
4062
4079
  process.stdout.write(`Watching ${corpus.paths.inboxDir} for new deltas (Ctrl+C to stop)\u2026
4063
4080
  `);
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import { createRequire as __llmnesiaCreateRequire } from "node:module";
3
3
  const require = __llmnesiaCreateRequire(import.meta.url);
4
4
  import {
5
5
  VERSION
6
- } from "./chunk-ZYE6VY6A.js";
6
+ } from "./chunk-GHKXNM7R.js";
7
7
 
8
8
  // src/nodeDownload.ts
9
9
  var NODE_DOWNLOAD_URLS = {
@@ -141,7 +141,7 @@ async function bootstrap() {
141
141
  await guideUnsupportedNodeVersion(process.versions.node);
142
142
  return 1;
143
143
  }
144
- const { main } = await import("./cli-RQMFEW5J.js");
144
+ const { main } = await import("./cli-CMCIFSWH.js");
145
145
  return main();
146
146
  }
147
147
  bootstrap().then((code) => {
@@ -19,11 +19,13 @@
19
19
  // without ~/.llmnesia/config.json this host is exactly as read-only as it
20
20
  // has always been for plain session indexing.
21
21
  //
22
- // Protocol v4 (extension → host). v1 was Claude-Code-only: `list` carried no
22
+ // Protocol v5 (extension → host). v1 was Claude-Code-only: `list` carried no
23
23
  // `source` and `get` took a bare sessionId. Both remain accepted, so an
24
24
  // extension older than this host keeps working; an extension NEWER than the
25
25
  // host detects v1 from the pong and syncs Claude Code only.
26
26
  //
27
+ // v5 adds per-source listing health and putSourceHealth metadata updates,
28
+ // keeping diagnostics separate from the conversation inbox.
27
29
  // v4 adds ZCode to `list`/`get` (the first source backed by a SQLite database
28
30
  // rather than per-session files; `get` materializes the transcript as JSONL).
29
31
  // The version is informational in both directions, as below: a v3 host never
@@ -34,7 +36,7 @@
34
36
  // treats that as "nothing to fetch", so the version is informational: hosts are
35
37
  // installed out of band and an extension upgraded ahead of its host is the
36
38
  // ordinary case, not a fault.
37
- // { cmd: "ping" } → { type: "pong", version: 4 }
39
+ // { cmd: "ping" } → { type: "pong", version: 5 }
38
40
  // { cmd: "list" } → { type: "list", sessions:
39
41
  // [{source, sessionId, mtime, project?, title?}] }
40
42
  // { cmd: "get", source, sessionId } → one or more { type: "chunk", sessionId, seq, total, data }
@@ -59,10 +61,10 @@ import { chmod, mkdir, readdir, readFile, rename, stat, writeFile } from "node:f
59
61
  import { homedir } from "node:os";
60
62
  import { join } from "node:path";
61
63
 
62
- // 4 adds ZCode (see the protocol comment above). Do not make it a gate: hosts
64
+ // 5 adds per-source listing health (see the protocol comment above). Do not make it a gate: hosts
63
65
  // are installed out of band and a version check would strand every user who
64
66
  // upgraded the extension first.
65
- const PROTOCOL_VERSION = 4;
67
+ const PROTOCOL_VERSION = 5;
66
68
  const PROJECTS_DIR = join(homedir(), ".claude", "projects");
67
69
  // ZCode keeps every session in one SQLite database (WAL mode) — the first
68
70
  // source with no per-session file. The host opens it READ-ONLY and materializes
@@ -134,12 +136,13 @@ function send(message) {
134
136
  // One entry per session: { source, sessionId, mtime, project?, title?, path }.
135
137
  // `path` is stripped before anything goes on the wire.
136
138
 
137
- async function listClaudeCodeSessions() {
139
+ async function listClaudeCodeSessions(issues) {
138
140
  const sessions = [];
139
141
  let projectDirs;
140
142
  try {
141
143
  projectDirs = await readdir(PROJECTS_DIR, { withFileTypes: true });
142
144
  } catch {
145
+ if (issues) issues.failed += 1;
143
146
  return sessions; // no ~/.claude/projects yet
144
147
  }
145
148
  for (const entry of projectDirs) {
@@ -151,6 +154,7 @@ async function listClaudeCodeSessions() {
151
154
  try {
152
155
  files = await readdir(dir);
153
156
  } catch {
157
+ if (issues) issues.failed += 1;
154
158
  continue;
155
159
  }
156
160
  for (const file of files) {
@@ -168,6 +172,7 @@ async function listClaudeCodeSessions() {
168
172
  path
169
173
  });
170
174
  } catch {
175
+ if (issues) issues.failed += 1;
171
176
  // Skip unreadable files.
172
177
  }
173
178
  }
@@ -186,18 +191,19 @@ function codexSessionId(filename) {
186
191
  return match ? match[1].toLowerCase() : base;
187
192
  }
188
193
 
189
- async function walkCodexDir(dir, depth, out) {
194
+ async function walkCodexDir(dir, depth, out, issues) {
190
195
  let entries;
191
196
  try {
192
197
  entries = await readdir(dir, { withFileTypes: true });
193
198
  } catch {
199
+ if (issues) issues.failed += 1;
194
200
  return out; // missing or unreadable — nothing to add
195
201
  }
196
202
  for (const entry of entries) {
197
203
  const path = join(dir, entry.name);
198
204
  if (entry.isDirectory()) {
199
205
  if (depth > 0) {
200
- await walkCodexDir(path, depth - 1, out);
206
+ await walkCodexDir(path, depth - 1, out, issues);
201
207
  }
202
208
  continue;
203
209
  }
@@ -213,6 +219,7 @@ async function walkCodexDir(dir, depth, out) {
213
219
  path
214
220
  });
215
221
  } catch {
222
+ if (issues) issues.failed += 1;
216
223
  // Skip unreadable files.
217
224
  }
218
225
  }
@@ -249,8 +256,8 @@ async function readCodexThreadNames(codexHome) {
249
256
  return titles;
250
257
  }
251
258
 
252
- async function listCodexSessions(codexHome) {
253
- const sessions = await walkCodexDir(join(codexHome, "sessions"), CODEX_MAX_DEPTH, []);
259
+ async function listCodexSessions(codexHome, issues) {
260
+ const sessions = await walkCodexDir(join(codexHome, "sessions"), CODEX_MAX_DEPTH, [], issues);
254
261
  if (sessions.length === 0) {
255
262
  return sessions;
256
263
  }
@@ -299,9 +306,10 @@ async function openZcodeDb() {
299
306
  * title ("New Session"); only real titles are attached, so the extension's
300
307
  * derived "<project> · <first prompt>" still wins when ZCode has no name.
301
308
  */
302
- async function listZcodeSessions() {
309
+ async function listZcodeSessions(issues) {
303
310
  const db = await openZcodeDb();
304
311
  if (!db) {
312
+ if (issues) issues.failed += 1;
305
313
  return [];
306
314
  }
307
315
  try {
@@ -331,6 +339,7 @@ async function listZcodeSessions() {
331
339
  }
332
340
  return sessions;
333
341
  } catch {
342
+ if (issues) issues.failed += 1;
334
343
  return []; // unreadable or mid-migration database — ZCode contributes nothing
335
344
  } finally {
336
345
  db.close();
@@ -467,6 +476,36 @@ async function listSessions() {
467
476
  return [...claude, ...codex, ...zcode];
468
477
  }
469
478
 
479
+ // A missing root and an unreadable source must not both appear as a healthy,
480
+ // empty scan. Paths and transcript data never enter these diagnostics.
481
+ async function listSessionsWithHealth() {
482
+ const config = await loadConfig();
483
+ const codexHome = config?.codexHome || DEFAULT_CODEX_HOME;
484
+ const sources = [
485
+ ["claude_code", PROJECTS_DIR, (issues) => listClaudeCodeSessions(issues)],
486
+ ["codex", join(codexHome, "sessions"), (issues) => listCodexSessions(codexHome, issues)],
487
+ ["zcode", ZCODE_DB, (issues) => listZcodeSessions(issues)]
488
+ ];
489
+ const sessions = [];
490
+ const sourceStatus = {};
491
+ for (const [source, path, list] of sources) {
492
+ try {
493
+ await stat(path);
494
+ } catch (error) {
495
+ sourceStatus[source] = { state: error?.code === "ENOENT" ? "missing" : "error" };
496
+ continue;
497
+ }
498
+ const issues = { failed: 0 };
499
+ try {
500
+ sessions.push(...await list(issues));
501
+ sourceStatus[source] = { state: issues.failed > 0 ? "partial" : "ok" };
502
+ } catch {
503
+ sourceStatus[source] = { state: "error" };
504
+ }
505
+ }
506
+ return { sessions, sourceStatus };
507
+ }
508
+
470
509
  async function loadConfig() {
471
510
  try {
472
511
  const raw = await readFile(CONFIG_FILE, "utf8");
@@ -648,6 +687,43 @@ async function readCorpusMemories(corpusRoot) {
648
687
  return { memories, skipped };
649
688
  }
650
689
 
690
+ // A separate metadata channel keeps health heartbeats out of the conversation
691
+ // inbox. Older MCP readers ignore this file and keep their true data age.
692
+ function sourceHealthRow(value) {
693
+ if (!value || typeof value !== "object") return null;
694
+ const { source, observedAt, connection, state, lastAttemptAt, lastSuccessfulSyncAt, lastCaptureAt, failedSessions } = value;
695
+ const time = (v) => Number.isSafeInteger(v) && v >= 0;
696
+ if (!["claude_code", "codex", "zcode"].includes(source) || !["connected", "disconnected"].includes(connection) ||
697
+ !["ok", "partial", "missing", "error", "unknown"].includes(state) || !time(observedAt) ||
698
+ observedAt > Date.now() + 300000 || !time(lastAttemptAt) || lastAttemptAt > observedAt ||
699
+ (lastSuccessfulSyncAt !== null && (!time(lastSuccessfulSyncAt) || lastSuccessfulSyncAt > observedAt)) ||
700
+ (lastCaptureAt !== null && (!time(lastCaptureAt) || lastCaptureAt > observedAt)) || !time(failedSessions)) return null;
701
+ return { source, observedAt, connection, state, lastAttemptAt, lastSuccessfulSyncAt, lastCaptureAt, failedSessions };
702
+ }
703
+
704
+ async function handleSourceHealth(message) {
705
+ const config = await loadConfig();
706
+ if (!config?.corpusRoot || !Array.isArray(message.rows) || message.rows.length > 3) {
707
+ send({ type: "error", error: "source health: invalid rows or no corpus configured" }); return;
708
+ }
709
+ const rows = message.rows.map(sourceHealthRow);
710
+ if (rows.some((row) => !row)) {
711
+ send({ type: "error", error: "source health: invalid report" }); return;
712
+ }
713
+ const path = join(config.corpusRoot, "source-health-v1.json");
714
+ let previous = [];
715
+ try { previous = JSON.parse(await readFile(path, "utf8")); } catch { /* First report. */ }
716
+ const current = new Map((Array.isArray(previous) ? previous : []).map(sourceHealthRow).filter(Boolean).map((row) => [row.source, row]));
717
+ for (const row of rows) {
718
+ if ((current.get(row.source)?.observedAt ?? -1) < row.observedAt) current.set(row.source, row);
719
+ }
720
+ await mkdir(config.corpusRoot, { recursive: true, mode: 0o700 });
721
+ const temp = `${path}.${process.pid}.tmp`;
722
+ await writeFile(temp, JSON.stringify([...current.values()]) + "\n", { mode: FILE_MODE });
723
+ await rename(temp, path);
724
+ send({ type: "sourceHealthAck" });
725
+ }
726
+
651
727
  async function handle(message) {
652
728
  if (!message || typeof message !== "object") {
653
729
  return;
@@ -662,7 +738,11 @@ async function handle(message) {
662
738
  send({ type: "corpusInfo", present: false });
663
739
  return;
664
740
  }
665
- send({ type: "corpusInfo", present: true, root: config.corpusRoot });
741
+ send({ type: "corpusInfo", present: true, root: config.corpusRoot, sourceHealthSupported: true });
742
+ return;
743
+ }
744
+ if (message.cmd === "putSourceHealth") {
745
+ await handleSourceHealth(message);
666
746
  return;
667
747
  }
668
748
  if (message.cmd === "putChunk") {
@@ -680,8 +760,8 @@ async function handle(message) {
680
760
  return;
681
761
  }
682
762
  if (message.cmd === "list") {
683
- const sessions = await listSessions();
684
- send({ type: "list", sessions: sessions.map(({ path, ...rest }) => rest) });
763
+ const { sessions, sourceStatus } = await listSessionsWithHealth();
764
+ send({ type: "list", sessions: sessions.map(({ path, ...rest }) => rest), sourceStatus });
685
765
  return;
686
766
  }
687
767
  if (message.cmd === "get") {
Binary file