chat-recall 0.7.7 → 0.7.8

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,
@@ -18119,7 +18172,7 @@ var init_http = __esm({
18119
18172
  CLI_VERSION = JSON.parse(readFileSync32(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
18120
18173
  } catch {
18121
18174
  try {
18122
- if (true) CLI_VERSION = "0.7.7";
18175
+ if (true) CLI_VERSION = "0.7.8";
18123
18176
  } catch {
18124
18177
  }
18125
18178
  }
@@ -18614,7 +18667,7 @@ var init_telemetry = __esm({
18614
18667
  init_sync_client();
18615
18668
  cliVersion = () => {
18616
18669
  try {
18617
- return true ? "0.7.7" : "dev";
18670
+ return true ? "0.7.8" : "dev";
18618
18671
  } catch {
18619
18672
  return "dev";
18620
18673
  }
@@ -20704,7 +20757,7 @@ var init_sync_client = __esm({
20704
20757
  ownVersion = JSON.parse(readFileSync35(join42(dir, "../package.json"), "utf-8")).version || ownVersion;
20705
20758
  } catch {
20706
20759
  try {
20707
- if (true) ownVersion = "0.7.7";
20760
+ if (true) ownVersion = "0.7.8";
20708
20761
  } catch {
20709
20762
  }
20710
20763
  }
@@ -23518,6 +23571,7 @@ var init_service_installer = __esm({
23518
23571
  // auto-indexer/indexer.ts
23519
23572
  init_backends();
23520
23573
  init_paths();
23574
+ init_tool_paths();
23521
23575
  init_collector_health();
23522
23576
  init_settings();
23523
23577
  init_sync_client();
@@ -23840,7 +23894,7 @@ try {
23840
23894
  ownVersion2 = JSON.parse(readFileSync39(join48(dir, "../package.json"), "utf-8")).version || ownVersion2;
23841
23895
  } catch {
23842
23896
  try {
23843
- if (true) ownVersion2 = "0.7.7";
23897
+ if (true) ownVersion2 = "0.7.8";
23844
23898
  } catch {
23845
23899
  }
23846
23900
  }
@@ -24225,9 +24279,8 @@ async function runCollectorMigration(opts) {
24225
24279
  init_sync_ledger();
24226
24280
  init_collector_health();
24227
24281
  import { existsSync as existsSync50, readFileSync as readFileSync45, statSync as statSync45 } from "fs";
24228
- var CLI_VERSION3 = true ? "0.7.7" : "0.0.0";
24282
+ var CLI_VERSION3 = true ? "0.7.8" : "0.0.0";
24229
24283
  var DEBOUNCE_MS = 5e3;
24230
- var CLAUDE_DIR = claudeBackend.projectsDir();
24231
24284
  var PLANS_DIR = claudeBackend.plansDir();
24232
24285
  var TASKS_DIR = claudeBackend.tasksDir();
24233
24286
  var HISTORY_PATH = claudeBackend.historyFile();
@@ -24327,7 +24380,8 @@ function watchWithFallback(name, pattern, t) {
24327
24380
  return w;
24328
24381
  }
24329
24382
  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`, {
24383
+ var CLAUDE_SESSION_ROOTS = claudeProjectDirs();
24384
+ var sessionWatcher = watchWithFallback("sessions", CLAUDE_SESSION_ROOTS.map((root) => `${root}/**/*.jsonl`), {
24331
24385
  ignored: (p, stats) => {
24332
24386
  if (/agent-/.test(p) || /^\./.test(basename25(p))) return true;
24333
24387
  const st = stats ?? statSync45(p, { throwIfNoEntry: false });
@@ -24377,7 +24431,7 @@ var opencodeWatcher = chokidar.watch(
24377
24431
  ...POLL(1e4)
24378
24432
  }
24379
24433
  );
24380
- var agentMemoryWatcher = chokidar.watch(`${CLAUDE_DIR}/*/memory/*.md`, {
24434
+ var agentMemoryWatcher = chokidar.watch(CLAUDE_SESSION_ROOTS.map((root) => `${root}/*/memory/*.md`), {
24381
24435
  persistent: true,
24382
24436
  ignoreInitial: true,
24383
24437
  awaitWriteFinish: { stabilityThreshold: 1e3, pollInterval: 100 },
@@ -24425,14 +24479,14 @@ for (const [name, watcher] of Object.entries(watchers)) {
24425
24479
  });
24426
24480
  }
24427
24481
  daemonLog.info(`chat-recall ship daemon started (thin collector \u2014 no local index)`);
24428
- daemonLog.info(` Watching sessions: ${CLAUDE_DIR}`);
24482
+ daemonLog.info(` Watching sessions: ${CLAUDE_SESSION_ROOTS.join(", ")}`);
24429
24483
  daemonLog.info(` Watching plans: ${PLANS_DIR}`);
24430
24484
  daemonLog.info(` Watching tasks: ${TASKS_DIR}`);
24431
24485
  daemonLog.info(` Watching history: ${HISTORY_PATH}`);
24432
24486
  daemonLog.info(` Watching diary: ${DIARY_DIR}`);
24433
24487
  daemonLog.info(` Watching codex: ${CODEX_SESSIONS_DIR}`);
24434
24488
  daemonLog.info(` Watching opencode: ${OPENCODE_DB_DIR}`);
24435
- daemonLog.info(` Watching agent memory: ${CLAUDE_DIR}/*/memory/`);
24489
+ daemonLog.info(` Watching agent memory: ${CLAUDE_SESSION_ROOTS.map((root) => `${root}/*/memory/`).join(", ")}`);
24436
24490
  daemonLog.info(` Resume-guard: ${CURRENT_RESUME_PATH}`);
24437
24491
  daemonLog.info(` Debounce: ${DEBOUNCE_MS}ms \xB7 ships via syncIncremental() to the configured server`);
24438
24492
  daemonLog.info(` Ready for changes...`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat-recall",
3
- "version": "0.7.7",
3
+ "version": "0.7.8",
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?).