chat-recall 0.7.7 → 0.7.9

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.
@@ -9754,15 +9754,36 @@ var init_claude = __esm({
9754
9754
  listSessions(opts = {}) {
9755
9755
  const cutoff = opts.sinceMs ?? 0;
9756
9756
  const filter = opts.projectFilter?.toLowerCase();
9757
- const seen = /* @__PURE__ */ new Set();
9758
- const out = [];
9757
+ const byId = /* @__PURE__ */ new Map();
9759
9758
  for (const root of claudeProjectDirs()) {
9760
- this.collectSessionsFromRoot(root, cutoff, filter, seen, out, opts.previews !== false);
9759
+ this.collectSessionsFromRoot(root, filter, byId, opts.previews !== false);
9761
9760
  }
9761
+ const out = [...byId.values()].filter((r) => r.mtime >= cutoff);
9762
9762
  out.sort((a, b) => b.mtime - a.mtime);
9763
9763
  return opts.limit ? out.slice(0, opts.limit) : out;
9764
9764
  }
9765
- collectSessionsFromRoot(root, cutoff, filter, seen, out, previews) {
9765
+ /**
9766
+ * Add this root's sessions to `byId`. A session resumed under a second
9767
+ * profile has one file in each home under the same id. It is ONE session: the
9768
+ * first home keeps its path, and its mtime is the newest of all copies.
9769
+ *
9770
+ * Each copy was listed as its own ref before. Session 8da8d72b had a primary
9771
+ * copy frozen at 05:55 and a second-home copy growing until 12:00; the walk
9772
+ * handled both refs under one ledger row with two different mtimes, and the
9773
+ * row that shipped nothing stamped the other copy's mtime.
9774
+ */
9775
+ collectSessionsFromRoot(root, filter, byId, previews) {
9776
+ const add = (ref) => {
9777
+ const prior = byId.get(ref.rawId);
9778
+ if (!prior) {
9779
+ byId.set(ref.rawId, ref);
9780
+ return;
9781
+ }
9782
+ if (ref.mtime > prior.mtime) {
9783
+ prior.mtime = ref.mtime;
9784
+ prior.modified = ref.modified;
9785
+ }
9786
+ };
9766
9787
  if (!existsSync11(root))
9767
9788
  return;
9768
9789
  for (const proj of readdirSync8(root, { withFileTypes: true })) {
@@ -9773,6 +9794,7 @@ var init_claude = __esm({
9773
9794
  const projPath = join11(root, proj.name);
9774
9795
  const projectPath = resolveProjectDirName(proj.name);
9775
9796
  const indexPath = join11(projPath, "sessions-index.json");
9797
+ const indexed = /* @__PURE__ */ new Set();
9776
9798
  if (existsSync11(indexPath)) {
9777
9799
  try {
9778
9800
  const indexData = JSON.parse(readFileSync7(indexPath, "utf-8"));
@@ -9780,10 +9802,8 @@ var init_claude = __esm({
9780
9802
  for (const entry of entries) {
9781
9803
  if (!entry.fullPath || !existsSync11(entry.fullPath))
9782
9804
  continue;
9783
- if ((entry.fileMtime ?? 0) < cutoff)
9784
- continue;
9785
- seen.add(entry.sessionId);
9786
- out.push({
9805
+ indexed.add(entry.sessionId);
9806
+ add({
9787
9807
  toolId: "claude",
9788
9808
  rawId: entry.sessionId,
9789
9809
  prefixedId: entry.sessionId,
@@ -9810,7 +9830,7 @@ var init_claude = __esm({
9810
9830
  if (!f.endsWith(".jsonl") || f === "sessions-index.json")
9811
9831
  continue;
9812
9832
  const sessionId = basename6(f, ".jsonl");
9813
- if (seen.has(sessionId))
9833
+ if (indexed.has(sessionId))
9814
9834
  continue;
9815
9835
  const fullPath = join11(projPath, f);
9816
9836
  let stat;
@@ -9819,10 +9839,13 @@ var init_claude = __esm({
9819
9839
  } catch {
9820
9840
  continue;
9821
9841
  }
9822
- if (stat.mtimeMs < cutoff)
9842
+ const prior = byId.get(sessionId);
9843
+ if (prior) {
9844
+ add({ ...prior, mtime: stat.mtimeMs, modified: stat.mtime.toISOString() });
9823
9845
  continue;
9846
+ }
9824
9847
  const firstPrompt = previews ? extractFirstUserPromptSync(fullPath, { maxLength: 200 }) : "";
9825
- out.push({
9848
+ add({
9826
9849
  toolId: "claude",
9827
9850
  rawId: sessionId,
9828
9851
  prefixedId: sessionId,
@@ -10326,6 +10349,7 @@ var init_shadow = __esm({
10326
10349
  // ../engine/dist/core/live-session-scan.js
10327
10350
  var live_session_scan_exports = {};
10328
10351
  __export(live_session_scan_exports, {
10352
+ INDEX_RECHECK_MS: () => INDEX_RECHECK_MS,
10329
10353
  MAX_EVENT_LINE_BYTES: () => MAX_EVENT_LINE_BYTES,
10330
10354
  detectTool: () => detectTool,
10331
10355
  findCodexSessionFile: () => findCodexSessionFile,
@@ -10372,9 +10396,29 @@ function findSessionFile(sessionId) {
10372
10396
  function invalidateSessionFileIndex() {
10373
10397
  indexCache = null;
10374
10398
  }
10399
+ function indexStillValid(cache2) {
10400
+ const roots = claudeProjectDirs();
10401
+ if (roots.length !== cache2.roots.length || roots.some((r, i) => r !== cache2.roots[i]))
10402
+ return false;
10403
+ for (const [dir, mtime] of cache2.dirs) {
10404
+ if (dirMtime(dir) !== mtime)
10405
+ return false;
10406
+ }
10407
+ return true;
10408
+ }
10375
10409
  function sessionFileIndex() {
10376
- if (indexCache)
10377
- return indexCache;
10410
+ if (indexCache) {
10411
+ const now = Date.now();
10412
+ if (now - indexCache.checkedAt < INDEX_RECHECK_MS)
10413
+ return indexCache.byId;
10414
+ if (indexStillValid(indexCache)) {
10415
+ indexCache.checkedAt = now;
10416
+ return indexCache.byId;
10417
+ }
10418
+ indexCache = null;
10419
+ }
10420
+ const dirs = /* @__PURE__ */ new Map();
10421
+ const roots = claudeProjectDirs();
10378
10422
  const byId = /* @__PURE__ */ new Map();
10379
10423
  const seenRoots = /* @__PURE__ */ new Set();
10380
10424
  const projectPaths = /* @__PURE__ */ new Map();
@@ -10386,10 +10430,11 @@ function sessionFileIndex() {
10386
10430
  }
10387
10431
  return p;
10388
10432
  };
10389
- for (const root of claudeProjectDirs()) {
10433
+ for (const root of roots) {
10390
10434
  if (seenRoots.has(root) || !existsSync13(root))
10391
10435
  continue;
10392
10436
  seenRoots.add(root);
10437
+ dirs.set(root, dirMtime(root));
10393
10438
  let entries;
10394
10439
  try {
10395
10440
  entries = readdirSync10(root, { withFileTypes: true });
@@ -10400,6 +10445,7 @@ function sessionFileIndex() {
10400
10445
  if (!entry.isDirectory())
10401
10446
  continue;
10402
10447
  const dir = join13(root, entry.name);
10448
+ dirs.set(dir, dirMtime(dir));
10403
10449
  let names;
10404
10450
  try {
10405
10451
  names = readdirSync10(dir);
@@ -10424,7 +10470,7 @@ function sessionFileIndex() {
10424
10470
  }
10425
10471
  }
10426
10472
  if (scanScopeDepth > 0)
10427
- indexCache = byId;
10473
+ indexCache = { byId, dirs, roots, checkedAt: Date.now() };
10428
10474
  return byId;
10429
10475
  }
10430
10476
  function findSessionFiles(sessionId) {
@@ -10797,7 +10843,7 @@ function liveScanRecentEdits(opts) {
10797
10843
  allEdits.sort((a, b) => b.ts - a.ts);
10798
10844
  return allEdits;
10799
10845
  }
10800
- var scanScopeDepth, indexCache, readScope, readScopeDepth, derivedScope, geminiProjectMapCache, geminiCache, MAX_EVENT_LINE_BYTES;
10846
+ var scanScopeDepth, indexCache, INDEX_RECHECK_MS, dirMtime, readScope, readScopeDepth, derivedScope, geminiProjectMapCache, geminiCache, MAX_EVENT_LINE_BYTES;
10801
10847
  var init_live_session_scan = __esm({
10802
10848
  "../engine/dist/core/live-session-scan.js"() {
10803
10849
  "use strict";
@@ -10809,6 +10855,14 @@ var init_live_session_scan = __esm({
10809
10855
  init_project_dir_name();
10810
10856
  scanScopeDepth = 0;
10811
10857
  indexCache = null;
10858
+ INDEX_RECHECK_MS = 1e3;
10859
+ dirMtime = (p) => {
10860
+ try {
10861
+ return statSync11(p).mtimeMs;
10862
+ } catch {
10863
+ return -1;
10864
+ }
10865
+ };
10812
10866
  readScope = null;
10813
10867
  readScopeDepth = 0;
10814
10868
  derivedScope = null;
package/dist/watch.js CHANGED
@@ -6874,9 +6874,29 @@ function findSessionFile(sessionId) {
6874
6874
  function invalidateSessionFileIndex() {
6875
6875
  indexCache = null;
6876
6876
  }
6877
+ function indexStillValid(cache4) {
6878
+ const roots = claudeProjectDirs();
6879
+ if (roots.length !== cache4.roots.length || roots.some((r, i) => r !== cache4.roots[i]))
6880
+ return false;
6881
+ for (const [dir, mtime] of cache4.dirs) {
6882
+ if (dirMtime(dir) !== mtime)
6883
+ return false;
6884
+ }
6885
+ return true;
6886
+ }
6877
6887
  function sessionFileIndex() {
6878
- if (indexCache)
6879
- return indexCache;
6888
+ if (indexCache) {
6889
+ const now = Date.now();
6890
+ if (now - indexCache.checkedAt < INDEX_RECHECK_MS)
6891
+ return indexCache.byId;
6892
+ if (indexStillValid(indexCache)) {
6893
+ indexCache.checkedAt = now;
6894
+ return indexCache.byId;
6895
+ }
6896
+ indexCache = null;
6897
+ }
6898
+ const dirs = /* @__PURE__ */ new Map();
6899
+ const roots = claudeProjectDirs();
6880
6900
  const byId = /* @__PURE__ */ new Map();
6881
6901
  const seenRoots = /* @__PURE__ */ new Set();
6882
6902
  const projectPaths = /* @__PURE__ */ new Map();
@@ -6888,10 +6908,11 @@ function sessionFileIndex() {
6888
6908
  }
6889
6909
  return p;
6890
6910
  };
6891
- for (const root of claudeProjectDirs()) {
6911
+ for (const root of roots) {
6892
6912
  if (seenRoots.has(root) || !existsSync7(root))
6893
6913
  continue;
6894
6914
  seenRoots.add(root);
6915
+ dirs.set(root, dirMtime(root));
6895
6916
  let entries;
6896
6917
  try {
6897
6918
  entries = readdirSync6(root, { withFileTypes: true });
@@ -6902,6 +6923,7 @@ function sessionFileIndex() {
6902
6923
  if (!entry.isDirectory())
6903
6924
  continue;
6904
6925
  const dir = join9(root, entry.name);
6926
+ dirs.set(dir, dirMtime(dir));
6905
6927
  let names;
6906
6928
  try {
6907
6929
  names = readdirSync6(dir);
@@ -6926,7 +6948,7 @@ function sessionFileIndex() {
6926
6948
  }
6927
6949
  }
6928
6950
  if (scanScopeDepth > 0)
6929
- indexCache = byId;
6951
+ indexCache = { byId, dirs, roots, checkedAt: Date.now() };
6930
6952
  return byId;
6931
6953
  }
6932
6954
  function findSessionFiles(sessionId) {
@@ -7084,7 +7106,7 @@ function findCodexSessionFileIn(root, sessionId) {
7084
7106
  }
7085
7107
  return scanDir(root);
7086
7108
  }
7087
- var scanScopeDepth, indexCache, readScope, readScopeDepth, derivedScope, geminiCache, MAX_EVENT_LINE_BYTES;
7109
+ var scanScopeDepth, indexCache, INDEX_RECHECK_MS, dirMtime, readScope, readScopeDepth, derivedScope, geminiCache, MAX_EVENT_LINE_BYTES;
7088
7110
  var init_live_session_scan = __esm({
7089
7111
  "../engine/dist/core/live-session-scan.js"() {
7090
7112
  "use strict";
@@ -7096,6 +7118,14 @@ var init_live_session_scan = __esm({
7096
7118
  init_project_dir_name();
7097
7119
  scanScopeDepth = 0;
7098
7120
  indexCache = null;
7121
+ INDEX_RECHECK_MS = 1e3;
7122
+ dirMtime = (p) => {
7123
+ try {
7124
+ return statSync5(p).mtimeMs;
7125
+ } catch {
7126
+ return -1;
7127
+ }
7128
+ };
7099
7129
  readScope = null;
7100
7130
  readScopeDepth = 0;
7101
7131
  derivedScope = null;
@@ -8196,15 +8226,36 @@ var init_claude2 = __esm({
8196
8226
  listSessions(opts = {}) {
8197
8227
  const cutoff = opts.sinceMs ?? 0;
8198
8228
  const filter = opts.projectFilter?.toLowerCase();
8199
- const seen = /* @__PURE__ */ new Set();
8200
- const out = [];
8229
+ const byId = /* @__PURE__ */ new Map();
8201
8230
  for (const root of claudeProjectDirs()) {
8202
- this.collectSessionsFromRoot(root, cutoff, filter, seen, out, opts.previews !== false);
8231
+ this.collectSessionsFromRoot(root, filter, byId, opts.previews !== false);
8203
8232
  }
8233
+ const out = [...byId.values()].filter((r) => r.mtime >= cutoff);
8204
8234
  out.sort((a, b) => b.mtime - a.mtime);
8205
8235
  return opts.limit ? out.slice(0, opts.limit) : out;
8206
8236
  }
8207
- collectSessionsFromRoot(root, cutoff, filter, seen, out, previews) {
8237
+ /**
8238
+ * Add this root's sessions to `byId`. A session resumed under a second
8239
+ * profile has one file in each home under the same id. It is ONE session: the
8240
+ * first home keeps its path, and its mtime is the newest of all copies.
8241
+ *
8242
+ * Each copy was listed as its own ref before. Session 8da8d72b had a primary
8243
+ * copy frozen at 05:55 and a second-home copy growing until 12:00; the walk
8244
+ * handled both refs under one ledger row with two different mtimes, and the
8245
+ * row that shipped nothing stamped the other copy's mtime.
8246
+ */
8247
+ collectSessionsFromRoot(root, filter, byId, previews) {
8248
+ const add = (ref) => {
8249
+ const prior = byId.get(ref.rawId);
8250
+ if (!prior) {
8251
+ byId.set(ref.rawId, ref);
8252
+ return;
8253
+ }
8254
+ if (ref.mtime > prior.mtime) {
8255
+ prior.mtime = ref.mtime;
8256
+ prior.modified = ref.modified;
8257
+ }
8258
+ };
8208
8259
  if (!existsSync10(root))
8209
8260
  return;
8210
8261
  for (const proj of readdirSync7(root, { withFileTypes: true })) {
@@ -8215,6 +8266,7 @@ var init_claude2 = __esm({
8215
8266
  const projPath = join10(root, proj.name);
8216
8267
  const projectPath = resolveProjectDirName(proj.name);
8217
8268
  const indexPath = join10(projPath, "sessions-index.json");
8269
+ const indexed = /* @__PURE__ */ new Set();
8218
8270
  if (existsSync10(indexPath)) {
8219
8271
  try {
8220
8272
  const indexData = JSON.parse(readFileSync5(indexPath, "utf-8"));
@@ -8222,10 +8274,8 @@ var init_claude2 = __esm({
8222
8274
  for (const entry of entries) {
8223
8275
  if (!entry.fullPath || !existsSync10(entry.fullPath))
8224
8276
  continue;
8225
- if ((entry.fileMtime ?? 0) < cutoff)
8226
- continue;
8227
- seen.add(entry.sessionId);
8228
- out.push({
8277
+ indexed.add(entry.sessionId);
8278
+ add({
8229
8279
  toolId: "claude",
8230
8280
  rawId: entry.sessionId,
8231
8281
  prefixedId: entry.sessionId,
@@ -8252,7 +8302,7 @@ var init_claude2 = __esm({
8252
8302
  if (!f.endsWith(".jsonl") || f === "sessions-index.json")
8253
8303
  continue;
8254
8304
  const sessionId = basename8(f, ".jsonl");
8255
- if (seen.has(sessionId))
8305
+ if (indexed.has(sessionId))
8256
8306
  continue;
8257
8307
  const fullPath = join10(projPath, f);
8258
8308
  let stat3;
@@ -8261,10 +8311,13 @@ var init_claude2 = __esm({
8261
8311
  } catch {
8262
8312
  continue;
8263
8313
  }
8264
- if (stat3.mtimeMs < cutoff)
8314
+ const prior = byId.get(sessionId);
8315
+ if (prior) {
8316
+ add({ ...prior, mtime: stat3.mtimeMs, modified: stat3.mtime.toISOString() });
8265
8317
  continue;
8318
+ }
8266
8319
  const firstPrompt = previews ? extractFirstUserPromptSync(fullPath, { maxLength: 200 }) : "";
8267
- out.push({
8320
+ add({
8268
8321
  toolId: "claude",
8269
8322
  rawId: sessionId,
8270
8323
  prefixedId: sessionId,
@@ -11792,7 +11845,21 @@ function reportWalkProgress(p) {
11792
11845
  lastProgressWrite = now;
11793
11846
  updateCollectorHealth({ progress: p });
11794
11847
  }
11795
- var healthPath, PROGRESS_WRITE_INTERVAL_MS, lastProgressWrite, STALE_AFTER_MS;
11848
+ function crashesAtBoot(prior, procStart) {
11849
+ const history = (prior?.crashes ?? []).filter((t) => typeof t === "number");
11850
+ if (!prior)
11851
+ return history;
11852
+ const leftOnPurpose = typeof prior.cleanExitAt === "number" && prior.cleanExitAt >= prior.startedAt;
11853
+ return leftOnPurpose ? history : [...history, procStart];
11854
+ }
11855
+ function recentCrashes(crashes, now = Date.now()) {
11856
+ const cutoff = now - 36e5;
11857
+ return [...new Set(crashes.filter((t) => t > cutoff))].sort((a, b) => a - b).slice(-20);
11858
+ }
11859
+ function markCleanExit(now = Date.now()) {
11860
+ updateCollectorHealth({ cleanExitAt: now });
11861
+ }
11862
+ var healthPath, PROGRESS_WRITE_INTERVAL_MS, lastProgressWrite, STALE_AFTER_MS, CRASHLOOP_RESTARTS;
11796
11863
  var init_collector_health = __esm({
11797
11864
  "../engine/dist/core/collector-health.js"() {
11798
11865
  "use strict";
@@ -11801,6 +11868,7 @@ var init_collector_health = __esm({
11801
11868
  PROGRESS_WRITE_INTERVAL_MS = 2e3;
11802
11869
  lastProgressWrite = 0;
11803
11870
  STALE_AFTER_MS = 30 * 6e4;
11871
+ CRASHLOOP_RESTARTS = 3;
11804
11872
  }
11805
11873
  });
11806
11874
 
@@ -18119,7 +18187,7 @@ var init_http = __esm({
18119
18187
  CLI_VERSION = JSON.parse(readFileSync32(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
18120
18188
  } catch {
18121
18189
  try {
18122
- if (true) CLI_VERSION = "0.7.7";
18190
+ if (true) CLI_VERSION = "0.7.9";
18123
18191
  } catch {
18124
18192
  }
18125
18193
  }
@@ -18614,7 +18682,7 @@ var init_telemetry = __esm({
18614
18682
  init_sync_client();
18615
18683
  cliVersion = () => {
18616
18684
  try {
18617
- return true ? "0.7.7" : "dev";
18685
+ return true ? "0.7.9" : "dev";
18618
18686
  } catch {
18619
18687
  return "dev";
18620
18688
  }
@@ -20704,7 +20772,7 @@ var init_sync_client = __esm({
20704
20772
  ownVersion = JSON.parse(readFileSync35(join42(dir, "../package.json"), "utf-8")).version || ownVersion;
20705
20773
  } catch {
20706
20774
  try {
20707
- if (true) ownVersion = "0.7.7";
20775
+ if (true) ownVersion = "0.7.9";
20708
20776
  } catch {
20709
20777
  }
20710
20778
  }
@@ -23518,6 +23586,7 @@ var init_service_installer = __esm({
23518
23586
  // auto-indexer/indexer.ts
23519
23587
  init_backends();
23520
23588
  init_paths();
23589
+ init_tool_paths();
23521
23590
  init_collector_health();
23522
23591
  init_settings();
23523
23592
  init_sync_client();
@@ -23840,7 +23909,7 @@ try {
23840
23909
  ownVersion2 = JSON.parse(readFileSync39(join48(dir, "../package.json"), "utf-8")).version || ownVersion2;
23841
23910
  } catch {
23842
23911
  try {
23843
- if (true) ownVersion2 = "0.7.7";
23912
+ if (true) ownVersion2 = "0.7.9";
23844
23913
  } catch {
23845
23914
  }
23846
23915
  }
@@ -24225,9 +24294,8 @@ async function runCollectorMigration(opts) {
24225
24294
  init_sync_ledger();
24226
24295
  init_collector_health();
24227
24296
  import { existsSync as existsSync50, readFileSync as readFileSync45, statSync as statSync45 } from "fs";
24228
- var CLI_VERSION3 = true ? "0.7.7" : "0.0.0";
24297
+ var CLI_VERSION3 = true ? "0.7.9" : "0.0.0";
24229
24298
  var DEBOUNCE_MS = 5e3;
24230
- var CLAUDE_DIR = claudeBackend.projectsDir();
24231
24299
  var PLANS_DIR = claudeBackend.plansDir();
24232
24300
  var TASKS_DIR = claudeBackend.tasksDir();
24233
24301
  var HISTORY_PATH = claudeBackend.historyFile();
@@ -24327,7 +24395,8 @@ function watchWithFallback(name, pattern, t) {
24327
24395
  return w;
24328
24396
  }
24329
24397
  var WATCH_WINDOW_MS = Math.max(1, Number(process.env.CHAT_RECALL_WATCH_WINDOW_DAYS) || 14) * 24 * 60 * 60 * 1e3;
24330
- var sessionWatcher = watchWithFallback("sessions", `${CLAUDE_DIR}/**/*.jsonl`, {
24398
+ var CLAUDE_SESSION_ROOTS = claudeProjectDirs();
24399
+ var sessionWatcher = watchWithFallback("sessions", CLAUDE_SESSION_ROOTS.map((root) => `${root}/**/*.jsonl`), {
24331
24400
  ignored: (p, stats) => {
24332
24401
  if (/agent-/.test(p) || /^\./.test(basename25(p))) return true;
24333
24402
  const st = stats ?? statSync45(p, { throwIfNoEntry: false });
@@ -24377,7 +24446,7 @@ var opencodeWatcher = chokidar.watch(
24377
24446
  ...POLL(1e4)
24378
24447
  }
24379
24448
  );
24380
- var agentMemoryWatcher = chokidar.watch(`${CLAUDE_DIR}/*/memory/*.md`, {
24449
+ var agentMemoryWatcher = chokidar.watch(CLAUDE_SESSION_ROOTS.map((root) => `${root}/*/memory/*.md`), {
24381
24450
  persistent: true,
24382
24451
  ignoreInitial: true,
24383
24452
  awaitWriteFinish: { stabilityThreshold: 1e3, pollInterval: 100 },
@@ -24425,14 +24494,14 @@ for (const [name, watcher] of Object.entries(watchers)) {
24425
24494
  });
24426
24495
  }
24427
24496
  daemonLog.info(`chat-recall ship daemon started (thin collector \u2014 no local index)`);
24428
- daemonLog.info(` Watching sessions: ${CLAUDE_DIR}`);
24497
+ daemonLog.info(` Watching sessions: ${CLAUDE_SESSION_ROOTS.join(", ")}`);
24429
24498
  daemonLog.info(` Watching plans: ${PLANS_DIR}`);
24430
24499
  daemonLog.info(` Watching tasks: ${TASKS_DIR}`);
24431
24500
  daemonLog.info(` Watching history: ${HISTORY_PATH}`);
24432
24501
  daemonLog.info(` Watching diary: ${DIARY_DIR}`);
24433
24502
  daemonLog.info(` Watching codex: ${CODEX_SESSIONS_DIR}`);
24434
24503
  daemonLog.info(` Watching opencode: ${OPENCODE_DB_DIR}`);
24435
- daemonLog.info(` Watching agent memory: ${CLAUDE_DIR}/*/memory/`);
24504
+ daemonLog.info(` Watching agent memory: ${CLAUDE_SESSION_ROOTS.map((root) => `${root}/*/memory/`).join(", ")}`);
24436
24505
  daemonLog.info(` Resume-guard: ${CURRENT_RESUME_PATH}`);
24437
24506
  daemonLog.info(` Debounce: ${DEBOUNCE_MS}ms \xB7 ships via syncIncremental() to the configured server`);
24438
24507
  daemonLog.info(` Ready for changes...`);
@@ -24479,20 +24548,16 @@ if (BOOT_CODE) {
24479
24548
  }
24480
24549
  var PROC_START = Date.now();
24481
24550
  var targetHealth = {};
24482
- function recentStarts() {
24483
- const prior = readCollectorHealth();
24484
- const cutoff = Date.now() - 36e5;
24485
- return [...prior?.starts ?? [], PROC_START].filter((t) => typeof t === "number" && t > cutoff).filter((t, i, a) => a.indexOf(t) === i).sort((a, b) => a - b).slice(-20);
24486
- }
24551
+ var BOOT_CRASHES = crashesAtBoot(readCollectorHealth(), PROC_START);
24487
24552
  function publishHealth() {
24488
- const starts = recentStarts();
24553
+ const crashes = recentCrashes(BOOT_CRASHES);
24489
24554
  updateCollectorHealth(Object.assign({
24490
24555
  v: 1,
24491
24556
  updatedAt: Date.now(),
24492
24557
  startedAt: PROC_START,
24493
- restartsLastHour: starts.length,
24558
+ restartsLastHour: crashes.length,
24494
24559
  targets: targetHealth
24495
- }, { starts }));
24560
+ }, { crashes, cleanExitAt: void 0, starts: void 0 }));
24496
24561
  }
24497
24562
  var SHADOW_PRUNE_MS = Number(process.env.CHAT_RECALL_SHADOW_PRUNE_MS) || 6 * 60 * 60 * 1e3;
24498
24563
  async function shadowPruneTick() {
@@ -24546,9 +24611,9 @@ function noteSyncOutcome(server, ok, err, accepted = 0) {
24546
24611
  }
24547
24612
  publishHealth();
24548
24613
  {
24549
- const n = recentStarts().length;
24550
- if (n >= 3) {
24551
- daemonLog.error(`WARNING: this collector has restarted ${n} times in the last hour. Sync is probably falling behind. Health: ${collectorHealthPath()}`);
24614
+ const n = recentCrashes(BOOT_CRASHES).length;
24615
+ if (n >= CRASHLOOP_RESTARTS) {
24616
+ daemonLog.error(`WARNING: this collector crashed ${n} times in the last hour. Sync is probably falling behind. Health: ${collectorHealthPath()}`);
24552
24617
  }
24553
24618
  }
24554
24619
  setInterval(() => {
@@ -24802,11 +24867,12 @@ if (process.platform === "win32") {
24802
24867
  process.on("SIGBREAK", () => shutdown("SIGBREAK"));
24803
24868
  process.on("SIGHUP", () => shutdown("SIGHUP"));
24804
24869
  }
24805
- process.on("exit", () => {
24870
+ process.on("exit", (code) => {
24806
24871
  try {
24807
24872
  flushLedger();
24808
24873
  } catch {
24809
24874
  }
24875
+ if (code === 0 || code === EXIT_CODE_CODE_CHANGED) markCleanExit();
24810
24876
  });
24811
24877
  export {
24812
24878
  _targetHealth,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat-recall",
3
- "version": "0.7.7",
3
+ "version": "0.7.9",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/munhq/chat-recall.git"
@@ -40,7 +40,9 @@ description: >-
40
40
  Every tool here takes a NAMED `session_id` (not a positional argument).
41
41
 
42
42
  - `mcp__chat-recall__recall_show` — read the session slice (`around_line`,
43
- `from_end`). Each message comes back in full. It returns **10 messages
43
+ `from_end`). Each message comes back in full, with its tool calls and
44
+ results. A body over 2000 characters shows its ends and the `expand_line`
45
+ that returns it whole. It returns **10 messages
44
46
  by default**: raise `max_messages` before telling the user what a session
45
47
  contains, or you will describe a tenth of it as the whole.
46
48
  - `mcp__chat-recall__recall_summary` — AI summary + outcome.
@@ -47,7 +47,9 @@ Once you have a session id:
47
47
  - `mcp__chat-recall__recall_context` — structured context (requests, decisions,
48
48
  files, tools).
49
49
  - `mcp__chat-recall__recall_show` — raw slice; `from_end: N` for the last N
50
- messages. Each message comes back in full, code and commands included. It
50
+ messages. Each message comes back in full, code and commands included, with
51
+ its tool results. A body over 2000 characters shows its ends; pass
52
+ `expand_line` to get it whole. It
51
53
  returns 10 messages unless you raise `max_messages`, so don't read a partial
52
54
  slice as the whole session.
53
55
  - `mcp__chat-recall__recall_summary` — AI summary + outcome (did the work land?).