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.
package/dist/mcp.js CHANGED
@@ -5402,8 +5402,8 @@ function writeSecrets(secrets) {
5402
5402
  _secretsCache = null;
5403
5403
  const p = secretsPath();
5404
5404
  mkdirSync3(dirname4(p), { recursive: true });
5405
- const body = "# chat-recall secrets \u2014 NOT committed/synced. Mode 0600.\n" + Object.entries(secrets).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
5406
- writeFileSync(p, body);
5405
+ const body2 = "# chat-recall secrets \u2014 NOT committed/synced. Mode 0600.\n" + Object.entries(secrets).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
5406
+ writeFileSync(p, body2);
5407
5407
  try {
5408
5408
  chmodSync2(p, 384);
5409
5409
  } catch {
@@ -6462,11 +6462,11 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
6462
6462
  }
6463
6463
  const toolResult = obj.toolUseResult;
6464
6464
  if (toolResult && typeof toolResult === "object" && toolCount < maxMessages) {
6465
- const resultText = toolResult.result;
6465
+ const resultText2 = toolResult.result;
6466
6466
  const query = toolResult.query;
6467
- if (resultText && typeof resultText === "string" && isValuableToolResult(resultText, "WebSearch")) {
6467
+ if (resultText2 && typeof resultText2 === "string" && isValuableToolResult(resultText2, "WebSearch")) {
6468
6468
  const searchText = query ? `Search: ${query}
6469
- ${resultText}` : resultText;
6469
+ ${resultText2}` : resultText2;
6470
6470
  content.toolResults.push({
6471
6471
  text: searchText.slice(0, 2e3),
6472
6472
  lineNumber: lineNum,
@@ -7391,9 +7391,29 @@ function findSessionFile(sessionId) {
7391
7391
  function invalidateSessionFileIndex() {
7392
7392
  indexCache = null;
7393
7393
  }
7394
+ function indexStillValid(cache4) {
7395
+ const roots = claudeProjectDirs();
7396
+ if (roots.length !== cache4.roots.length || roots.some((r, i) => r !== cache4.roots[i]))
7397
+ return false;
7398
+ for (const [dir, mtime] of cache4.dirs) {
7399
+ if (dirMtime(dir) !== mtime)
7400
+ return false;
7401
+ }
7402
+ return true;
7403
+ }
7394
7404
  function sessionFileIndex() {
7395
- if (indexCache)
7396
- return indexCache;
7405
+ if (indexCache) {
7406
+ const now = Date.now();
7407
+ if (now - indexCache.checkedAt < INDEX_RECHECK_MS)
7408
+ return indexCache.byId;
7409
+ if (indexStillValid(indexCache)) {
7410
+ indexCache.checkedAt = now;
7411
+ return indexCache.byId;
7412
+ }
7413
+ indexCache = null;
7414
+ }
7415
+ const dirs = /* @__PURE__ */ new Map();
7416
+ const roots = claudeProjectDirs();
7397
7417
  const byId = /* @__PURE__ */ new Map();
7398
7418
  const seenRoots = /* @__PURE__ */ new Set();
7399
7419
  const projectPaths = /* @__PURE__ */ new Map();
@@ -7405,10 +7425,11 @@ function sessionFileIndex() {
7405
7425
  }
7406
7426
  return p;
7407
7427
  };
7408
- for (const root of claudeProjectDirs()) {
7428
+ for (const root of roots) {
7409
7429
  if (seenRoots.has(root) || !existsSync8(root))
7410
7430
  continue;
7411
7431
  seenRoots.add(root);
7432
+ dirs.set(root, dirMtime(root));
7412
7433
  let entries;
7413
7434
  try {
7414
7435
  entries = readdirSync8(root, { withFileTypes: true });
@@ -7419,6 +7440,7 @@ function sessionFileIndex() {
7419
7440
  if (!entry.isDirectory())
7420
7441
  continue;
7421
7442
  const dir = join12(root, entry.name);
7443
+ dirs.set(dir, dirMtime(dir));
7422
7444
  let names;
7423
7445
  try {
7424
7446
  names = readdirSync8(dir);
@@ -7443,7 +7465,7 @@ function sessionFileIndex() {
7443
7465
  }
7444
7466
  }
7445
7467
  if (scanScopeDepth > 0)
7446
- indexCache = byId;
7468
+ indexCache = { byId, dirs, roots, checkedAt: Date.now() };
7447
7469
  return byId;
7448
7470
  }
7449
7471
  function findSessionFiles(sessionId) {
@@ -7601,7 +7623,7 @@ function findCodexSessionFileIn(root, sessionId) {
7601
7623
  }
7602
7624
  return scanDir(root);
7603
7625
  }
7604
- var scanScopeDepth, indexCache, readScope, readScopeDepth, derivedScope, geminiCache, MAX_EVENT_LINE_BYTES;
7626
+ var scanScopeDepth, indexCache, INDEX_RECHECK_MS, dirMtime, readScope, readScopeDepth, derivedScope, geminiCache, MAX_EVENT_LINE_BYTES;
7605
7627
  var init_live_session_scan = __esm({
7606
7628
  "../engine/dist/core/live-session-scan.js"() {
7607
7629
  "use strict";
@@ -7613,6 +7635,14 @@ var init_live_session_scan = __esm({
7613
7635
  init_project_dir_name();
7614
7636
  scanScopeDepth = 0;
7615
7637
  indexCache = null;
7638
+ INDEX_RECHECK_MS = 1e3;
7639
+ dirMtime = (p) => {
7640
+ try {
7641
+ return statSync8(p).mtimeMs;
7642
+ } catch {
7643
+ return -1;
7644
+ }
7645
+ };
7616
7646
  readScope = null;
7617
7647
  readScopeDepth = 0;
7618
7648
  derivedScope = null;
@@ -7828,7 +7858,7 @@ function gitLogRepo(repo, startIso, endIso) {
7828
7858
  const authorIso = seg.slice(fieldPositions[1] + 1, fieldPositions[2]);
7829
7859
  const authorName = seg.slice(fieldPositions[2] + 1, fieldPositions[3]);
7830
7860
  const subject = seg.slice(fieldPositions[3] + 1, fieldPositions[4]);
7831
- let body = seg.slice(fieldPositions[4] + 1);
7861
+ let body2 = seg.slice(fieldPositions[4] + 1);
7832
7862
  current = {
7833
7863
  sha,
7834
7864
  shortSha,
@@ -7842,11 +7872,11 @@ function gitLogRepo(repo, startIso, endIso) {
7842
7872
  };
7843
7873
  const recovered = [];
7844
7874
  let m;
7845
- while (m = TRAILING_NUMSTAT.exec(body)) {
7875
+ while (m = TRAILING_NUMSTAT.exec(body2)) {
7846
7876
  recovered.unshift(m[1]);
7847
- body = body.slice(0, body.length - m[0].length);
7877
+ body2 = body2.slice(0, body2.length - m[0].length);
7848
7878
  }
7849
- current.body = body.trim();
7879
+ current.body = body2.trim();
7850
7880
  for (const line of recovered)
7851
7881
  recordNumstat(line);
7852
7882
  } else if (NUMSTAT.test(seg)) {
@@ -8713,15 +8743,36 @@ var init_claude2 = __esm({
8713
8743
  listSessions(opts = {}) {
8714
8744
  const cutoff = opts.sinceMs ?? 0;
8715
8745
  const filter = opts.projectFilter?.toLowerCase();
8716
- const seen = /* @__PURE__ */ new Set();
8717
- const out = [];
8746
+ const byId = /* @__PURE__ */ new Map();
8718
8747
  for (const root of claudeProjectDirs()) {
8719
- this.collectSessionsFromRoot(root, cutoff, filter, seen, out, opts.previews !== false);
8748
+ this.collectSessionsFromRoot(root, filter, byId, opts.previews !== false);
8720
8749
  }
8750
+ const out = [...byId.values()].filter((r) => r.mtime >= cutoff);
8721
8751
  out.sort((a, b) => b.mtime - a.mtime);
8722
8752
  return opts.limit ? out.slice(0, opts.limit) : out;
8723
8753
  }
8724
- collectSessionsFromRoot(root, cutoff, filter, seen, out, previews) {
8754
+ /**
8755
+ * Add this root's sessions to `byId`. A session resumed under a second
8756
+ * profile has one file in each home under the same id. It is ONE session: the
8757
+ * first home keeps its path, and its mtime is the newest of all copies.
8758
+ *
8759
+ * Each copy was listed as its own ref before. Session 8da8d72b had a primary
8760
+ * copy frozen at 05:55 and a second-home copy growing until 12:00; the walk
8761
+ * handled both refs under one ledger row with two different mtimes, and the
8762
+ * row that shipped nothing stamped the other copy's mtime.
8763
+ */
8764
+ collectSessionsFromRoot(root, filter, byId, previews) {
8765
+ const add = (ref) => {
8766
+ const prior = byId.get(ref.rawId);
8767
+ if (!prior) {
8768
+ byId.set(ref.rawId, ref);
8769
+ return;
8770
+ }
8771
+ if (ref.mtime > prior.mtime) {
8772
+ prior.mtime = ref.mtime;
8773
+ prior.modified = ref.modified;
8774
+ }
8775
+ };
8725
8776
  if (!existsSync11(root))
8726
8777
  return;
8727
8778
  for (const proj of readdirSync9(root, { withFileTypes: true })) {
@@ -8732,6 +8783,7 @@ var init_claude2 = __esm({
8732
8783
  const projPath = join13(root, proj.name);
8733
8784
  const projectPath = resolveProjectDirName(proj.name);
8734
8785
  const indexPath = join13(projPath, "sessions-index.json");
8786
+ const indexed = /* @__PURE__ */ new Set();
8735
8787
  if (existsSync11(indexPath)) {
8736
8788
  try {
8737
8789
  const indexData = JSON.parse(readFileSync7(indexPath, "utf-8"));
@@ -8739,10 +8791,8 @@ var init_claude2 = __esm({
8739
8791
  for (const entry of entries) {
8740
8792
  if (!entry.fullPath || !existsSync11(entry.fullPath))
8741
8793
  continue;
8742
- if ((entry.fileMtime ?? 0) < cutoff)
8743
- continue;
8744
- seen.add(entry.sessionId);
8745
- out.push({
8794
+ indexed.add(entry.sessionId);
8795
+ add({
8746
8796
  toolId: "claude",
8747
8797
  rawId: entry.sessionId,
8748
8798
  prefixedId: entry.sessionId,
@@ -8769,7 +8819,7 @@ var init_claude2 = __esm({
8769
8819
  if (!f.endsWith(".jsonl") || f === "sessions-index.json")
8770
8820
  continue;
8771
8821
  const sessionId = basename10(f, ".jsonl");
8772
- if (seen.has(sessionId))
8822
+ if (indexed.has(sessionId))
8773
8823
  continue;
8774
8824
  const fullPath = join13(projPath, f);
8775
8825
  let stat3;
@@ -8778,10 +8828,13 @@ var init_claude2 = __esm({
8778
8828
  } catch {
8779
8829
  continue;
8780
8830
  }
8781
- if (stat3.mtimeMs < cutoff)
8831
+ const prior = byId.get(sessionId);
8832
+ if (prior) {
8833
+ add({ ...prior, mtime: stat3.mtimeMs, modified: stat3.mtime.toISOString() });
8782
8834
  continue;
8835
+ }
8783
8836
  const firstPrompt = previews ? extractFirstUserPromptSync(fullPath, { maxLength: 200 }) : "";
8784
- out.push({
8837
+ add({
8785
8838
  toolId: "claude",
8786
8839
  rawId: sessionId,
8787
8840
  prefixedId: sessionId,
@@ -8911,20 +8964,20 @@ var init_claude2 = __esm({
8911
8964
  text += (text ? "\n" : "") + it.text;
8912
8965
  } else if (it.type === "tool_result") {
8913
8966
  const id = typeof it.tool_use_id === "string" ? it.tool_use_id : "";
8914
- let body = "";
8967
+ let body2 = "";
8915
8968
  const c = it.content;
8916
8969
  if (typeof c === "string")
8917
- body = c;
8970
+ body2 = c;
8918
8971
  else if (Array.isArray(c)) {
8919
8972
  for (const sub of c) {
8920
8973
  if (sub && typeof sub === "object" && sub.type === "text") {
8921
8974
  const t = sub.text;
8922
8975
  if (typeof t === "string")
8923
- body += t;
8976
+ body2 += t;
8924
8977
  }
8925
8978
  }
8926
8979
  }
8927
- toolResults.push({ id, body, isError: it.is_error === true });
8980
+ toolResults.push({ id, body: body2, isError: it.is_error === true });
8928
8981
  }
8929
8982
  }
8930
8983
  }
@@ -9556,7 +9609,7 @@ var init_opencode = __esm({
9556
9609
  });
9557
9610
  const out = d?.state?.output;
9558
9611
  if (out !== void 0 && out !== null) {
9559
- const body = typeof out === "string" ? out : JSON.stringify(out);
9612
+ const body2 = typeof out === "string" ? out : JSON.stringify(out);
9560
9613
  events.push({
9561
9614
  kind: "tool_result",
9562
9615
  ts,
@@ -9564,9 +9617,9 @@ var init_opencode = __esm({
9564
9617
  line: lineNum,
9565
9618
  toolUseId: callId,
9566
9619
  toolName,
9567
- resultBody: body,
9620
+ resultBody: body2,
9568
9621
  resultIsError: d?.state?.status === "error",
9569
- resultBytes: body.length
9622
+ resultBytes: body2.length
9570
9623
  });
9571
9624
  }
9572
9625
  }
@@ -10270,13 +10323,13 @@ var init_codex2 = __esm({
10270
10323
  } else if (obj.type === "response_item" && payload.type === "function_call_output") {
10271
10324
  const callId = String(payload.call_id || "");
10272
10325
  const out = payload.output;
10273
- let body = "";
10326
+ let body2 = "";
10274
10327
  let isError = false;
10275
10328
  let exitCode;
10276
10329
  if (typeof out === "string")
10277
- body = out;
10330
+ body2 = out;
10278
10331
  else if (out && typeof out === "object") {
10279
- body = String(out.content ?? out.output ?? out.stdout ?? out.text ?? "");
10332
+ body2 = String(out.content ?? out.output ?? out.stdout ?? out.text ?? "");
10280
10333
  if (typeof out.exit_code === "number") {
10281
10334
  exitCode = out.exit_code;
10282
10335
  isError = exitCode !== 0;
@@ -10288,10 +10341,10 @@ var init_codex2 = __esm({
10288
10341
  tsIso,
10289
10342
  line: lineNum,
10290
10343
  toolUseId: callId,
10291
- resultBody: body,
10344
+ resultBody: body2,
10292
10345
  resultIsError: isError,
10293
10346
  resultExitCode: exitCode,
10294
- resultBytes: body.length
10347
+ resultBytes: body2.length
10295
10348
  });
10296
10349
  }
10297
10350
  }
@@ -11313,15 +11366,15 @@ function messagesToEvents(messages, createdAtMs, updatedAtMs) {
11313
11366
  const p = part;
11314
11367
  if (p.type !== "tool-result")
11315
11368
  continue;
11316
- const body = resultBodyOf(p);
11369
+ const body2 = resultBodyOf(p);
11317
11370
  events.push({
11318
11371
  kind: "tool_result",
11319
11372
  ts: clock,
11320
11373
  line: 0,
11321
11374
  toolUseId: typeof p.toolCallId === "string" ? p.toolCallId : void 0,
11322
- resultBody: body,
11375
+ resultBody: body2,
11323
11376
  resultIsError: isError,
11324
- resultBytes: Buffer.byteLength(body, "utf8")
11377
+ resultBytes: Buffer.byteLength(body2, "utf8")
11325
11378
  });
11326
11379
  }
11327
11380
  }
@@ -12184,11 +12237,11 @@ var init_cursor = __esm({
12184
12237
  const events = readComposerEvents(r.composer);
12185
12238
  if (events.length === 0)
12186
12239
  return null;
12187
- const body = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
12240
+ const body2 = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
12188
12241
  return {
12189
12242
  tool: "cursor",
12190
12243
  mtime: statSync15(r.composer.globalDbPath).mtimeMs,
12191
- files: [{ name: `${r.composer.composerId}.jsonl`, bytes: Buffer.from(body, "utf8") }]
12244
+ files: [{ name: `${r.composer.composerId}.jsonl`, bytes: Buffer.from(body2, "utf8") }]
12192
12245
  };
12193
12246
  } catch {
12194
12247
  return null;
@@ -13599,11 +13652,11 @@ async function collectCode(opts) {
13599
13652
  for (const block of log22.split("\0END\0")) {
13600
13653
  if (!block.includes("\0"))
13601
13654
  continue;
13602
- const [h, body] = [block.slice(0, block.indexOf("\0")).trim(), block.slice(block.indexOf("\0") + 1)];
13655
+ const [h, body2] = [block.slice(0, block.indexOf("\0")).trim(), block.slice(block.indexOf("\0") + 1)];
13603
13656
  if (!h)
13604
13657
  continue;
13605
13658
  totalCommits++;
13606
- if (AI_RE.test(body)) {
13659
+ if (AI_RE.test(body2)) {
13607
13660
  aiCommits++;
13608
13661
  try {
13609
13662
  const files2 = execSync3(`git -C "${ws}" show --format= --name-only ${h}`, { encoding: "utf8", timeout: 2e4, maxBuffer: 64 * 1024 * 1024, stdio: ["ignore", "pipe", "ignore"] });
@@ -15773,8 +15826,8 @@ function parseFrontmatter(content) {
15773
15826
  return { body: content };
15774
15827
  const header = content.slice(3, close);
15775
15828
  const afterFence = content.indexOf("\n", close + 1);
15776
- const body = afterFence === -1 ? "" : content.slice(afterFence + 1);
15777
- const out = { body };
15829
+ const body2 = afterFence === -1 ? "" : content.slice(afterFence + 1);
15830
+ const out = { body: body2 };
15778
15831
  for (const line of header.split("\n")) {
15779
15832
  const m = line.match(/^\s*([A-Za-z_]+)\s*:\s*(.+?)\s*$/);
15780
15833
  if (!m)
@@ -15866,9 +15919,9 @@ var init_plan_source = __esm({
15866
15919
  const isAgentPlan = planName.includes("-agent-");
15867
15920
  const content = readFileSync20(filePath, "utf-8");
15868
15921
  const fm = parseFrontmatter(content);
15869
- const body = fm.body;
15870
- const firstLine = body.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || planName;
15871
- const projectPath = fm.cwd || this.extractProjectPath(body);
15922
+ const body2 = fm.body;
15923
+ const firstLine = body2.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || planName;
15924
+ const projectPath = fm.cwd || this.extractProjectPath(body2);
15872
15925
  const baseName = planName.replace(/-agent-[a-z0-9]+$/i, "");
15873
15926
  const claudeSessionId = fm.sessionId || (UUID_RE3.test(baseName) ? baseName : void 0);
15874
15927
  yield {
@@ -15878,7 +15931,7 @@ var init_plan_source = __esm({
15878
15931
  projectPath,
15879
15932
  filePath,
15880
15933
  mtime: stat3.mtimeMs,
15881
- contentPreview: body.slice(0, 300),
15934
+ contentPreview: body2.slice(0, 300),
15882
15935
  extra: {
15883
15936
  tool: "claude",
15884
15937
  isAgentPlan,
@@ -15985,8 +16038,8 @@ var init_plan_source = __esm({
15985
16038
  return [];
15986
16039
  const content = readFileSync20(item.filePath, "utf-8");
15987
16040
  const chunks = [];
15988
- const { body } = parseFrontmatter(content);
15989
- const sections = splitByHeaders(body);
16041
+ const { body: body2 } = parseFrontmatter(content);
16042
+ const sections = splitByHeaders(body2);
15990
16043
  for (let i = 0; i < sections.length; i++) {
15991
16044
  const section = sections[i];
15992
16045
  if (!section.text.trim() || section.text.trim().length < 20)
@@ -16619,10 +16672,10 @@ import { homedir as homedir11 } from "os";
16619
16672
  import { join as join34, basename as basename16 } from "path";
16620
16673
  function parseFrontmatter2(content) {
16621
16674
  const fm = {};
16622
- let body = content;
16675
+ let body2 = content;
16623
16676
  const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
16624
16677
  if (m) {
16625
- body = m[2];
16678
+ body2 = m[2];
16626
16679
  for (const line of m[1].split(/\r?\n/)) {
16627
16680
  const t = line.trim();
16628
16681
  if (!t || t.startsWith("#"))
@@ -16639,7 +16692,7 @@ function parseFrontmatter2(content) {
16639
16692
  fm[k] = v;
16640
16693
  }
16641
16694
  }
16642
- return { fm, body };
16695
+ return { fm, body: body2 };
16643
16696
  }
16644
16697
  function projectHashToPath(hashDir) {
16645
16698
  return resolveProjectDirName(hashDir);
@@ -16716,9 +16769,9 @@ var init_agent_memory_source = __esm({
16716
16769
  if (!existsSync29(item.filePath))
16717
16770
  return [];
16718
16771
  const content = readFileSync23(item.filePath, "utf-8");
16719
- const { body } = parseFrontmatter2(content);
16772
+ const { body: body2 } = parseFrontmatter2(content);
16720
16773
  const chunks = [];
16721
- const sections = splitByHeaders(body);
16774
+ const sections = splitByHeaders(body2);
16722
16775
  for (let i = 0; i < sections.length; i++) {
16723
16776
  const section = sections[i];
16724
16777
  if (!section.text.trim() || section.text.trim().length < 20)
@@ -18438,11 +18491,11 @@ function parseCommand(content, fallbackName, format) {
18438
18491
  body: t.prompt || ""
18439
18492
  };
18440
18493
  }
18441
- const { fm, body } = parseFrontmatter4(content);
18494
+ const { fm, body: body2 } = parseFrontmatter4(content);
18442
18495
  return {
18443
18496
  name: fm.name || fallbackName,
18444
18497
  description: fm.description || "",
18445
- body: body.trim()
18498
+ body: body2.trim()
18446
18499
  };
18447
18500
  }
18448
18501
  var MAX_CHUNK_CHARS12, MAX_BODY_CHARS2, SlashCommandsSource;
@@ -18586,12 +18639,12 @@ function parseAgent(content, fallbackName, format) {
18586
18639
  body: t.developer_instructions || t.instructions || ""
18587
18640
  };
18588
18641
  }
18589
- const { fm, body } = parseFrontmatter4(content);
18642
+ const { fm, body: body2 } = parseFrontmatter4(content);
18590
18643
  return {
18591
18644
  name: fm.name || fallbackName,
18592
18645
  description: fm.description || "",
18593
18646
  tools: fm.tools || "",
18594
- body: body.trim()
18647
+ body: body2.trim()
18595
18648
  };
18596
18649
  }
18597
18650
  var MAX_CHUNK_CHARS13, MAX_BODY_CHARS3, SubagentsSource;
@@ -19495,7 +19548,7 @@ var init_http = __esm({
19495
19548
  CLI_VERSION = JSON.parse(readFileSync36(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
19496
19549
  } catch {
19497
19550
  try {
19498
- if (true) CLI_VERSION = "0.7.7";
19551
+ if (true) CLI_VERSION = "0.7.8";
19499
19552
  } catch {
19500
19553
  }
19501
19554
  }
@@ -19994,7 +20047,7 @@ var init_telemetry = __esm({
19994
20047
  init_sync_client();
19995
20048
  cliVersion2 = () => {
19996
20049
  try {
19997
- return true ? "0.7.7" : "dev";
20050
+ return true ? "0.7.8" : "dev";
19998
20051
  } catch {
19999
20052
  return "dev";
20000
20053
  }
@@ -20035,20 +20088,20 @@ async function probeServer(base, opts = {}) {
20035
20088
  if (res.status === 404 || res.status === 410) return { kind: "moved", status: res.status };
20036
20089
  if (res.status >= 500) return { kind: "server_error", status: res.status };
20037
20090
  if (!res.ok) return { kind: "moved", status: res.status };
20038
- let body;
20091
+ let body2;
20039
20092
  try {
20040
- body = await res.text();
20093
+ body2 = await res.text();
20041
20094
  } catch (err) {
20042
20095
  return { kind: "unreachable", error: describeCause(err), reason: classifyUnreachable(err) };
20043
20096
  }
20044
20097
  let parsed;
20045
20098
  try {
20046
- parsed = JSON.parse(body);
20099
+ parsed = JSON.parse(body2);
20047
20100
  } catch {
20048
- return { kind: "not_api", status: res.status, snippet: body.trim().slice(0, 60) };
20101
+ return { kind: "not_api", status: res.status, snippet: body2.trim().slice(0, 60) };
20049
20102
  }
20050
20103
  if (typeof parsed?.apiVersion !== "number") {
20051
- return { kind: "not_api", status: res.status, snippet: body.trim().slice(0, 60) };
20104
+ return { kind: "not_api", status: res.status, snippet: body2.trim().slice(0, 60) };
20052
20105
  }
20053
20106
  return {
20054
20107
  kind: "ok",
@@ -20533,10 +20586,10 @@ async function fetchTenantSecurityConfig(cred) {
20533
20586
  let tenantRules = [];
20534
20587
  let rulePackVersion = null;
20535
20588
  if (rulesRes.ok) {
20536
- const body = await rulesRes.json().catch(() => ({}));
20537
- const enabled = (body.rules || []).filter((r) => r.enabled !== false);
20589
+ const body2 = await rulesRes.json().catch(() => ({}));
20590
+ const enabled = (body2.rules || []).filter((r) => r.enabled !== false);
20538
20591
  tenantRules = enabled;
20539
- const packSpec = body.pack?.rules ? { version: body.pack.version, rules: body.pack.rules.map((r) => ({ name: r.name, regex: r.regex, flags: r.flags, redact: true, source: r.source })) } : { version: body.version, rules: enabled.filter((r) => r.redact === true).map((r) => ({ name: r.name, regex: r.regex, redact: true, source: "tenant" })) };
20592
+ const packSpec = body2.pack?.rules ? { version: body2.pack.version, rules: body2.pack.rules.map((r) => ({ name: r.name, regex: r.regex, flags: r.flags, redact: true, source: r.source })) } : { version: body2.version, rules: enabled.filter((r) => r.redact === true).map((r) => ({ name: r.name, regex: r.regex, redact: true, source: "tenant" })) };
20540
20593
  lastServerPackSpec = { version: packSpec.version || "", rules: packSpec.rules };
20541
20594
  const pack = installServerRulePack(packSpec);
20542
20595
  rulePackVersion = serverRulePackVersion();
@@ -20544,20 +20597,20 @@ async function fetchTenantSecurityConfig(cred) {
20544
20597
  console.error(`[sync] server redaction rule "${r.name}" ignored: ${r.reason}`);
20545
20598
  }
20546
20599
  if (pack.accepted > 0) {
20547
- const rev = body.pack?.revision ? `, rev ${body.pack.revision}` : "";
20600
+ const rev = body2.pack?.revision ? `, rev ${body2.pack.revision}` : "";
20548
20601
  console.error(`[sync] ${pack.accepted} server redaction rule(s) active (pack ${rulePackVersion}${rev})`);
20549
20602
  }
20550
20603
  }
20551
20604
  let verifySecrets = true;
20552
20605
  if (settingsRes?.ok) {
20553
- const body = await settingsRes.json().catch(() => ({}));
20554
- if (typeof body.verifySecrets === "boolean") verifySecrets = body.verifySecrets;
20555
- if (typeof body.telemetry === "boolean") {
20556
- setTelemetryEligible(base, body.telemetry);
20606
+ const body2 = await settingsRes.json().catch(() => ({}));
20607
+ if (typeof body2.verifySecrets === "boolean") verifySecrets = body2.verifySecrets;
20608
+ if (typeof body2.telemetry === "boolean") {
20609
+ setTelemetryEligible(base, body2.telemetry);
20557
20610
  try {
20558
20611
  const prior = readCollectorHealth()?.telemetryEligible ?? {};
20559
20612
  updateCollectorHealth({
20560
- telemetryEligible: { ...prior, [base]: { allowed: body.telemetry, at: Date.now() } }
20613
+ telemetryEligible: { ...prior, [base]: { allowed: body2.telemetry, at: Date.now() } }
20561
20614
  });
20562
20615
  } catch {
20563
20616
  }
@@ -20616,14 +20669,14 @@ async function fetchTenantSyncConfig(cred) {
20616
20669
  return EMPTY_SYNC_CONFIG;
20617
20670
  }
20618
20671
  if (!res.ok) return lastKnownSyncConfig(base, `HTTP ${res.status}`);
20619
- const body = await res.json().catch(() => ({}));
20672
+ const body2 = await res.json().catch(() => ({}));
20620
20673
  const config2 = {
20621
- excludeTools: Array.isArray(body.excludeTools) ? body.excludeTools.filter((t) => typeof t === "string") : [],
20622
- excludeProjects: Array.isArray(body.excludeProjects) ? body.excludeProjects.filter((p) => typeof p === "string") : [],
20674
+ excludeTools: Array.isArray(body2.excludeTools) ? body2.excludeTools.filter((t) => typeof t === "string") : [],
20675
+ excludeProjects: Array.isArray(body2.excludeProjects) ? body2.excludeProjects.filter((p) => typeof p === "string") : [],
20623
20676
  // Ids only — installSourceExclusions drops anything else, so a server
20624
20677
  // sending a path cannot make this collector read it.
20625
- excludeSources: Array.isArray(body.excludeSources) ? body.excludeSources.filter((x) => typeof x === "string") : [],
20626
- approveSources: Array.isArray(body.approveSources) ? body.approveSources.filter((x) => typeof x === "string") : []
20678
+ excludeSources: Array.isArray(body2.excludeSources) ? body2.excludeSources.filter((x) => typeof x === "string") : [],
20679
+ approveSources: Array.isArray(body2.approveSources) ? body2.approveSources.filter((x) => typeof x === "string") : []
20627
20680
  };
20628
20681
  const applied = installSourceExclusions(config2.excludeSources);
20629
20682
  if (applied.excluded > 0) {
@@ -21018,9 +21071,9 @@ async function syncToTargetInScope(cred, opts = {}) {
21018
21071
  const maxInflight = explicitThrottle ? 1 : isLocalHost(cred.serverUrl) ? Math.max(LOCAL_INFLIGHT, advertised ?? 0) : advertised ?? REMOTE_INFLIGHT_FALLBACK;
21019
21072
  const UPLOAD_TIMEOUT_MS = Number(process.env.CHAT_RECALL_UPLOAD_TIMEOUT_MS) || 9e4;
21020
21073
  const gzipBody = serverAcceptsGzipBody(cred.serverUrl);
21021
- const post = async (body) => {
21074
+ const post = async (body2) => {
21022
21075
  const payload = (() => {
21023
- const withProgress = walkProgressForUpload ? { ...body, progress: walkProgressForUpload } : body;
21076
+ const withProgress = walkProgressForUpload ? { ...body2, progress: walkProgressForUpload } : body2;
21024
21077
  const json = JSON.stringify(withProgress);
21025
21078
  const raw = Buffer.from(json, "utf8");
21026
21079
  return gzipBody ? gzipSync3(raw, { level: 6 }) : raw;
@@ -21044,25 +21097,25 @@ async function syncToTargetInScope(cred, opts = {}) {
21044
21097
  });
21045
21098
  if (res.ok) {
21046
21099
  acceptedUploads.set(cred.serverUrl, (acceptedUploads.get(cred.serverUrl) ?? 0) + 1);
21047
- const body2 = await res.json().catch(() => ({}));
21048
- if (typeof body2.telemetry === "boolean") {
21049
- setTelemetryEligible(base, body2.telemetry);
21100
+ const body3 = await res.json().catch(() => ({}));
21101
+ if (typeof body3.telemetry === "boolean") {
21102
+ setTelemetryEligible(base, body3.telemetry);
21050
21103
  try {
21051
21104
  const prior = readCollectorHealth()?.telemetryEligible ?? {};
21052
21105
  updateCollectorHealth({
21053
- telemetryEligible: { ...prior, [base]: { allowed: body2.telemetry, at: Date.now() } }
21106
+ telemetryEligible: { ...prior, [base]: { allowed: body3.telemetry, at: Date.now() } }
21054
21107
  });
21055
21108
  } catch {
21056
21109
  }
21057
21110
  }
21058
- const adv = body2.limits?.ingestConcurrencyPerTenant;
21111
+ const adv = body3.limits?.ingestConcurrencyPerTenant;
21059
21112
  if (typeof adv === "number" && Number.isFinite(adv) && adv >= 1) {
21060
21113
  setServerIngestConcurrency(base, Math.floor(adv));
21061
21114
  }
21062
- if (body2.cli && body2.cli.version) {
21115
+ if (body3.cli && body3.cli.version) {
21063
21116
  const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2, sweepStaleStaging: sweepStaleStaging2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
21064
21117
  const authHeaders = cred.token ? { authorization: `Bearer ${cred.token}` } : {};
21065
- const plan = planAutoUpdate2(base, { cli: body2.cli }, ownVersion, process.env.CHAT_RECALL_AUTO_UPDATE);
21118
+ const plan = planAutoUpdate2(base, { cli: body3.cli }, ownVersion, process.env.CHAT_RECALL_AUTO_UPDATE);
21066
21119
  sweepStaleStaging2();
21067
21120
  if (plan.update) {
21068
21121
  void runAutoUpdate2(base, authHeaders, ownVersion).catch(() => {
@@ -21079,13 +21132,13 @@ async function syncToTargetInScope(cred, opts = {}) {
21079
21132
  if (res.status === 402) {
21080
21133
  let detail = text;
21081
21134
  try {
21082
- const body2 = JSON.parse(text);
21083
- detail = body2.error ?? text;
21084
- if (body2.detail) detail += `
21085
- ${body2.detail}`;
21086
- if (body2.resetsAt) detail += ` (resets ${new Date(body2.resetsAt).toISOString().slice(0, 10)})`;
21087
- if (body2.upgradeUrl) detail += `
21088
- ${body2.upgradeUrl}`;
21135
+ const body3 = JSON.parse(text);
21136
+ detail = body3.error ?? text;
21137
+ if (body3.detail) detail += `
21138
+ ${body3.detail}`;
21139
+ if (body3.resetsAt) detail += ` (resets ${new Date(body3.resetsAt).toISOString().slice(0, 10)})`;
21140
+ if (body3.upgradeUrl) detail += `
21141
+ ${body3.upgradeUrl}`;
21089
21142
  } catch {
21090
21143
  }
21091
21144
  throw classedError("payment_required", `sync paused by the server: ${detail}`);
@@ -21114,7 +21167,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21114
21167
  let poolError = null;
21115
21168
  const MAX_INFLIGHT_BYTES = (Number(process.env.CHAT_RECALL_MAX_INFLIGHT_MB) || 32) * 1024 * 1024;
21116
21169
  let inflightBytes = 0;
21117
- const submit = async (body, after, bytesHint = 0) => {
21170
+ const submit = async (body2, after, bytesHint = 0) => {
21118
21171
  while (inflight.size > 0 && (inflight.size >= maxInflight || inflightBytes + bytesHint > MAX_INFLIGHT_BYTES)) {
21119
21172
  await Promise.race(inflight);
21120
21173
  }
@@ -21122,7 +21175,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21122
21175
  inflightBytes += bytesHint;
21123
21176
  let task;
21124
21177
  task = (async () => {
21125
- await post(body);
21178
+ await post(body2);
21126
21179
  after();
21127
21180
  })().catch((e) => {
21128
21181
  poolError = poolError ?? e;
@@ -21189,18 +21242,18 @@ async function syncToTargetInScope(cred, opts = {}) {
21189
21242
  signal: ac.signal
21190
21243
  });
21191
21244
  if (!res.ok) throw new Error(`append sync failed: HTTP ${res.status} ${await res.text().catch(() => "")}`);
21192
- const body = await res.json().catch(() => ({}));
21193
- if (body.cli && body.cli.version) {
21245
+ const body2 = await res.json().catch(() => ({}));
21246
+ if (body2.cli && body2.cli.version) {
21194
21247
  const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2, sweepStaleStaging: sweepStaleStaging2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
21195
21248
  const authHeaders = cred.token ? { authorization: `Bearer ${cred.token}` } : {};
21196
- const plan = planAutoUpdate2(base, { cli: body.cli }, ownVersion, process.env.CHAT_RECALL_AUTO_UPDATE);
21249
+ const plan = planAutoUpdate2(base, { cli: body2.cli }, ownVersion, process.env.CHAT_RECALL_AUTO_UPDATE);
21197
21250
  sweepStaleStaging2();
21198
21251
  if (plan.update) {
21199
21252
  void runAutoUpdate2(base, authHeaders, ownVersion).catch(() => {
21200
21253
  });
21201
21254
  }
21202
21255
  }
21203
- const fullNeeded = new Set(body.full_resync_needed ?? []);
21256
+ const fullNeeded = new Set(body2.full_resync_needed ?? []);
21204
21257
  const acked = convs.filter((c) => !fullNeeded.has(c.session_id));
21205
21258
  if (acked.length > 0) {
21206
21259
  try {
@@ -21235,9 +21288,9 @@ async function syncToTargetInScope(cred, opts = {}) {
21235
21288
  signal: ac.signal
21236
21289
  });
21237
21290
  if (!res.ok) throw new Error(`chunked head sync failed: HTTP ${res.status} ${await res.text().catch(() => "")}`);
21238
- const body = await res.json().catch(() => ({}));
21291
+ const body2 = await res.json().catch(() => ({}));
21239
21292
  const id = conv.session_id;
21240
- const guarded = (body.shrink_guarded ?? []).find((g) => g.session_id === id);
21293
+ const guarded = (body2.shrink_guarded ?? []).find((g) => g.session_id === id);
21241
21294
  const mtime = conv.mtime;
21242
21295
  try {
21243
21296
  if (!guarded) {
@@ -22114,7 +22167,7 @@ var init_sync_client = __esm({
22114
22167
  ownVersion = JSON.parse(readFileSync38(join49(dir, "../package.json"), "utf-8")).version || ownVersion;
22115
22168
  } catch {
22116
22169
  try {
22117
- if (true) ownVersion = "0.7.7";
22170
+ if (true) ownVersion = "0.7.8";
22118
22171
  } catch {
22119
22172
  }
22120
22173
  }
@@ -22175,7 +22228,7 @@ function reportClientEvent2(kind, opts = {}) {
22175
22228
  try {
22176
22229
  const targets = loadAllCredentials();
22177
22230
  if (!targets.length) return;
22178
- const body = JSON.stringify({
22231
+ const body2 = JSON.stringify({
22179
22232
  events: [{
22180
22233
  ts: Date.now(),
22181
22234
  kind: String(kind).slice(0, 64),
@@ -22192,7 +22245,7 @@ function reportClientEvent2(kind, opts = {}) {
22192
22245
  void fetch(`${t.serverUrl.replace(/\/+$/, "")}/api/client-events`, {
22193
22246
  method: "POST",
22194
22247
  headers,
22195
- body,
22248
+ body: body2,
22196
22249
  signal: AbortSignal.timeout(3e3)
22197
22250
  }).catch(() => {
22198
22251
  });
@@ -22258,8 +22311,8 @@ async function fetchNpmLatest() {
22258
22311
  headers: { accept: "application/vnd.npm.install-v1+json" }
22259
22312
  }, 5e3);
22260
22313
  if (!res.ok) return null;
22261
- const body = await res.json();
22262
- const v = body["dist-tags"]?.latest;
22314
+ const body2 = await res.json();
22315
+ const v = body2["dist-tags"]?.latest;
22263
22316
  return typeof v === "string" && v ? v : null;
22264
22317
  } catch {
22265
22318
  return null;
@@ -23538,9 +23591,9 @@ function splitSummary(summary) {
23538
23591
  marks.sort((a, b) => a.start - b.start);
23539
23592
  for (let i = 0; i < marks.length; i++) {
23540
23593
  const stop = i + 1 < marks.length ? marks[i + 1].end : summary.length;
23541
- const body = summary.slice(marks[i].start, stop).trim();
23542
- if (body)
23543
- out[marks[i].key] = body;
23594
+ const body2 = summary.slice(marks[i].start, stop).trim();
23595
+ if (body2)
23596
+ out[marks[i].key] = body2;
23544
23597
  }
23545
23598
  return out;
23546
23599
  }
@@ -23640,6 +23693,104 @@ function crossProjectNote(headProject, priorRows, hasProjectFilter) {
23640
23693
  return `_These sessions span ${names.size} projects (${[...names].join(", ")}). Pass \`project_filter\` to scope the resume to one._`;
23641
23694
  }
23642
23695
 
23696
+ // ../engine/dist/mcp/show-render.js
23697
+ var BODY_LIMIT = 2e3;
23698
+ var EDGE = 100;
23699
+ var INDENT = " ";
23700
+ var oneLine = (v) => String(v).replace(/\s*\n\s*/g, " \u23CE ");
23701
+ var indent = (text) => text.split("\n").map((l) => INDENT + l).join("\n");
23702
+ function safeSlice(s, start, end) {
23703
+ const lo = start > 0 && /[\uDC00-\uDFFF]/.test(s[start] ?? "") ? start - 1 : start;
23704
+ const hi = end < s.length && /[\uD800-\uDBFF]/.test(s[end - 1] ?? "") ? end + 1 : end;
23705
+ return s.slice(lo, hi);
23706
+ }
23707
+ function body(text, line, opts) {
23708
+ if (opts.full || text.length <= BODY_LIMIT)
23709
+ return indent(text);
23710
+ const lines = text.split("\n").length;
23711
+ const cut = text.length - 2 * EDGE;
23712
+ const how = opts.expandHint ? ` ${opts.expandHint(line)}` : "";
23713
+ return [
23714
+ indent(safeSlice(text, 0, EDGE)),
23715
+ `${INDENT}\u2026 ${cut} characters cut (${text.length} in all, ${lines} lines).${how} \u2026`,
23716
+ indent(safeSlice(text, text.length - EDGE, text.length))
23717
+ ].join("\n");
23718
+ }
23719
+ function resultText(result) {
23720
+ if (typeof result === "string")
23721
+ return result;
23722
+ if (Array.isArray(result)) {
23723
+ return result.map((b) => {
23724
+ if (b?.type === "text")
23725
+ return String(b.text ?? "");
23726
+ if (b?.type === "image")
23727
+ return "[image]";
23728
+ if (b?.type === "tool_reference")
23729
+ return `[tool_reference: ${b.tool_name ?? ""}]`;
23730
+ return JSON.stringify(b);
23731
+ }).join("\n");
23732
+ }
23733
+ return JSON.stringify(result);
23734
+ }
23735
+ function renderToolCall(tc, line, opts) {
23736
+ const inp = tc.input && typeof tc.input === "object" ? tc.input : {};
23737
+ const out = [];
23738
+ if (tc.name === "Bash" && inp.command !== void 0) {
23739
+ const desc = inp.description ? ` # ${oneLine(inp.description)}` : "";
23740
+ out.push(`[Bash]${desc}
23741
+ $ ${oneLine(inp.command)}`);
23742
+ } else if (inp.file_path !== void 0) {
23743
+ const inline = {};
23744
+ const blocks = [];
23745
+ for (const [k, v] of Object.entries(inp)) {
23746
+ if (k === "file_path")
23747
+ continue;
23748
+ if (typeof v === "string" && (v.includes("\n") || v.length > 120))
23749
+ blocks.push([k, v]);
23750
+ else
23751
+ inline[k] = v;
23752
+ }
23753
+ const opts_ = Object.keys(inline).length ? ` ${JSON.stringify(inline)}` : "";
23754
+ out.push(`[${tc.name}] ${inp.file_path}${opts_}`);
23755
+ for (const [k, v] of blocks)
23756
+ out.push(` ${k}:`, body(v, line, opts));
23757
+ } else if (typeof tc.input === "string") {
23758
+ out.push(`[${tc.name}]`, body(tc.input, line, opts));
23759
+ } else {
23760
+ const json = JSON.stringify(tc.input ?? {});
23761
+ if (opts.full || json.length <= BODY_LIMIT)
23762
+ out.push(`[${tc.name}] ${oneLine(json)}`);
23763
+ else
23764
+ out.push(`[${tc.name}]`, body(json, line, opts));
23765
+ }
23766
+ if (tc.result !== void 0) {
23767
+ out.push(tc.isError ? " \u2192 error:" : " \u2192 result:", body(resultText(tc.result), line, opts));
23768
+ }
23769
+ return out.join("\n");
23770
+ }
23771
+ function renderShowMessages(messages, opts = {}) {
23772
+ const out = [];
23773
+ for (const msg of messages) {
23774
+ const text = msg.content ?? "";
23775
+ const thinking = msg.thinking ?? "";
23776
+ const calls = msg.toolCalls ?? [];
23777
+ if (!text.trim() && !thinking.trim() && !calls.length && msg.thinking !== void 0)
23778
+ continue;
23779
+ out.push(`**${msg.role}** (line ${msg.line})`);
23780
+ if (thinking.trim())
23781
+ out.push(`_(thinking)_
23782
+ ${thinking}`);
23783
+ if (text.trim())
23784
+ out.push(text);
23785
+ for (const tc of calls)
23786
+ out.push(renderToolCall(tc, msg.line, opts));
23787
+ if (!text.trim() && !thinking.trim() && !calls.length)
23788
+ out.push("_(empty)_");
23789
+ out.push("");
23790
+ }
23791
+ return out;
23792
+ }
23793
+
23643
23794
  // ../engine/dist/core/decision-areas.js
23644
23795
  var DECISION_AREAS = [
23645
23796
  "auth",
@@ -23758,12 +23909,12 @@ import { config } from "dotenv";
23758
23909
  var TRIAL_WARN_DAYS = 3;
23759
23910
  async function buildHttpError(path2, res) {
23760
23911
  const status = res.status;
23761
- let body = null;
23912
+ let body2 = null;
23762
23913
  try {
23763
- body = await res.clone().json();
23914
+ body2 = await res.clone().json();
23764
23915
  } catch {
23765
23916
  }
23766
- const str = (k) => typeof body?.[k] === "string" ? body[k] : "";
23917
+ const str = (k) => typeof body2?.[k] === "string" ? body2[k] : "";
23767
23918
  if (status === 402) {
23768
23919
  const parts = [str("error") || "payment required"];
23769
23920
  if (str("detail"))
@@ -24190,14 +24341,14 @@ function codeindexAvailable() {
24190
24341
  }
24191
24342
  return _codeindexAvailable;
24192
24343
  }
24193
- function withCodeindexHint(body, kind) {
24344
+ function withCodeindexHint(body2, kind) {
24194
24345
  if (!codeindexAvailable())
24195
- return body;
24346
+ return body2;
24196
24347
  const hints = {
24197
24348
  files: "\n\n_Tip: codeindex is also installed \u2014 call `find_symbol` or `get_outline` on these files for current symbol-level detail._",
24198
24349
  session: "\n\n_Tip: codeindex is also installed \u2014 call `get_outline` on each file for its current symbol structure._"
24199
24350
  };
24200
- return body + hints[kind];
24351
+ return body2 + hints[kind];
24201
24352
  }
24202
24353
  function remoteCredentials() {
24203
24354
  const ambient = currentCredentials();
@@ -24227,14 +24378,14 @@ function remoteCredentials() {
24227
24378
  return null;
24228
24379
  }
24229
24380
  }
24230
- async function remotePost(path2, body) {
24381
+ async function remotePost(path2, body2) {
24231
24382
  const cred = remoteCredentials();
24232
24383
  if (!cred)
24233
24384
  throw new Error('scope "server" needs a login \u2014 run `chat-recall login <server-url>` first.');
24234
24385
  const res = await fetch(cred.base + path2, {
24235
24386
  method: "POST",
24236
24387
  headers: { "content-type": "application/json", authorization: `Bearer ${cred.token}` },
24237
- body: JSON.stringify(body)
24388
+ body: JSON.stringify(body2)
24238
24389
  });
24239
24390
  if (!res.ok)
24240
24391
  throw await buildHttpError(path2, res);
@@ -24251,27 +24402,27 @@ async function remoteGet(path2) {
24251
24402
  throw await buildHttpError(path2, res);
24252
24403
  return res.json();
24253
24404
  }
24254
- async function remotePatch(path2, body) {
24405
+ async function remotePatch(path2, body2) {
24255
24406
  const cred = remoteCredentials();
24256
24407
  if (!cred)
24257
24408
  throw new Error('scope "server" needs a login \u2014 run `chat-recall login <server-url>` first.');
24258
24409
  const res = await fetch(cred.base + path2, {
24259
24410
  method: "PATCH",
24260
24411
  headers: { "content-type": "application/json", authorization: `Bearer ${cred.token}` },
24261
- body: JSON.stringify(body)
24412
+ body: JSON.stringify(body2)
24262
24413
  });
24263
24414
  if (!res.ok)
24264
24415
  throw await buildHttpError(path2, res);
24265
24416
  return res.json();
24266
24417
  }
24267
- async function remotePut(path2, body) {
24418
+ async function remotePut(path2, body2) {
24268
24419
  const cred = remoteCredentials();
24269
24420
  if (!cred)
24270
24421
  throw new Error('scope "server" needs a login \u2014 run `chat-recall login <server-url>` first.');
24271
24422
  const res = await fetch(cred.base + path2, {
24272
24423
  method: "PUT",
24273
24424
  headers: { "content-type": "application/json", authorization: `Bearer ${cred.token}` },
24274
- body: JSON.stringify(body)
24425
+ body: JSON.stringify(body2)
24275
24426
  });
24276
24427
  if (!res.ok)
24277
24428
  throw await buildHttpError(path2, res);
@@ -24324,12 +24475,12 @@ async function remoteGetSoft(path2, params = {}) {
24324
24475
  const q = qs.toString();
24325
24476
  const res = await fetch(cred.base + (q ? `${path2}?${q}` : path2), { headers: { authorization: `Bearer ${cred.token}` } });
24326
24477
  if (res.status === 202 || res.status === 404 || res.status === 409) {
24327
- const body = await res.json().catch(() => ({}));
24328
- let message = body.message ?? body.error;
24329
- if (Array.isArray(body.candidates) && body.candidates.length) {
24478
+ const body2 = await res.json().catch(() => ({}));
24479
+ let message = body2.message ?? body2.error;
24480
+ if (Array.isArray(body2.candidates) && body2.candidates.length) {
24330
24481
  message = `${message ?? "Ambiguous session id prefix."}
24331
24482
  Candidates:
24332
- ${body.candidates.map((c) => ` \u2022 ${c}`).join("\n")}
24483
+ ${body2.candidates.map((c) => ` \u2022 ${c}`).join("\n")}
24333
24484
  Re-run with a longer prefix or the full id.`;
24334
24485
  }
24335
24486
  return { status: res.status, data: null, message };
@@ -24352,35 +24503,9 @@ var RecallShowSchema = z.object({
24352
24503
  session_id: z.string().describe("Session ID from search results"),
24353
24504
  around_line: z.number().optional().describe("Optional line number to show context around"),
24354
24505
  max_messages: z.number().optional().default(10).describe("Maximum messages to return"),
24355
- from_end: z.number().optional().describe("Return the last N messages of the session. Mutually exclusive with around_line.")
24506
+ from_end: z.number().optional().describe("Return the last N messages of the session. Mutually exclusive with around_line."),
24507
+ expand_line: z.number().optional().describe("Return only the message at this line, with every tool input and result whole.")
24356
24508
  });
24357
- function renderShowMessages(messages) {
24358
- const fmtToolCall = (tc) => {
24359
- const inp = tc.input || {};
24360
- const oneLine = (v) => String(v).replace(/\s*\n\s*/g, " \u23CE ");
24361
- if (tc.name === "Bash" && inp.command !== void 0) {
24362
- const desc = inp.description ? ` # ${oneLine(inp.description)}` : "";
24363
- return `[Bash]${desc}
24364
- $ ${oneLine(inp.command)}`;
24365
- }
24366
- if (inp.file_path !== void 0)
24367
- return `[${tc.name}] ${inp.file_path}`;
24368
- return `[${tc.name}] ${oneLine(JSON.stringify(inp))}`;
24369
- };
24370
- const out = [];
24371
- for (const msg of messages) {
24372
- out.push(`**${msg.role}** (line ${msg.line})`);
24373
- const text = msg.content;
24374
- if (text.trim())
24375
- out.push(text);
24376
- for (const tc of msg.toolCalls || [])
24377
- out.push(fmtToolCall(tc));
24378
- if (!text.trim() && !(msg.toolCalls || []).length)
24379
- out.push("_(empty)_");
24380
- out.push("");
24381
- }
24382
- return out;
24383
- }
24384
24509
  var RecallRecentSchema = z.object({
24385
24510
  project_filter: z.string().optional().describe('Filter by project name (e.g., "acme", "poly")'),
24386
24511
  limit: z.number().optional().default(10).describe("Number of recent sessions to show"),
@@ -24990,14 +25115,17 @@ plan id (the plan filename without .md, as returned by
24990
25115
  recall_memory_search(source_types:['plan'])) and renders the complete plan text.
24991
25116
 
24992
25117
  Set \`from_end: N\` to fetch the last N messages (no line-number guessing).
24993
- Every message comes back in full, code blocks and commands included.`,
25118
+ Message text and commands come back in full. Tool calls come with their inputs
25119
+ and results; a body over 2000 characters shows its first and last 100 and the
25120
+ line to pass as \`expand_line\` to get it whole.`,
24994
25121
  inputSchema: {
24995
25122
  type: "object",
24996
25123
  properties: {
24997
25124
  session_id: { type: "string", description: "Session ID from search results, or a plan id (filename without .md)" },
24998
25125
  around_line: { type: "number", description: "Optional line number to show context around" },
24999
25126
  max_messages: { type: "number", default: 10, description: "Maximum messages to return" },
25000
- from_end: { type: "number", description: "Return the last N messages (alternative to around_line)." }
25127
+ from_end: { type: "number", description: "Return the last N messages (alternative to around_line)." },
25128
+ expand_line: { type: "number", description: "Return only the message at this line, with every tool input and result whole." }
25001
25129
  },
25002
25130
  required: ["session_id"]
25003
25131
  }
@@ -26429,6 +26557,16 @@ ${plan.data.content}` }] };
26429
26557
  return { content: [{ type: "text", text: soft.message || `Session (or plan) not found: ${params.session_id}` }] };
26430
26558
  }
26431
26559
  const messagesList = soft.data.messages;
26560
+ const expandHint = (line) => `Call recall_show with session_id "${params.session_id}" and expand_line ${line} for the whole text.`;
26561
+ if (params.expand_line !== void 0) {
26562
+ const hit = messagesList.find((m) => m.line === params.expand_line);
26563
+ if (!hit) {
26564
+ const lines = messagesList.map((m) => m.line);
26565
+ return { content: [{ type: "text", text: `No message at line ${params.expand_line} in ${params.session_id}. Its lines run from ${Math.min(...lines)} to ${Math.max(...lines)}.` }] };
26566
+ }
26567
+ const out = [`Session: ${params.session_id}`, `Line ${hit.line}, whole.`, "", ...renderShowMessages([hit], { full: true })];
26568
+ return { content: [{ type: "text", text: out.join("\n") }] };
26569
+ }
26432
26570
  let displayMessages = messagesList;
26433
26571
  if (params.from_end !== void 0) {
26434
26572
  const n = Math.max(1, Math.min(params.from_end, messagesList.length));
@@ -26453,7 +26591,7 @@ ${plan.data.content}` }] };
26453
26591
  output.push(`Showing ${displayMessages.length} around line ${params.around_line}.`);
26454
26592
  }
26455
26593
  output.push("");
26456
- output.push(...renderShowMessages(displayMessages));
26594
+ output.push(...renderShowMessages(displayMessages, { expandHint }));
26457
26595
  {
26458
26596
  const rc = resumeCommandFor(params.session_id);
26459
26597
  if (rc)
@@ -27198,10 +27336,10 @@ _Call again with \`detail: true\` for the full brief (fix, locations, agent prom
27198
27336
  const head = `## ${i + 1}. ${t.title}
27199
27337
 
27200
27338
  - id: \`${t.id}\` \xB7 status: ${t.status}${t.projectId ? ` \xB7 project: ${t.projectId}` : ""}${t.linkedSessionId ? ` \xB7 already claimed by session ${t.linkedSessionId.slice(0, 8)}` : ""}`;
27201
- const body = (t.description || "").trim();
27202
- return body ? `${head}
27339
+ const body2 = (t.description || "").trim();
27340
+ return body2 ? `${head}
27203
27341
 
27204
- ${body}` : head;
27342
+ ${body2}` : head;
27205
27343
  });
27206
27344
  const howTo = [
27207
27345
  "---",
@@ -28418,8 +28556,8 @@ _Tell the user \u2014 this changes how future sessions treat their repo._` }] };
28418
28556
  if (params.scope === "fan_out" && !params.device) {
28419
28557
  return { content: [{ type: "text", text: "`fan_out` copies between ONE machine's own tools \u2014 name the machine with `device`." }] };
28420
28558
  }
28421
- const body = params.scope === "fan_out" ? { kind: "sync_all", deviceId: params.device } : { kind: "pull", types: params.types, name: params.name, deviceId: params.device ?? null };
28422
- const r = await remotePost("/api/sync-intents", body);
28559
+ const body2 = params.scope === "fan_out" ? { kind: "sync_all", deviceId: params.device } : { kind: "pull", types: params.types, name: params.name, deviceId: params.device ?? null };
28560
+ const r = await remotePost("/api/sync-intents", body2);
28423
28561
  if (!r.id)
28424
28562
  return { content: [{ type: "text", text: `Queue refused it: ${r.error || "unknown error"}` }] };
28425
28563
  const what = params.name ? `"${params.name}"` : params.types?.length ? params.types.join(", ") : "every artifact type";
@@ -28852,7 +28990,7 @@ function installCrashGuards() {
28852
28990
  reportClientEvent2("mcp_crash", { message: err.stack || err.message });
28853
28991
  });
28854
28992
  }
28855
- var RELAY_VERSION = true ? "0.7.7" : "0.0.0";
28993
+ var RELAY_VERSION = true ? "0.7.8" : "0.0.0";
28856
28994
  function daemonIdleSecs() {
28857
28995
  const raw = process.env.CHAT_RECALL_DAEMON_IDLE_SECS;
28858
28996
  if (raw === void 0) return 900;