chat-recall 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -7736,23 +7736,8 @@ var init_opencode = __esm({
7736
7736
  const projectPath = row.project_path || row.directory || "";
7737
7737
  if (filter && !projectPath.toLowerCase().includes(filter))
7738
7738
  continue;
7739
- const firstUser = db.prepare(`
7740
- SELECT p.data
7741
- FROM part p JOIN message m ON m.id = p.message_id
7742
- WHERE p.session_id = ?
7743
- AND p.data LIKE '%"type":"text"%'
7744
- AND m.data LIKE '%"role":"user"%'
7745
- ORDER BY p.time_created ASC LIMIT 1
7746
- `).get(row.id);
7747
- let firstPrompt = "";
7748
- if (firstUser) {
7749
- try {
7750
- const parsed = JSON.parse(firstUser.data);
7751
- firstPrompt = flatString(String(parsed?.text || "").slice(0, 200));
7752
- } catch {
7753
- }
7754
- }
7755
- const messageCount = db.prepare("SELECT COUNT(*) as c FROM message WHERE session_id = ?").get(row.id)?.c ?? 0;
7739
+ const firstPrompt = opts.previews === false ? "" : this.firstUserPrompt(db, row.id);
7740
+ const messageCount = opts.previews === false ? 0 : db.prepare("SELECT COUNT(*) as c FROM message WHERE session_id = ?").get(row.id)?.c ?? 0;
7756
7741
  const created = new Date(row.time_created || row.time_updated).toISOString();
7757
7742
  const modified = new Date(row.time_updated).toISOString();
7758
7743
  out.push({
@@ -7776,6 +7761,45 @@ var init_opencode = __esm({
7776
7761
  db.close();
7777
7762
  }
7778
7763
  }
7764
+ /**
7765
+ * The first user text part of a session.
7766
+ *
7767
+ * Walks the session's user messages oldest first through the
7768
+ * (session_id, time_created, id) index and stops at the first one with a
7769
+ * text part. The role and the text are read by SQLite: a first message can
7770
+ * carry whole diffs in `summary` (107 MB on one row), and parsing that in JS
7771
+ * to read one field took the process to 510 MB. The earlier query filtered
7772
+ * every part and message with LIKE and then sorted, so it read all of a
7773
+ * session's data: 423 MB for a 236-session listing.
7774
+ */
7775
+ firstUserPrompt(db, sessionId) {
7776
+ try {
7777
+ const has = (table, col) => !!db.prepare(`SELECT 1 FROM pragma_table_info(?) WHERE name = ?`).get(table, col);
7778
+ const msgOrder = has("message", "time_created") ? "time_created, id" : "rowid";
7779
+ const partOrder = has("part", "time_created") ? "time_created, id" : "rowid";
7780
+ const nextUser = db.prepare(`SELECT id, ${msgOrder === "rowid" ? "rowid AS k1, 0 AS k2" : "time_created AS k1, id AS k2"} FROM message
7781
+ WHERE session_id = ? AND (${msgOrder === "rowid" ? "rowid, 0" : "time_created, id"}) > (?, ?)
7782
+ AND json_extract(data, '$.role') = 'user'
7783
+ ORDER BY ${msgOrder} LIMIT 1`);
7784
+ const firstText = db.prepare(`SELECT substr(json_extract(data, '$.text'), 1, 400) AS text FROM part
7785
+ WHERE message_id = ? AND json_extract(data, '$.type') = 'text'
7786
+ AND length(trim(json_extract(data, '$.text'))) > 0
7787
+ ORDER BY ${partOrder} LIMIT 1`);
7788
+ let k1 = Number.MIN_SAFE_INTEGER, k2 = msgOrder === "rowid" ? 0 : "";
7789
+ for (; ; ) {
7790
+ const m = nextUser.get(sessionId, k1, k2);
7791
+ if (!m)
7792
+ return "";
7793
+ k1 = m.k1;
7794
+ k2 = m.k2;
7795
+ const t = firstText.get(m.id);
7796
+ if (t?.text)
7797
+ return flatString(t.text.slice(0, 200));
7798
+ }
7799
+ } catch {
7800
+ return "";
7801
+ }
7802
+ }
7779
7803
  /** OpenCode auto-generates a session title (model-written, distinct from the
7780
7804
  * first prompt — e.g. "Replacing Claude with Gemini CLI"). It's a plain
7781
7805
  * column on the session row. */
@@ -8093,7 +8117,7 @@ var init_opencode = __esm({
8093
8117
 
8094
8118
  // ../engine/dist/core/backends/tail-read.js
8095
8119
  import { openSync as openSync2, readSync as readSync2, closeSync as closeSync2, statSync as statSync4 } from "node:fs";
8096
- function readTailFromOffset(path, offset) {
8120
+ function readTailFromOffset(path, offset, maxBytes = TAIL_READ_MAX_BYTES) {
8097
8121
  let size = 0;
8098
8122
  try {
8099
8123
  size = statSync4(path).size;
@@ -8102,7 +8126,7 @@ function readTailFromOffset(path, offset) {
8102
8126
  }
8103
8127
  if (size <= offset)
8104
8128
  return { text: "", newOffset: offset };
8105
- const len = Math.min(size - offset, 16 * 1024 * 1024);
8129
+ const len = Math.min(size - offset, Math.max(1, maxBytes));
8106
8130
  const buf = Buffer.allocUnsafe(len);
8107
8131
  let fd = null;
8108
8132
  try {
@@ -8118,11 +8142,16 @@ function readTailFromOffset(path, offset) {
8118
8142
  return { text: "", newOffset: offset };
8119
8143
  const slice = got < len ? buf.subarray(0, got) : buf;
8120
8144
  const lastNl = slice.lastIndexOf(10);
8121
- if (lastNl < 0) {
8122
- return { text: "", newOffset: offset };
8145
+ if (lastNl >= 0) {
8146
+ const text = slice.subarray(0, lastNl + 1).toString("utf-8");
8147
+ return { text, newOffset: offset + lastNl + 1 };
8123
8148
  }
8124
- const text = slice.subarray(0, lastNl + 1).toString("utf-8");
8125
- return { text, newOffset: offset + lastNl + 1 };
8149
+ if (offset + got >= size)
8150
+ return { text: "", newOffset: offset };
8151
+ const end = findNewline(fd, offset + got, size);
8152
+ if (end < 0)
8153
+ return { text: "", newOffset: offset };
8154
+ return { text: "", newOffset: end + 1, skippedBytes: end + 1 - offset };
8126
8155
  } catch {
8127
8156
  return { text: "", newOffset: offset };
8128
8157
  } finally {
@@ -8134,9 +8163,26 @@ function readTailFromOffset(path, offset) {
8134
8163
  }
8135
8164
  }
8136
8165
  }
8166
+ function findNewline(fd, from, size) {
8167
+ const block = Buffer.allocUnsafe(SKIP_SCAN_BYTES);
8168
+ let pos = from;
8169
+ while (pos < size) {
8170
+ const n = readSync2(fd, block, 0, Math.min(SKIP_SCAN_BYTES, size - pos), pos);
8171
+ if (n <= 0)
8172
+ return -1;
8173
+ const i = block.subarray(0, n).indexOf(10);
8174
+ if (i >= 0)
8175
+ return pos + i;
8176
+ pos += n;
8177
+ }
8178
+ return -1;
8179
+ }
8180
+ var TAIL_READ_MAX_BYTES, SKIP_SCAN_BYTES;
8137
8181
  var init_tail_read = __esm({
8138
8182
  "../engine/dist/core/backends/tail-read.js"() {
8139
8183
  "use strict";
8184
+ TAIL_READ_MAX_BYTES = 16 * 1024 * 1024;
8185
+ SKIP_SCAN_BYTES = 1024 * 1024;
8140
8186
  }
8141
8187
  });
8142
8188
 
@@ -8387,28 +8433,29 @@ var init_codex2 = __esm({
8387
8433
  continue;
8388
8434
  let firstPrompt = "";
8389
8435
  let messageCount = 0;
8390
- try {
8391
- const lines = readFileSync3(filePath, "utf-8").split("\n");
8392
- for (const line of lines) {
8393
- if (!line.trim())
8394
- continue;
8395
- let ev;
8396
- try {
8397
- ev = JSON.parse(line);
8398
- } catch {
8399
- continue;
8400
- }
8401
- if (ev.type === "event_msg" && ev.payload?.type === "user_message") {
8402
- messageCount++;
8403
- if (!firstPrompt && ev.payload.message) {
8404
- firstPrompt = flatString(String(ev.payload.message).slice(0, 200));
8436
+ if (opts.previews !== false)
8437
+ try {
8438
+ const lines = readFileSync3(filePath, "utf-8").split("\n");
8439
+ for (const line of lines) {
8440
+ if (!line.trim())
8441
+ continue;
8442
+ let ev;
8443
+ try {
8444
+ ev = JSON.parse(line);
8445
+ } catch {
8446
+ continue;
8447
+ }
8448
+ if (ev.type === "event_msg" && ev.payload?.type === "user_message") {
8449
+ messageCount++;
8450
+ if (!firstPrompt && ev.payload.message) {
8451
+ firstPrompt = flatString(String(ev.payload.message).slice(0, 200));
8452
+ }
8453
+ } else if (ev.type === "response_item" && ev.payload?.type === "message") {
8454
+ messageCount++;
8405
8455
  }
8406
- } else if (ev.type === "response_item" && ev.payload?.type === "message") {
8407
- messageCount++;
8408
8456
  }
8457
+ } catch {
8409
8458
  }
8410
- } catch {
8411
- }
8412
8459
  const prior = seen.get(rawId);
8413
8460
  if (prior) {
8414
8461
  if (stat3.mtimeMs > prior.mtime) {
@@ -8843,11 +8890,11 @@ var init_codex2 = __esm({
8843
8890
  return 0;
8844
8891
  }
8845
8892
  }
8846
- async readFromOffset(prefixedId, offset) {
8893
+ async readFromOffset(prefixedId, offset, maxBytes) {
8847
8894
  const loc = this.findSession(prefixedId);
8848
8895
  if (!loc)
8849
8896
  return { text: "", newOffset: offset };
8850
- return readTailFromOffset(loc.path, offset);
8897
+ return readTailFromOffset(loc.path, offset, maxBytes);
8851
8898
  }
8852
8899
  };
8853
8900
  codexBackend = new CodexBackend();
@@ -9098,21 +9145,22 @@ var init_agy = __esm({
9098
9145
  const projectDir = projectPath ? basename8(projectPath) : "";
9099
9146
  let firstPrompt = "";
9100
9147
  let messageCount = 0;
9101
- try {
9102
- const lines = readFileSync4(filePath, "utf-8").split("\n").filter(Boolean);
9103
- messageCount = lines.length;
9104
- for (const line of lines) {
9105
- const obj = JSON.parse(line);
9106
- if (obj.source === "USER_EXPLICIT" && obj.type === "USER_INPUT" && typeof obj.content === "string") {
9107
- const content = obj.content;
9108
- const match = content.match(/<USER_REQUEST>([\s\S]*?)<\/USER_REQUEST>/);
9109
- const rawText = match ? match[1].trim() : content.trim();
9110
- firstPrompt = flatString(rawText.slice(0, 200));
9111
- break;
9148
+ if (opts.previews !== false)
9149
+ try {
9150
+ const lines = readFileSync4(filePath, "utf-8").split("\n").filter(Boolean);
9151
+ messageCount = lines.length;
9152
+ for (const line of lines) {
9153
+ const obj = JSON.parse(line);
9154
+ if (obj.source === "USER_EXPLICIT" && obj.type === "USER_INPUT" && typeof obj.content === "string") {
9155
+ const content = obj.content;
9156
+ const match = content.match(/<USER_REQUEST>([\s\S]*?)<\/USER_REQUEST>/);
9157
+ const rawText = match ? match[1].trim() : content.trim();
9158
+ firstPrompt = flatString(rawText.slice(0, 200));
9159
+ break;
9160
+ }
9112
9161
  }
9162
+ } catch {
9113
9163
  }
9114
- } catch {
9115
- }
9116
9164
  if (seenIds.has(rawId))
9117
9165
  continue;
9118
9166
  seenIds.add(rawId);
@@ -9408,11 +9456,11 @@ var init_agy = __esm({
9408
9456
  return 0;
9409
9457
  }
9410
9458
  }
9411
- async readFromOffset(prefixedId, offset) {
9459
+ async readFromOffset(prefixedId, offset, maxBytes) {
9412
9460
  const loc = this.findSession(prefixedId);
9413
9461
  if (!loc)
9414
9462
  return { text: "", newOffset: offset };
9415
- return readTailFromOffset(loc.path, offset);
9463
+ return readTailFromOffset(loc.path, offset, maxBytes);
9416
9464
  }
9417
9465
  };
9418
9466
  agyBackend = new AgyBackend();
@@ -10608,7 +10656,7 @@ var init_backends = __esm({
10608
10656
  // ../engine/dist/transcript/shadow.js
10609
10657
  import { gzipSync as gzipSync2, gunzipSync as gunzipSync2 } from "zlib";
10610
10658
  import { createHash as createHash2 } from "crypto";
10611
- import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync2, renameSync as renameSync2, statSync as statSync10 } from "fs";
10659
+ import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync2, renameSync as renameSync2, statSync as statSync10, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
10612
10660
  import { dirname as dirname12, join as join14 } from "path";
10613
10661
  function shadowRoot() {
10614
10662
  return join14(getDataDir(), "shadow");
@@ -10832,6 +10880,29 @@ function seedShadow(sessionId, container) {
10832
10880
  }
10833
10881
  return fullest;
10834
10882
  }
10883
+ function shadowUncompressedBytes(tool, rawId) {
10884
+ const path = shadowFileFor(tool, rawId);
10885
+ let fd = null;
10886
+ try {
10887
+ const size = statSync10(path).size;
10888
+ if (size < 18)
10889
+ return 0;
10890
+ fd = openSync4(path, "r");
10891
+ const tail = Buffer.alloc(4);
10892
+ if (readSync4(fd, tail, 0, 4, size - 4) !== 4)
10893
+ return 0;
10894
+ return tail.readUInt32LE(0);
10895
+ } catch {
10896
+ return 0;
10897
+ } finally {
10898
+ if (fd !== null) {
10899
+ try {
10900
+ closeSync4(fd);
10901
+ } catch {
10902
+ }
10903
+ }
10904
+ }
10905
+ }
10835
10906
  function shadowSizeOnDisk(tool, rawId) {
10836
10907
  try {
10837
10908
  return statSync10(shadowFileFor(tool, rawId)).size;
@@ -10852,7 +10923,7 @@ var init_shadow = __esm({
10852
10923
  });
10853
10924
 
10854
10925
  // ../engine/dist/core/live-session-scan.js
10855
- import { existsSync as existsSync13, readdirSync as readdirSync10, readFileSync as readFileSync9, statSync as statSync11, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
10926
+ import { existsSync as existsSync13, readdirSync as readdirSync10, readFileSync as readFileSync9, statSync as statSync11, openSync as openSync5, readSync as readSync5, closeSync as closeSync5 } from "fs";
10856
10927
  import { join as join15, basename as basename9 } from "path";
10857
10928
  function detectTool(sessionId) {
10858
10929
  return getBackendForId(sessionId)?.id ?? "claude";
@@ -10879,6 +10950,15 @@ function sessionFileIndex() {
10879
10950
  return indexCache;
10880
10951
  const byId = /* @__PURE__ */ new Map();
10881
10952
  const seenRoots = /* @__PURE__ */ new Set();
10953
+ const projectPaths = /* @__PURE__ */ new Map();
10954
+ const projectPathOf = (dirName) => {
10955
+ let p = projectPaths.get(dirName);
10956
+ if (p === void 0) {
10957
+ p = resolveProjectDirName(dirName);
10958
+ projectPaths.set(dirName, p);
10959
+ }
10960
+ return p;
10961
+ };
10882
10962
  for (const root of claudeProjectDirs()) {
10883
10963
  if (seenRoots.has(root) || !existsSync13(root))
10884
10964
  continue;
@@ -10906,7 +10986,7 @@ function sessionFileIndex() {
10906
10986
  const located = {
10907
10987
  path: join15(dir, name),
10908
10988
  projectDir: entry.name,
10909
- projectPath: resolveProjectDirName(entry.name)
10989
+ projectPath: projectPathOf(entry.name)
10910
10990
  };
10911
10991
  const prior = byId.get(id);
10912
10992
  if (prior)
@@ -11096,7 +11176,7 @@ var init_live_session_scan = __esm({
11096
11176
  });
11097
11177
 
11098
11178
  // ../engine/dist/core/first-prompt.js
11099
- import { createReadStream as createReadStream2, existsSync as existsSync14, openSync as openSync5, readSync as readSync5, closeSync as closeSync5 } from "fs";
11179
+ import { createReadStream as createReadStream2, existsSync as existsSync14, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
11100
11180
  import { StringDecoder } from "string_decoder";
11101
11181
  function stripBanners2(s) {
11102
11182
  let out = s;
@@ -11150,7 +11230,7 @@ function extractFirstUserPromptSync(filePath, options = {}) {
11150
11230
  const maxBytes = options.maxBytes ?? 4 * 1024 * 1024;
11151
11231
  let fd;
11152
11232
  try {
11153
- fd = openSync5(filePath, "r");
11233
+ fd = openSync6(filePath, "r");
11154
11234
  } catch {
11155
11235
  return "";
11156
11236
  }
@@ -11162,7 +11242,7 @@ function extractFirstUserPromptSync(filePath, options = {}) {
11162
11242
  let pos = 0;
11163
11243
  let lineNo = 0;
11164
11244
  for (; ; ) {
11165
- const n = readSync5(fd, buf, 0, CHUNK, pos);
11245
+ const n = readSync6(fd, buf, 0, CHUNK, pos);
11166
11246
  if (n <= 0)
11167
11247
  break;
11168
11248
  pos += n;
@@ -11188,7 +11268,7 @@ function extractFirstUserPromptSync(filePath, options = {}) {
11188
11268
  return "";
11189
11269
  } finally {
11190
11270
  try {
11191
- closeSync5(fd);
11271
+ closeSync6(fd);
11192
11272
  } catch {
11193
11273
  }
11194
11274
  }
@@ -11207,7 +11287,7 @@ var init_first_prompt = __esm({
11207
11287
  });
11208
11288
 
11209
11289
  // ../engine/dist/core/backends/claude.js
11210
- import { existsSync as existsSync15, readdirSync as readdirSync11, readFileSync as readFileSync10, statSync as statSync12, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
11290
+ import { existsSync as existsSync15, readdirSync as readdirSync11, readFileSync as readFileSync10, statSync as statSync12, openSync as openSync7, readSync as readSync7, closeSync as closeSync7 } from "fs";
11211
11291
  import { join as join16, basename as basename10 } from "path";
11212
11292
  function stripReminders(text) {
11213
11293
  return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
@@ -11304,13 +11384,13 @@ var init_claude2 = __esm({
11304
11384
  return null;
11305
11385
  let head;
11306
11386
  try {
11307
- const fd = openSync6(located.path, "r");
11387
+ const fd = openSync7(located.path, "r");
11308
11388
  try {
11309
11389
  const buf = Buffer.alloc(65536);
11310
- const n = readSync6(fd, buf, 0, buf.length, 0);
11390
+ const n = readSync7(fd, buf, 0, buf.length, 0);
11311
11391
  head = buf.toString("utf-8", 0, n);
11312
11392
  } finally {
11313
- closeSync6(fd);
11393
+ closeSync7(fd);
11314
11394
  }
11315
11395
  } catch {
11316
11396
  return null;
@@ -11355,12 +11435,12 @@ var init_claude2 = __esm({
11355
11435
  const seen = /* @__PURE__ */ new Set();
11356
11436
  const out = [];
11357
11437
  for (const root of claudeProjectDirs()) {
11358
- this.collectSessionsFromRoot(root, cutoff, filter, seen, out);
11438
+ this.collectSessionsFromRoot(root, cutoff, filter, seen, out, opts.previews !== false);
11359
11439
  }
11360
11440
  out.sort((a, b) => b.mtime - a.mtime);
11361
11441
  return opts.limit ? out.slice(0, opts.limit) : out;
11362
11442
  }
11363
- collectSessionsFromRoot(root, cutoff, filter, seen, out) {
11443
+ collectSessionsFromRoot(root, cutoff, filter, seen, out, previews) {
11364
11444
  if (!existsSync15(root))
11365
11445
  return;
11366
11446
  for (const proj of readdirSync11(root, { withFileTypes: true })) {
@@ -11419,7 +11499,7 @@ var init_claude2 = __esm({
11419
11499
  }
11420
11500
  if (stat3.mtimeMs < cutoff)
11421
11501
  continue;
11422
- const firstPrompt = extractFirstUserPromptSync(fullPath, { maxLength: 200 });
11502
+ const firstPrompt = previews ? extractFirstUserPromptSync(fullPath, { maxLength: 200 }) : "";
11423
11503
  out.push({
11424
11504
  toolId: "claude",
11425
11505
  rawId: sessionId,
@@ -11782,14 +11862,14 @@ var init_claude2 = __esm({
11782
11862
  spansMultipleSources(prefixedId) {
11783
11863
  return findSessionFiles(this.toRawId(prefixedId)).length > 1;
11784
11864
  }
11785
- async readFromOffset(prefixedId, offset) {
11865
+ async readFromOffset(prefixedId, offset, maxBytes) {
11786
11866
  const rawId = this.toRawId(prefixedId);
11787
11867
  const copies = findSessionFiles(rawId);
11788
11868
  if (copies.length === 0)
11789
11869
  return { text: "", newOffset: offset };
11790
11870
  if (copies.length > 1)
11791
11871
  return { text: "", newOffset: offset };
11792
- return readTailFromOffset(copies[0].path, offset);
11872
+ return readTailFromOffset(copies[0].path, offset, maxBytes);
11793
11873
  }
11794
11874
  };
11795
11875
  claudeBackend = new ClaudeBackend();
@@ -11839,6 +11919,7 @@ function loadProjectsConfig() {
11839
11919
  }
11840
11920
  function resetProjectResolverCache() {
11841
11921
  resolveCache.clear();
11922
+ inputCache.clear();
11842
11923
  gitCache.clear();
11843
11924
  workspaceCache.clear();
11844
11925
  wsIdCache.clear();
@@ -11855,12 +11936,13 @@ function resolveProjectId(inputPath) {
11855
11936
  if (!isAbsolute(inputPath)) {
11856
11937
  return { id: `path:${inputPath}`, displayName: inputPath, source: "path" };
11857
11938
  }
11939
+ const known = inputCache.get(inputPath);
11940
+ if (known)
11941
+ return known;
11858
11942
  const realPath = safeRealpath(inputPath);
11859
- const cached = resolveCache.get(realPath);
11860
- if (cached)
11861
- return cached;
11862
- const result = doResolve(realPath);
11943
+ const result = resolveCache.get(realPath) ?? doResolve(realPath);
11863
11944
  resolveCache.set(realPath, result);
11945
+ inputCache.set(inputPath, result);
11864
11946
  return result;
11865
11947
  }
11866
11948
  function resolveWorkspaceId(inputPath) {
@@ -12161,7 +12243,7 @@ function globToRegex(glob) {
12161
12243
  function escapeRe(s) {
12162
12244
  return s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
12163
12245
  }
12164
- var DEFAULT_AUTO_WORKSPACE_MIN_REPOS, BOT_WORKTREE_MARKERS, resolveCache, gitCache, workspaceCache, wsIdCache, cachedConfig, GIT_TIMEOUT_MS, AUTO_WORKSPACE_MAX_DEPTH;
12246
+ var DEFAULT_AUTO_WORKSPACE_MIN_REPOS, BOT_WORKTREE_MARKERS, resolveCache, inputCache, gitCache, workspaceCache, wsIdCache, cachedConfig, GIT_TIMEOUT_MS, AUTO_WORKSPACE_MAX_DEPTH;
12165
12247
  var init_project_resolver = __esm({
12166
12248
  "../engine/dist/core/project-resolver.js"() {
12167
12249
  "use strict";
@@ -12169,6 +12251,7 @@ var init_project_resolver = __esm({
12169
12251
  DEFAULT_AUTO_WORKSPACE_MIN_REPOS = 3;
12170
12252
  BOT_WORKTREE_MARKERS = [".claude-pr-bot"];
12171
12253
  resolveCache = /* @__PURE__ */ new Map();
12254
+ inputCache = /* @__PURE__ */ new Map();
12172
12255
  gitCache = /* @__PURE__ */ new Map();
12173
12256
  workspaceCache = /* @__PURE__ */ new Map();
12174
12257
  wsIdCache = /* @__PURE__ */ new Map();
@@ -13742,10 +13825,10 @@ var init_types = __esm({
13742
13825
 
13743
13826
  // ../engine/dist/transcript/opencode.js
13744
13827
  async function parseOpenCodeSubagents(parentSessionId) {
13745
- const { existsSync: existsSync57 } = await import("fs");
13828
+ const { existsSync: existsSync58 } = await import("fs");
13746
13829
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
13747
13830
  const dbPath = opencodeBackend2.dbPath();
13748
- if (!existsSync57(dbPath))
13831
+ if (!existsSync58(dbPath))
13749
13832
  return [];
13750
13833
  const realParent = parentSessionId.replace("opencode_", "");
13751
13834
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -13786,10 +13869,10 @@ async function parseOpenCodeSubagents(parentSessionId) {
13786
13869
  }
13787
13870
  }
13788
13871
  async function parseOpenCodeTranscript(sessionId) {
13789
- const { existsSync: existsSync57 } = await import("fs");
13872
+ const { existsSync: existsSync58 } = await import("fs");
13790
13873
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
13791
13874
  const dbPath = opencodeBackend2.dbPath();
13792
- if (!existsSync57(dbPath))
13875
+ if (!existsSync58(dbPath))
13793
13876
  return [];
13794
13877
  const realId = sessionId.replace("opencode_", "");
13795
13878
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -13866,6 +13949,7 @@ __export(transcript_exports, {
13866
13949
  shadowFileFor: () => shadowFileFor,
13867
13950
  shadowRoot: () => shadowRoot,
13868
13951
  shadowSizeOnDisk: () => shadowSizeOnDisk,
13952
+ shadowUncompressedBytes: () => shadowUncompressedBytes,
13869
13953
  snapshotShadow: () => snapshotShadow,
13870
13954
  trimTranscriptForSync: () => trimTranscriptForSync,
13871
13955
  updateShadow: () => updateShadow,
@@ -18335,7 +18419,7 @@ var init_sync_fields = __esm({
18335
18419
  });
18336
18420
 
18337
18421
  // ../engine/dist/core/index-lock.js
18338
- import { existsSync as existsSync39, mkdirSync as mkdirSync7, openSync as openSync7, writeFileSync as writeFileSync6, readFileSync as readFileSync30, rmSync as rmSync4, statSync as statSync35, closeSync as closeSync7 } from "fs";
18422
+ import { existsSync as existsSync39, mkdirSync as mkdirSync7, openSync as openSync8, writeFileSync as writeFileSync6, readFileSync as readFileSync30, rmSync as rmSync4, statSync as statSync35, closeSync as closeSync8 } from "fs";
18339
18423
  import { dirname as dirname16, join as join39 } from "path";
18340
18424
  function acquireIndexLock(opts) {
18341
18425
  const lockPath = lockFilePath();
@@ -18347,7 +18431,7 @@ function acquireIndexLock(opts) {
18347
18431
  }
18348
18432
  let fd;
18349
18433
  try {
18350
- fd = openSync7(lockPath, "wx", 384);
18434
+ fd = openSync8(lockPath, "wx", 384);
18351
18435
  } catch (err) {
18352
18436
  if (err.code === "EEXIST")
18353
18437
  return null;
@@ -18357,7 +18441,7 @@ function acquireIndexLock(opts) {
18357
18441
  try {
18358
18442
  writeFileSync6(fd, JSON.stringify(holder));
18359
18443
  } finally {
18360
- closeSync7(fd);
18444
+ closeSync8(fd);
18361
18445
  }
18362
18446
  let released = false;
18363
18447
  return {
@@ -18457,7 +18541,7 @@ var init_http = __esm({
18457
18541
  CLI_VERSION = JSON.parse(readFileSync31(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
18458
18542
  } catch {
18459
18543
  try {
18460
- if (true) CLI_VERSION = "0.7.0";
18544
+ if (true) CLI_VERSION = "0.7.2";
18461
18545
  } catch {
18462
18546
  }
18463
18547
  }
@@ -18956,7 +19040,7 @@ var init_telemetry = __esm({
18956
19040
  init_sync_client();
18957
19041
  cliVersion2 = () => {
18958
19042
  try {
18959
- return true ? "0.7.0" : "dev";
19043
+ return true ? "0.7.2" : "dev";
18960
19044
  } catch {
18961
19045
  return "dev";
18962
19046
  }
@@ -19312,16 +19396,17 @@ __export(auto_update_exports, {
19312
19396
  noteUpdateFailure: () => noteUpdateFailure,
19313
19397
  planAutoUpdate: () => planAutoUpdate,
19314
19398
  readUpdateState: () => readUpdateState,
19399
+ resolveNpm: () => resolveNpm,
19315
19400
  runAutoUpdate: () => runAutoUpdate,
19316
19401
  runningPrefix: () => runningPrefix,
19317
19402
  sweepStaleStaging: () => sweepStaleStaging,
19318
19403
  tarballUrl: () => tarballUrl
19319
19404
  });
19320
- import { execSync as execSync2 } from "node:child_process";
19321
- import { readFileSync as readFileSync34 } from "node:fs";
19405
+ import { execSync as execSync2, execFileSync as execFileSync3 } from "node:child_process";
19406
+ import { readFileSync as readFileSync34, existsSync as existsSync41 } from "node:fs";
19322
19407
  import { fileURLToPath as fileURLToPath2 } from "node:url";
19323
19408
  import { writeFileSync as writeFileSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync5, readdirSync as readdirSync31, statSync as statSync36, mkdirSync as mkdirSync9 } from "node:fs";
19324
- import { join as join41, dirname as dirname18, sep as sep2 } from "node:path";
19409
+ import { join as join41, dirname as dirname18, sep as sep2, posix as pathPosix, win32 as pathWin32 } from "node:path";
19325
19410
  import { tmpdir as tmpdir2, homedir as homedir15 } from "node:os";
19326
19411
  import { createHash as createHash8 } from "node:crypto";
19327
19412
  function compareVersions(a, b) {
@@ -19436,25 +19521,46 @@ function runningPrefix(moduleUrl = import.meta.url) {
19436
19521
  const beforeNodeModules = here.slice(0, at);
19437
19522
  return beforeNodeModules.endsWith(`${sep2}lib`) ? beforeNodeModules.slice(0, -4) : beforeNodeModules;
19438
19523
  }
19439
- function npmInstall(cmd) {
19524
+ function npmInstall(npm, args) {
19440
19525
  try {
19441
- execSync2(cmd, { stdio: ["ignore", "ignore", "pipe"] });
19526
+ const opts = { stdio: ["ignore", "ignore", "pipe"], env: npm.env };
19527
+ if (npm.shell) execSync2([npm.file, ...npm.args, ...args].map((a) => `"${a}"`).join(" "), opts);
19528
+ else execFileSync3(npm.file, [...npm.args, ...args], opts);
19442
19529
  } catch (e) {
19443
19530
  const err = e;
19444
19531
  const detail = String(err.stderr ?? "").trim().split("\n").filter((l) => l.trim() && !/^npm (notice|warn)\b/i.test(l)).slice(-4).join(" | ").slice(0, 400);
19445
19532
  throw new Error(detail ? `${err.message ?? "install failed"} \u2014 ${detail}` : err.message ?? "install failed");
19446
19533
  }
19447
19534
  }
19535
+ function resolveNpm(loc) {
19536
+ const win = loc.platform === "win32";
19537
+ const p = win ? pathWin32 : pathPosix;
19538
+ const nodeDir = p.dirname(loc.execPath);
19539
+ const cliUnder = (root) => win ? p.join(root, "node_modules", "npm", "bin", "npm-cli.js") : p.join(root, "lib", "node_modules", "npm", "bin", "npm-cli.js");
19540
+ const candidates2 = [
19541
+ cliUnder(win ? nodeDir : p.join(nodeDir, "..")),
19542
+ ...loc.prefix ? [cliUnder(loc.prefix)] : []
19543
+ ];
19544
+ const pathKey = Object.keys(loc.env).find((k) => k.toUpperCase() === "PATH") ?? "PATH";
19545
+ const binDirs = [nodeDir, ...loc.prefix ? [win ? loc.prefix : p.join(loc.prefix, "bin")] : []];
19546
+ const delimiter3 = win ? ";" : ":";
19547
+ const current = loc.env[pathKey] ?? "";
19548
+ const env = { ...loc.env, [pathKey]: [...binDirs, ...current ? [current] : []].join(delimiter3) };
19549
+ const cli = candidates2.find((c) => loc.exists(c));
19550
+ if (cli) return { file: loc.execPath, args: [cli], env, shell: false };
19551
+ return { file: "npm", args: [], env, shell: true };
19552
+ }
19448
19553
  function realInstall(tgz) {
19449
19554
  const prefix = runningPrefix();
19555
+ const npm = resolveNpm({ execPath: process.execPath, prefix, platform: process.platform, env: process.env, exists: existsSync41 });
19450
19556
  if (prefix) {
19451
- npmInstall(`npm install -g --prefix "${prefix}" "${tgz}"`);
19557
+ npmInstall(npm, ["install", "-g", "--prefix", prefix, tgz]);
19452
19558
  return;
19453
19559
  }
19454
19560
  try {
19455
- npmInstall(`npm install -g "${tgz}"`);
19561
+ npmInstall(npm, ["install", "-g", tgz]);
19456
19562
  } catch {
19457
- npmInstall(`npm install -g --prefix "${process.env.HOME}/.local" "${tgz}"`);
19563
+ npmInstall(npm, ["install", "-g", "--prefix", join41(homedir15(), ".local"), tgz]);
19458
19564
  }
19459
19565
  }
19460
19566
  function realRestart(platform2) {
@@ -19555,7 +19661,9 @@ var init_auto_update = __esm({
19555
19661
  var sync_client_exports = {};
19556
19662
  __export(sync_client_exports, {
19557
19663
  BATCH_SCAN_MAX_BYTES: () => BATCH_SCAN_MAX_BYTES,
19664
+ DERIVED_MAX_BYTES: () => DERIVED_MAX_BYTES,
19558
19665
  FULL_BUILD_MAX_BYTES: () => FULL_BUILD_MAX_BYTES,
19666
+ SYNC_CHUNK_BYTES: () => SYNC_CHUNK_BYTES,
19559
19667
  _acceptedUploads: () => _acceptedUploads,
19560
19668
  _fetchTenantSyncConfigForTests: () => _fetchTenantSyncConfigForTests,
19561
19669
  _resetCapsCacheForTests: () => _resetCapsCacheForTests,
@@ -19574,8 +19682,10 @@ __export(sync_client_exports, {
19574
19682
  reconcileFields: () => reconcileFields,
19575
19683
  removeCredentials: () => removeCredentials,
19576
19684
  saveCredentials: () => saveCredentials,
19685
+ scanTranscriptFindingsChunked: () => scanTranscriptFindingsChunked,
19577
19686
  serverAcceptsGzipBody: () => serverAcceptsGzipBody,
19578
19687
  serverIngestConcurrency: () => serverIngestConcurrency,
19688
+ sessionBuildBytes: () => sessionBuildBytes,
19579
19689
  sessionFileBytes: () => sessionFileBytes,
19580
19690
  setServerIngestConcurrency: () => setServerIngestConcurrency,
19581
19691
  syncIncremental: () => syncIncremental,
@@ -19965,7 +20075,7 @@ async function reconcileFields(opts = {}) {
19965
20075
  let cached = null;
19966
20076
  const fullRefs = () => cached ??= listAvailableBackends().flatMap((b) => {
19967
20077
  try {
19968
- return b.listSessions({});
20078
+ return b.listSessions({ previews: false });
19969
20079
  } catch {
19970
20080
  return [];
19971
20081
  }
@@ -20128,7 +20238,7 @@ async function syncToTargetInScope(cred, opts = {}) {
20128
20238
  const listSinceMs = changedWalk ? Math.max(0, (opts.sinceMs ?? 0) - CHANGED_WALK_OVERLAP_MS) : opts.useLedger ? void 0 : opts.sinceMs;
20129
20239
  const refs = listAvailableBackends().flatMap((b) => {
20130
20240
  try {
20131
- return b.listSessions({ sinceMs: listSinceMs });
20241
+ return b.listSessions({ sinceMs: listSinceMs, previews: false });
20132
20242
  } catch {
20133
20243
  return [];
20134
20244
  }
@@ -20347,16 +20457,53 @@ async function syncToTargetInScope(cred, opts = {}) {
20347
20457
  clearTimeout(timer);
20348
20458
  }
20349
20459
  };
20460
+ const postChunkedHead = async (conv, fileSize) => {
20461
+ const headers = { "content-type": "application/json" };
20462
+ if (cred.token) headers.authorization = `Bearer ${cred.token}`;
20463
+ const ac = new AbortController();
20464
+ const timer = setTimeout(() => ac.abort(new Error(`upload timed out after ${UPLOAD_TIMEOUT_MS}ms`)), UPLOAD_TIMEOUT_MS);
20465
+ try {
20466
+ const res = await fetch(`${base3}/api/sync`, {
20467
+ method: "POST",
20468
+ headers,
20469
+ body: JSON.stringify({ conversations: [conv] }),
20470
+ signal: ac.signal
20471
+ });
20472
+ if (!res.ok) throw new Error(`chunked head sync failed: HTTP ${res.status} ${await res.text().catch(() => "")}`);
20473
+ const body = await res.json().catch(() => ({}));
20474
+ const id = conv.session_id;
20475
+ const guarded = (body.shrink_guarded ?? []).find((g) => g.session_id === id);
20476
+ const mtime = conv.mtime;
20477
+ try {
20478
+ if (!guarded) {
20479
+ markSynced(base3, [{ id, mtime, offset: conv.from_offset, size: conv.from_offset, acked: true }]);
20480
+ } else if (typeof guarded.o === "number" && guarded.o > 0 && guarded.o <= fileSize) {
20481
+ markSynced(base3, [{ id, mtime, offset: guarded.o, size: guarded.o, acked: true }]);
20482
+ } else {
20483
+ markSynced(base3, [{ id, mtime }]);
20484
+ }
20485
+ } catch {
20486
+ }
20487
+ appendResults.uploaded++;
20488
+ } finally {
20489
+ clearTimeout(timer);
20490
+ }
20491
+ };
20350
20492
  let appendBatch = [];
20493
+ let appendBatchBytes = 0;
20351
20494
  const APPEND_BATCH_SIZE = 50;
20495
+ const APPEND_BATCH_MAX_BYTES = SYNC_CHUNK_BYTES;
20352
20496
  const appendFlush = async () => {
20353
20497
  if (appendBatch.length === 0) return;
20354
20498
  const batch = appendBatch;
20355
20499
  appendBatch = [];
20500
+ appendBatchBytes = 0;
20356
20501
  await postAppend(batch);
20357
20502
  };
20358
- const appendAdd = async (conv) => {
20503
+ const appendAdd = async (conv, bytes = 0) => {
20504
+ if (appendBatch.length > 0 && appendBatchBytes + bytes > APPEND_BATCH_MAX_BYTES) await appendFlush();
20359
20505
  appendBatch.push(conv);
20506
+ appendBatchBytes += bytes;
20360
20507
  if (appendBatch.length >= APPEND_BATCH_SIZE) await appendFlush();
20361
20508
  };
20362
20509
  const itemsBatch = makeBatcher("items", 100);
@@ -20406,7 +20553,7 @@ async function syncToTargetInScope(cred, opts = {}) {
20406
20553
  cred,
20407
20554
  () => listAvailableBackends().flatMap((b) => {
20408
20555
  try {
20409
- return b.listSessions({});
20556
+ return b.listSessions({ previews: false });
20410
20557
  } catch {
20411
20558
  return [];
20412
20559
  }
@@ -20435,7 +20582,7 @@ async function syncToTargetInScope(cred, opts = {}) {
20435
20582
  let externalFindings = null;
20436
20583
  if (scanSecrets) {
20437
20584
  externalFindings = /* @__PURE__ */ new Map();
20438
- const toScan = slice.filter((ref) => modeOf(ref) === "full" && sessionFileBytes(ref) <= FULL_BUILD_MAX_BYTES);
20585
+ const toScan = slice.filter((ref) => modeOf(ref) === "full" && sessionBuildBytes(ref) <= FULL_BUILD_MAX_BYTES);
20439
20586
  trace?.(`batch secret scan start: ${toScan.length} session(s), \u2264${(BATCH_SCAN_MAX_BYTES / 1048576).toFixed(0)}MB per slice`);
20440
20587
  const batch = batchScanExternal(
20441
20588
  toScan.map((ref) => ({
@@ -20479,11 +20626,17 @@ async function syncToTargetInScope(cred, opts = {}) {
20479
20626
  const ack = ledger.get(ref.prefixedId);
20480
20627
  try {
20481
20628
  const tail = await buildConversationTail(ref, ack.o ?? 0, { mapPath, tenantRules });
20482
- if (tail && tail.findings.length > 0) {
20629
+ const chunked = sessionBuildBytes(ref) > FULL_BUILD_MAX_BYTES;
20630
+ if (tail && tail.findings.length > 0 && !chunked) {
20483
20631
  promotedForFindings = true;
20484
20632
  } else if (tail) {
20485
20633
  redactions += tail.redactions;
20486
- await appendAdd(tail.conv);
20634
+ await appendAdd(tail.conv, tail.bytes);
20635
+ if (tail.findings.length > 0) {
20636
+ await appendFlush();
20637
+ const all = await scanTranscriptFindingsChunked(ref, tenantRules);
20638
+ if (all.length > 0) await findingsBatch.add(all);
20639
+ }
20487
20640
  walked++;
20488
20641
  if (walked % 250 === 0) console.error(`[sync] ${walked}/${slice.length} sessions\u2026`);
20489
20642
  continue;
@@ -20531,12 +20684,13 @@ async function syncToTargetInScope(cred, opts = {}) {
20531
20684
  }
20532
20685
  redactions += built.redactions;
20533
20686
  if (built.srcHash) shippedHash.set(ref.prefixedId, built.srcHash);
20534
- await convBatch.add(built.conv);
20687
+ if (built.conv.chunked) await postChunkedHead(built.conv, sessionFileBytes(ref));
20688
+ else await convBatch.add(built.conv);
20535
20689
  if (built.findings.length > 0) await findingsBatch.add(built.findings);
20536
20690
  if (upload.sessionMeta) {
20537
20691
  await accumulateKg(extractEntities(built.kgText, { projectPath: built.projectPath, sourceType: "session", sessionId: ref.prefixedId, validFrom: ref.mtime ? new Date(ref.mtime).toISOString().slice(0, 10) : void 0 }), ref.prefixedId);
20538
20692
  }
20539
- if (upload.sessionMeta && sessionFileBytes(ref) <= FULL_BUILD_MAX_BYTES) {
20693
+ if (upload.sessionMeta && sessionFileBytes(ref) <= DERIVED_MAX_BYTES) {
20540
20694
  try {
20541
20695
  const count = { redactions: 0 };
20542
20696
  const offloaded = await scanPool().runDerived({
@@ -20716,35 +20870,70 @@ async function syncToTargetInScope(cred, opts = {}) {
20716
20870
  scanMs: Math.round(scanMs)
20717
20871
  };
20718
20872
  }
20873
+ async function scanTranscriptFindingsChunked(ref, tenantRules) {
20874
+ const backend = getBackendForId(ref.prefixedId);
20875
+ const findings = [];
20876
+ if (!backend?.readFromOffset) return findings;
20877
+ let offset = 0;
20878
+ let lineBase = 0;
20879
+ for (; ; ) {
20880
+ const chunk = await backend.readFromOffset(ref.prefixedId, offset, SYNC_CHUNK_BYTES);
20881
+ if (chunk.newOffset <= offset) break;
20882
+ offset = chunk.newOffset;
20883
+ if (chunk.skippedBytes) {
20884
+ lineBase += 1;
20885
+ continue;
20886
+ }
20887
+ try {
20888
+ for (const f of scanTextForFindings(chunk.text)) {
20889
+ findings.push({ session_id: ref.prefixedId, detector: "builtin", rule: f.rule, line: lineBase + f.line, preview: f.preview });
20890
+ }
20891
+ if (tenantRules && tenantRules.length > 0) {
20892
+ for (const f of scanTenantRules(chunk.text, tenantRules)) {
20893
+ findings.push({ session_id: ref.prefixedId, detector: f.detector, rule: f.rule, line: lineBase + f.line, preview: f.preview });
20894
+ }
20895
+ }
20896
+ } catch {
20897
+ }
20898
+ for (let i = chunk.text.indexOf("\n"); i >= 0; i = chunk.text.indexOf("\n", i + 1)) lineBase++;
20899
+ }
20900
+ return dropFuzzyFindings(findings, (f) => ({ detector: f.detector, rule: f.rule }));
20901
+ }
20719
20902
  function sessionFileBytes(ref) {
20720
20903
  const backend = getBackendForId(ref.prefixedId);
20721
20904
  if (!backend?.isAppendOnly?.()) return 0;
20722
20905
  return backend.fileSize?.(ref.prefixedId) ?? 0;
20723
20906
  }
20907
+ function sessionBuildBytes(ref) {
20908
+ const main = sessionFileBytes(ref);
20909
+ if (main === 0) return 0;
20910
+ return Math.max(main, shadowUncompressedBytes(ref.toolId, ref.rawId));
20911
+ }
20724
20912
  async function buildConversationSync(ref, mtime, opts = {}) {
20725
20913
  const mapPath = opts.mapPath ?? ((p) => p);
20726
20914
  let includeRaw = opts.includeRaw !== false;
20727
20915
  let includeMeta = opts.includeMeta !== false;
20728
20916
  let srcHash;
20729
- const fileBytes = sessionFileBytes(ref);
20730
- const oversized = fileBytes > FULL_BUILD_MAX_BYTES;
20731
- let tailText = "";
20732
- let tailOffsetEnd = 0;
20917
+ const oversized = sessionBuildBytes(ref) > FULL_BUILD_MAX_BYTES;
20918
+ let headOffsetEnd = 0;
20919
+ let oversizedFindings = null;
20733
20920
  let transcript = null;
20734
20921
  let fullestContainer = null;
20735
20922
  if (oversized) {
20736
20923
  const backend = getBackendForId(ref.prefixedId);
20737
20924
  if (!backend?.readFromOffset) return null;
20738
20925
  try {
20739
- const tail = await backend.readFromOffset(ref.prefixedId, fileBytes - FULL_BUILD_TAIL_BYTES);
20740
- tailText = tail.text;
20741
- tailOffsetEnd = tail.newOffset;
20742
- const messages = parseTailMessages(ref.toolId, tailText);
20926
+ let head = await backend.readFromOffset(ref.prefixedId, 0, SYNC_CHUNK_BYTES);
20927
+ for (let hops = 0; head.skippedBytes && hops < 8; hops++) {
20928
+ head = await backend.readFromOffset(ref.prefixedId, head.newOffset, SYNC_CHUNK_BYTES);
20929
+ }
20930
+ headOffsetEnd = head.newOffset;
20931
+ const messages = parseTailMessages(ref.toolId, head.text);
20743
20932
  if (messages.length > 0) transcript = { messages, subagents: [] };
20744
20933
  } catch {
20745
20934
  }
20746
20935
  includeRaw = false;
20747
- includeMeta = false;
20936
+ oversizedFindings = await scanTranscriptFindingsChunked(ref, opts.tenantRules);
20748
20937
  } else {
20749
20938
  try {
20750
20939
  const backend = getBackendForId(ref.prefixedId) ?? getBackend("claude");
@@ -20805,7 +20994,6 @@ async function buildConversationSync(ref, mtime, opts = {}) {
20805
20994
  );
20806
20995
  let projectPath = ref.projectPath;
20807
20996
  const meta = { messageCount: textMessages.length };
20808
- if (oversized) meta.truncated = true;
20809
20997
  if (textMessages.filter((m) => m.role === "user").length <= 1) meta.oneShot = true;
20810
20998
  try {
20811
20999
  const cwd = ref.fullPath ? readCwdFromJsonl(ref.fullPath) : "";
@@ -20919,21 +21107,7 @@ async function buildConversationSync(ref, mtime, opts = {}) {
20919
21107
  }
20920
21108
  scanMs = performance.now() - t0;
20921
21109
  }
20922
- if (oversized && tailText) {
20923
- const t0 = performance.now();
20924
- try {
20925
- for (const f of scanTextForFindings(tailText)) {
20926
- findings.push({ session_id: ref.prefixedId, detector: "builtin", rule: f.rule, line: f.line, preview: f.preview });
20927
- }
20928
- if (opts.tenantRules && opts.tenantRules.length > 0) {
20929
- for (const f of scanTenantRules(tailText, opts.tenantRules)) {
20930
- findings.push({ session_id: ref.prefixedId, detector: f.detector, rule: f.rule, line: f.line, preview: f.preview });
20931
- }
20932
- }
20933
- } catch {
20934
- }
20935
- scanMs = performance.now() - t0;
20936
- }
21110
+ if (oversizedFindings) findings.push(...oversizedFindings);
20937
21111
  const keptFindings = dropFuzzyFindings(findings, (f) => ({ detector: f.detector, rule: f.rule }));
20938
21112
  findings.length = 0;
20939
21113
  findings.push(...keptFindings);
@@ -20979,7 +21153,10 @@ async function buildConversationSync(ref, mtime, opts = {}) {
20979
21153
  // (convBatch onFlush uses this, not a recomputed size) so the next
20980
21154
  // append's base_offset matches exactly — no spurious continuity misses on
20981
21155
  // an actively-growing session. undefined for non-append-only backends.
20982
- from_offset: oversized ? tailOffsetEnd : getBackendForId(ref.prefixedId)?.fileSize?.(ref.rawId),
21156
+ from_offset: oversized ? headOffsetEnd : getBackendForId(ref.prefixedId)?.fileSize?.(ref.rawId),
21157
+ // The head of a chunked session. The server reports back when its shrink
21158
+ // guard keeps a fuller stored copy, so the client appends from there.
21159
+ ...oversized ? { chunked: true } : {},
20983
21160
  meta,
20984
21161
  mtime
20985
21162
  },
@@ -21001,12 +21178,30 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
21001
21178
  const backend = getBackendForId(ref.prefixedId);
21002
21179
  if (!backend?.readFromOffset) return null;
21003
21180
  const mapPath = opts.mapPath ?? ((p) => p);
21004
- const { text: tailText, newOffset } = await backend.readFromOffset(ref.prefixedId, fromOffset);
21005
- if (!tailText.trim()) return null;
21006
- const messages = parseTailMessages(ref.toolId, tailText);
21007
- if (messages.length === 0) return null;
21181
+ const { text: tailText, newOffset, skippedBytes } = await backend.readFromOffset(ref.prefixedId, fromOffset, SYNC_CHUNK_BYTES);
21182
+ if (newOffset <= fromOffset) return null;
21183
+ const messages = skippedBytes ? [] : parseTailMessages(ref.toolId, tailText);
21008
21184
  const textMessages = messages.filter((m) => m.role !== "summary" && m.content?.trim());
21009
- if (textMessages.length === 0 && !messages.some((m) => m.toolCalls?.length)) return null;
21185
+ const hasContent = textMessages.length > 0 || messages.some((m) => m.toolCalls?.length);
21186
+ if (!hasContent) {
21187
+ return {
21188
+ conv: {
21189
+ session_id: ref.prefixedId,
21190
+ tool: ref.toolId,
21191
+ project_path: mapPath(ref.projectPath),
21192
+ append: true,
21193
+ base_offset: fromOffset,
21194
+ from_offset: newOffset,
21195
+ redacted_text: "",
21196
+ envelope: { v: TRANSCRIPT_VERSION, messages: [], subagents: [] },
21197
+ mtime: Math.floor(ref.mtime) || 0
21198
+ },
21199
+ redactions: 0,
21200
+ newOffset,
21201
+ findings: [],
21202
+ bytes: 0
21203
+ };
21204
+ }
21010
21205
  const firstUser = textMessages.find((m) => m.role === "user");
21011
21206
  if (isInternalToolPrompt(firstUser?.content)) return null;
21012
21207
  const count = { redactions: 0 };
@@ -21049,7 +21244,8 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
21049
21244
  },
21050
21245
  redactions: count.redactions,
21051
21246
  newOffset,
21052
- findings
21247
+ findings,
21248
+ bytes: Buffer.byteLength(tailText)
21053
21249
  };
21054
21250
  }
21055
21251
  function isSyncSkip(r) {
@@ -21079,7 +21275,7 @@ async function syncIncremental(opts = {}) {
21079
21275
  lock.release();
21080
21276
  }
21081
21277
  }
21082
- var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, SYNC_RULES_REFUSAL, hashPath, acceptedUploads, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
21278
+ var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, SYNC_RULES_REFUSAL, hashPath, acceptedUploads, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, DERIVED_MAX_BYTES, SYNC_CHUNK_BYTES, BATCH_SCAN_MAX_BYTES;
21083
21279
  var init_sync_client = __esm({
21084
21280
  "src/sync-client.ts"() {
21085
21281
  "use strict";
@@ -21124,7 +21320,7 @@ var init_sync_client = __esm({
21124
21320
  ownVersion = JSON.parse(readFileSync35(join42(dir, "../package.json"), "utf-8")).version || ownVersion;
21125
21321
  } catch {
21126
21322
  try {
21127
- if (true) ownVersion = "0.7.0";
21323
+ if (true) ownVersion = "0.7.2";
21128
21324
  } catch {
21129
21325
  }
21130
21326
  }
@@ -21151,8 +21347,9 @@ var init_sync_client = __esm({
21151
21347
  capsOk = /* @__PURE__ */ new Map();
21152
21348
  advertisedIngestConc = /* @__PURE__ */ new Map();
21153
21349
  advertisedGzipBody = /* @__PURE__ */ new Set();
21154
- FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "64", 10)) * 1024 * 1024;
21155
- FULL_BUILD_TAIL_BYTES = 16 * 1024 * 1024;
21350
+ FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "24", 10)) * 1024 * 1024;
21351
+ DERIVED_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_DERIVED_MAX_MB || "64", 10)) * 1024 * 1024;
21352
+ SYNC_CHUNK_BYTES = 8 * 1024 * 1024;
21156
21353
  BATCH_SCAN_MAX_BYTES = Math.max(1, parseInt(process.env.CHAT_RECALL_BATCHSCAN_MAX_MB || "64", 10)) * 1024 * 1024;
21157
21354
  }
21158
21355
  });
@@ -21273,7 +21470,7 @@ var init_update_notice = __esm({
21273
21470
  });
21274
21471
 
21275
21472
  // ../engine/dist/core/which.js
21276
- import { existsSync as existsSync41, statSync as statSync38, accessSync, constants } from "node:fs";
21473
+ import { existsSync as existsSync42, statSync as statSync38, accessSync, constants } from "node:fs";
21277
21474
  import { join as join44, isAbsolute as isAbsolute2, delimiter as delimiter2, sep as sep3 } from "node:path";
21278
21475
  function executable(p) {
21279
21476
  try {
@@ -21314,7 +21511,7 @@ function resolveOnPath(name) {
21314
21511
  continue;
21315
21512
  for (const c of candidates(name)) {
21316
21513
  const full = join44(dir, c);
21317
- if (existsSync41(full) && executable(full))
21514
+ if (existsSync42(full) && executable(full))
21318
21515
  return full;
21319
21516
  }
21320
21517
  }
@@ -21377,7 +21574,7 @@ __export(mcp_clients_exports, {
21377
21574
  inspectMcpClients: () => inspectMcpClients,
21378
21575
  registerMcpEverywhere: () => registerMcpEverywhere
21379
21576
  });
21380
- import { existsSync as existsSync43, mkdirSync as mkdirSync12, readFileSync as readFileSync37, writeFileSync as writeFileSync12 } from "fs";
21577
+ import { existsSync as existsSync44, mkdirSync as mkdirSync12, readFileSync as readFileSync37, writeFileSync as writeFileSync12 } from "fs";
21381
21578
  import { homedir as homedir16 } from "os";
21382
21579
  import { dirname as dirname21, join as join46 } from "path";
21383
21580
  function envHome(varName, fallback) {
@@ -21396,7 +21593,7 @@ function readJsonObject(path) {
21396
21593
  }
21397
21594
  function registerMcpServersJson(target, path, spec) {
21398
21595
  let cfg = {};
21399
- if (existsSync43(path)) {
21596
+ if (existsSync44(path)) {
21400
21597
  try {
21401
21598
  cfg = readJsonObject(path);
21402
21599
  } catch (err) {
@@ -21425,7 +21622,7 @@ function registerOpencodeJson(path, spec) {
21425
21622
  const id = "opencode";
21426
21623
  const label = "OpenCode";
21427
21624
  let cfg = {};
21428
- if (existsSync43(path)) {
21625
+ if (existsSync44(path)) {
21429
21626
  try {
21430
21627
  cfg = readJsonObject(path);
21431
21628
  } catch (err) {
@@ -21464,7 +21661,7 @@ function registerCodexToml(path, spec) {
21464
21661
  const id = "codex";
21465
21662
  const label = "Codex";
21466
21663
  const block = codexBlock(spec);
21467
- const prev = existsSync43(path) ? readFileSync37(path, "utf-8") : "";
21664
+ const prev = existsSync44(path) ? readFileSync37(path, "utf-8") : "";
21468
21665
  const lines = prev.length ? prev.split("\n") : [];
21469
21666
  const start = lines.findIndex((l) => l.trim() === CODEX_HEADER);
21470
21667
  if (start === -1) {
@@ -21505,7 +21702,7 @@ function inspectMcpClients(opts = {}) {
21505
21702
  return MCP_CLIENTS.map((c) => {
21506
21703
  const path = c.configPath(home);
21507
21704
  let registered = false;
21508
- if (existsSync43(path)) {
21705
+ if (existsSync44(path)) {
21509
21706
  try {
21510
21707
  const raw = readFileSync37(path, "utf-8");
21511
21708
  registered = c.id === "codex" ? raw.includes(CODEX_HEADER) : !!(JSON.parse(raw).mcpServers?.[MCP_SERVER_NAME] ?? JSON.parse(raw).mcp?.[MCP_SERVER_NAME]);
@@ -21542,7 +21739,7 @@ var init_mcp_clients = __esm({
21542
21739
  configPath: (home) => join46(envHome("CHAT_RECALL_CODEX_HOME", join46(home, ".codex")), "config.toml"),
21543
21740
  isPresent(home) {
21544
21741
  const p = this.configPath(home);
21545
- return existsSync43(p) || existsSync43(dirname21(p));
21742
+ return existsSync44(p) || existsSync44(dirname21(p));
21546
21743
  },
21547
21744
  register(spec, home) {
21548
21745
  return registerCodexToml(this.configPath(home), spec);
@@ -21555,7 +21752,7 @@ var init_mcp_clients = __esm({
21555
21752
  configPath: (home) => join46(envHome("XDG_CONFIG_HOME", join46(home, ".config")), "opencode", "opencode.json"),
21556
21753
  isPresent(home) {
21557
21754
  const p = this.configPath(home);
21558
- return existsSync43(p) || existsSync43(dirname21(p));
21755
+ return existsSync44(p) || existsSync44(dirname21(p));
21559
21756
  },
21560
21757
  register(spec, home) {
21561
21758
  return registerOpencodeJson(this.configPath(home), spec);
@@ -21569,7 +21766,7 @@ var init_mcp_clients = __esm({
21569
21766
  bin: "agy",
21570
21767
  configPath: (home) => join46(home, ".gemini", "config", "mcp_config.json"),
21571
21768
  isPresent(home) {
21572
- return existsSync43(join46(home, ".gemini", "antigravity-cli")) || existsSync43(this.configPath(home));
21769
+ return existsSync44(join46(home, ".gemini", "antigravity-cli")) || existsSync44(this.configPath(home));
21573
21770
  },
21574
21771
  register(spec, home) {
21575
21772
  return registerMcpServersJson(this, this.configPath(home), spec);
@@ -21582,7 +21779,7 @@ var init_mcp_clients = __esm({
21582
21779
  configPath: (home) => join46(home, ".cursor", "mcp.json"),
21583
21780
  isPresent(home) {
21584
21781
  const p = this.configPath(home);
21585
- return existsSync43(p) || existsSync43(dirname21(p));
21782
+ return existsSync44(p) || existsSync44(dirname21(p));
21586
21783
  },
21587
21784
  register(spec, home) {
21588
21785
  return registerMcpServersJson(this, this.configPath(home), spec);
@@ -21606,7 +21803,7 @@ __export(companions_exports, {
21606
21803
  uninstallCodeindex: () => uninstallCodeindex,
21607
21804
  unregisterCodeindexMcp: () => unregisterCodeindexMcp
21608
21805
  });
21609
- import { existsSync as existsSync44, mkdirSync as mkdirSync13, statSync as statSync39, chmodSync as chmodSync4, unlinkSync as unlinkSync3, renameSync as renameSync5, createWriteStream, readFileSync as readFileSync38, writeFileSync as writeFileSync13 } from "node:fs";
21806
+ import { existsSync as existsSync45, mkdirSync as mkdirSync13, statSync as statSync39, chmodSync as chmodSync4, unlinkSync as unlinkSync3, renameSync as renameSync5, createWriteStream, readFileSync as readFileSync38, writeFileSync as writeFileSync13 } from "node:fs";
21610
21807
  import { homedir as homedir17 } from "node:os";
21611
21808
  import { join as join47 } from "node:path";
21612
21809
  import { execSync as execSync3 } from "node:child_process";
@@ -21729,7 +21926,7 @@ Refusing to install. The release may have been re-uploaded, or something is serv
21729
21926
  renameSync5(tmpPath, CODEINDEX_BIN_PATH);
21730
21927
  } catch (err) {
21731
21928
  try {
21732
- if (existsSync44(tmpPath))
21929
+ if (existsSync45(tmpPath))
21733
21930
  unlinkSync3(tmpPath);
21734
21931
  } catch {
21735
21932
  }
@@ -21738,7 +21935,7 @@ Refusing to install. The release may have been re-uploaded, or something is serv
21738
21935
  return checkCodeindexStatus();
21739
21936
  }
21740
21937
  function uninstallCodeindex() {
21741
- if (existsSync44(CODEINDEX_BIN_PATH)) {
21938
+ if (existsSync45(CODEINDEX_BIN_PATH)) {
21742
21939
  unlinkSync3(CODEINDEX_BIN_PATH);
21743
21940
  return { removed: true, path: CODEINDEX_BIN_PATH };
21744
21941
  }
@@ -21746,7 +21943,7 @@ function uninstallCodeindex() {
21746
21943
  }
21747
21944
  function registerCodeindexMcp(mcpJsonPath, binaryPath) {
21748
21945
  let cfg = {};
21749
- if (existsSync44(mcpJsonPath)) {
21946
+ if (existsSync45(mcpJsonPath)) {
21750
21947
  try {
21751
21948
  cfg = JSON.parse(readFileSync38(mcpJsonPath, "utf-8"));
21752
21949
  } catch {
@@ -21761,7 +21958,7 @@ function registerCodeindexMcp(mcpJsonPath, binaryPath) {
21761
21958
  return { added: true };
21762
21959
  }
21763
21960
  function unregisterCodeindexMcp(mcpJsonPath) {
21764
- if (!existsSync44(mcpJsonPath))
21961
+ if (!existsSync45(mcpJsonPath))
21765
21962
  return { removed: false };
21766
21963
  let cfg;
21767
21964
  try {
@@ -21807,7 +22004,7 @@ __export(install_skills_exports, {
21807
22004
  skillsSourceDir: () => skillsSourceDir,
21808
22005
  uninstallSkills: () => uninstallSkills
21809
22006
  });
21810
- import { readdirSync as readdirSync33, existsSync as existsSync45, mkdirSync as mkdirSync14, rmSync as rmSync7, cpSync, writeFileSync as writeFileSync14, readFileSync as readFileSync39, statSync as statSync40, realpathSync as realpathSync4 } from "fs";
22007
+ import { readdirSync as readdirSync33, existsSync as existsSync46, mkdirSync as mkdirSync14, rmSync as rmSync7, cpSync, writeFileSync as writeFileSync14, readFileSync as readFileSync39, statSync as statSync40, realpathSync as realpathSync4 } from "fs";
21811
22008
  import { createHash as createHash11 } from "crypto";
21812
22009
  import { join as join48, basename as basename21 } from "path";
21813
22010
  function skillsSourceDir() {
@@ -21846,8 +22043,8 @@ function skillStamp() {
21846
22043
  }
21847
22044
  function bundledSkillNames() {
21848
22045
  const src = skillsSourceDir();
21849
- if (!existsSync45(src)) return [];
21850
- return readdirSync33(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync45(join48(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
22046
+ if (!existsSync46(src)) return [];
22047
+ return readdirSync33(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync46(join48(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
21851
22048
  }
21852
22049
  function realPathOrSelf(p) {
21853
22050
  try {
@@ -21871,7 +22068,7 @@ function claudeSkillTargets() {
21871
22068
  dir,
21872
22069
  // Judge each profile on ITS OWN transcripts, not the primary's: a home
21873
22070
  // with no projects/ is a directory someone made, not a Claude install.
21874
- available: existsSync45(join48(home, "projects"))
22071
+ available: existsSync46(join48(home, "projects"))
21875
22072
  });
21876
22073
  }
21877
22074
  if (targets.length === 0) {
@@ -21895,7 +22092,7 @@ function skillTargets() {
21895
22092
  ];
21896
22093
  }
21897
22094
  function isManaged(dir) {
21898
- return existsSync45(join48(dir, MARKER));
22095
+ return existsSync46(join48(dir, MARKER));
21899
22096
  }
21900
22097
  function installSkills(opts = {}) {
21901
22098
  const onlyAvailable = opts.onlyAvailable ?? true;
@@ -21913,7 +22110,7 @@ function installSkills(opts = {}) {
21913
22110
  }
21914
22111
  for (const name of names) {
21915
22112
  const dest = join48(t.dir, name);
21916
- if (existsSync45(dest) && !isManaged(dest)) {
22113
+ if (existsSync46(dest) && !isManaged(dest)) {
21917
22114
  skippedUserOwned.push(name);
21918
22115
  continue;
21919
22116
  }
@@ -21933,7 +22130,7 @@ function uninstallSkills() {
21933
22130
  const perTarget = [];
21934
22131
  for (const t of skillTargets()) {
21935
22132
  const removed = [];
21936
- if (!existsSync45(t.dir)) {
22133
+ if (!existsSync46(t.dir)) {
21937
22134
  perTarget.push({ id: t.id, label: t.label, removed });
21938
22135
  continue;
21939
22136
  }
@@ -21957,7 +22154,7 @@ function skillsNeedRefresh() {
21957
22154
  if (!t.available) continue;
21958
22155
  for (const name of names) {
21959
22156
  const marker = join48(t.dir, name, MARKER);
21960
- if (!existsSync45(marker)) return true;
22157
+ if (!existsSync46(marker)) return true;
21961
22158
  try {
21962
22159
  if (readFileSync39(marker, "utf-8").trim() !== cur) return true;
21963
22160
  } catch {
@@ -21975,7 +22172,7 @@ function skillStatus() {
21975
22172
  let ver = null;
21976
22173
  for (const name of names) {
21977
22174
  const marker = join48(t.dir, name, MARKER);
21978
- if (existsSync45(marker)) {
22175
+ if (existsSync46(marker)) {
21979
22176
  installed++;
21980
22177
  try {
21981
22178
  ver = readFileSync39(marker, "utf-8").trim();
@@ -22163,7 +22360,7 @@ __export(toolkit_sync_exports, {
22163
22360
  supportedTargetsFor: () => supportedTargetsFor,
22164
22361
  writeMcpEntry: () => writeMcpEntry
22165
22362
  });
22166
- import { existsSync as existsSync46, mkdirSync as mkdirSync15, readFileSync as readFileSync41, writeFileSync as writeFileSync15, cpSync as cpSync2 } from "fs";
22363
+ import { existsSync as existsSync47, mkdirSync as mkdirSync15, readFileSync as readFileSync41, writeFileSync as writeFileSync15, cpSync as cpSync2 } from "fs";
22167
22364
  import { dirname as dirname22, join as join50, basename as basename23 } from "path";
22168
22365
  import { homedir as homedir19 } from "os";
22169
22366
  function supportedTargetsFor(type) {
@@ -22218,11 +22415,11 @@ function skillsDirFor(tool) {
22218
22415
  }
22219
22416
  function copySkill(row, toTool) {
22220
22417
  const skillDir = row.extra.skillDir || dirname22(row.filePath);
22221
- if (!existsSync46(skillDir))
22418
+ if (!existsSync47(skillDir))
22222
22419
  return { ok: false, status: 404, error: `Source dir missing: ${skillDir}` };
22223
22420
  const name = row.extra.skillName || basename23(skillDir);
22224
22421
  const targetDir = join50(skillsDirFor(toTool), name);
22225
- if (existsSync46(targetDir))
22422
+ if (existsSync47(targetDir))
22226
22423
  return { ok: false, status: 409, error: `Already exists: ${targetDir}` };
22227
22424
  try {
22228
22425
  mkdirSync15(dirname22(targetDir), { recursive: true });
@@ -22254,7 +22451,7 @@ function readMcpEntry(tool, name) {
22254
22451
  return entry;
22255
22452
  }
22256
22453
  for (const t of tries) {
22257
- if (!existsSync46(t.path))
22454
+ if (!existsSync47(t.path))
22258
22455
  continue;
22259
22456
  try {
22260
22457
  const cfg = JSON.parse(readFileSync41(t.path, "utf-8"));
@@ -22267,7 +22464,7 @@ function readMcpEntry(tool, name) {
22267
22464
  return null;
22268
22465
  }
22269
22466
  function readCodexMcpEntry(path, target) {
22270
- if (!existsSync46(path))
22467
+ if (!existsSync47(path))
22271
22468
  return null;
22272
22469
  let content;
22273
22470
  try {
@@ -22321,7 +22518,7 @@ function readCodexMcpEntry(path, target) {
22321
22518
  }
22322
22519
  function writeCodexMcpEntry(path, name, entry) {
22323
22520
  let content = "";
22324
- if (existsSync46(path))
22521
+ if (existsSync47(path))
22325
22522
  content = readFileSync41(path, "utf-8");
22326
22523
  const header = `[mcp_servers.${name}]`;
22327
22524
  if (content.includes(header))
@@ -22379,7 +22576,7 @@ function writeMcpEntry(toTool, name, entry) {
22379
22576
  key2 = "mcp";
22380
22577
  }
22381
22578
  let cfg = {};
22382
- if (existsSync46(path)) {
22579
+ if (existsSync47(path)) {
22383
22580
  try {
22384
22581
  cfg = JSON.parse(readFileSync41(path, "utf-8"));
22385
22582
  } catch {
@@ -22443,7 +22640,7 @@ function copyCommandOrAgent(type, row, toTool) {
22443
22640
  return { ok: false, status: 404, error: `Source unreadable: ${e instanceof Error ? e.message : "failed"}` };
22444
22641
  }
22445
22642
  const out = emit(type, art, toTool);
22446
- if (existsSync46(out.path))
22643
+ if (existsSync47(out.path))
22447
22644
  return { ok: false, status: 409, error: `Already exists: ${out.path}` };
22448
22645
  try {
22449
22646
  mkdirSync15(dirname22(out.path), { recursive: true });
@@ -22463,7 +22660,7 @@ function copyInstructions(row, toTool) {
22463
22660
  return { ok: false, status: 404, error: `Source unreadable: ${e instanceof Error ? e.message : "failed"}` };
22464
22661
  }
22465
22662
  const out = emit("instructions", art, toTool, row.projectPath || void 0);
22466
- if (existsSync46(out.path))
22663
+ if (existsSync47(out.path))
22467
22664
  return { ok: false, status: 409, error: `Already exists: ${out.path}` };
22468
22665
  try {
22469
22666
  mkdirSync15(dirname22(out.path), { recursive: true });
@@ -22613,7 +22810,7 @@ __export(toolkit_pull_exports, {
22613
22810
  planPull: () => planPull,
22614
22811
  portableCommand: () => portableCommand
22615
22812
  });
22616
- import { existsSync as existsSync47, mkdirSync as mkdirSync16, statSync as statSync41, writeFileSync as writeFileSync16 } from "node:fs";
22813
+ import { existsSync as existsSync48, mkdirSync as mkdirSync16, statSync as statSync41, writeFileSync as writeFileSync16 } from "node:fs";
22617
22814
  import { dirname as dirname23, join as join51, basename as basename24, isAbsolute as isAbsolute3 } from "node:path";
22618
22815
  function isExecutableFile(p) {
22619
22816
  try {
@@ -22766,7 +22963,7 @@ function executePull(rows, opts = {}) {
22766
22963
  for (const tool of tools) {
22767
22964
  const dir = join51(skillsDirFor(tool), name);
22768
22965
  const file = join51(dir, "SKILL.md");
22769
- if (existsSync47(file)) {
22966
+ if (existsSync48(file)) {
22770
22967
  outcomes.push({ type: "skill", name, tool, status: "present" });
22771
22968
  continue;
22772
22969
  }
@@ -22811,7 +23008,7 @@ function executePull(rows, opts = {}) {
22811
23008
  outcomes.push({ type, name, tool, status: "failed", reason: e instanceof Error ? e.message : "emit failed" });
22812
23009
  continue;
22813
23010
  }
22814
- if (existsSync47(out.path)) {
23011
+ if (existsSync48(out.path)) {
22815
23012
  outcomes.push({ type, name, tool, status: "present" });
22816
23013
  continue;
22817
23014
  }
@@ -22969,7 +23166,7 @@ __export(board_hooks_exports, {
22969
23166
  removeBoardHooks: () => removeBoardHooks,
22970
23167
  resolveCliEntry: () => resolveCliEntry
22971
23168
  });
22972
- import { existsSync as existsSync48, mkdirSync as mkdirSync17, readFileSync as readFileSync42, writeFileSync as writeFileSync17 } from "node:fs";
23169
+ import { existsSync as existsSync49, mkdirSync as mkdirSync17, readFileSync as readFileSync42, writeFileSync as writeFileSync17 } from "node:fs";
22973
23170
  import { dirname as dirname24, join as join52 } from "node:path";
22974
23171
  function boardHookCommand(execPath, cliEntry, mode) {
22975
23172
  return `"${execPath}" "${cliEntry}" task-hook --${mode}`;
@@ -23009,7 +23206,7 @@ function removeBoardHooks(config2) {
23009
23206
  return removed;
23010
23207
  }
23011
23208
  function readConfig(file) {
23012
- if (!existsSync48(file)) return { hooks: {} };
23209
+ if (!existsSync49(file)) return { hooks: {} };
23013
23210
  try {
23014
23211
  const parsed = JSON.parse(readFileSync42(file, "utf-8"));
23015
23212
  if (!parsed.hooks || typeof parsed.hooks !== "object") parsed.hooks = {};
@@ -23021,7 +23218,7 @@ function readConfig(file) {
23021
23218
  function registerBoardHooks(files, execPath, cliEntry) {
23022
23219
  const written = [];
23023
23220
  for (const file of files) {
23024
- if (existsSync48(file)) {
23221
+ if (existsSync49(file)) {
23025
23222
  try {
23026
23223
  JSON.parse(readFileSync42(file, "utf-8"));
23027
23224
  } catch {
@@ -23041,7 +23238,7 @@ function registerBoardHooks(files, execPath, cliEntry) {
23041
23238
  }
23042
23239
  function resolveCliEntry(distDir) {
23043
23240
  const bundled = join52(distDir, "cli.js");
23044
- return existsSync48(bundled) ? bundled : join52(distDir, "cli.ts");
23241
+ return existsSync49(bundled) ? bundled : join52(distDir, "cli.ts");
23045
23242
  }
23046
23243
  var BOARD_HOOKS;
23047
23244
  var init_board_hooks = __esm({
@@ -23144,6 +23341,9 @@ async function repairSession(id, opts = {}) {
23144
23341
  note: "archive count is a lower bound; the rebuild also unions the on-disk tail"
23145
23342
  };
23146
23343
  }
23344
+ if (!opts.force && endpoints.every((ep) => (currentOnServer.get(ep.url) ?? 0) >= fullest.messages)) {
23345
+ return { sessionId: id, status: "already-full", fullestMessages: fullest.messages, fullestSource: fullest.source, pushed: [] };
23346
+ }
23147
23347
  seedShadow(rawId, fullest.container);
23148
23348
  const located = backend.findSession(rawId);
23149
23349
  const projectPath = located?.projectPath || cwdFromContainer(fullest.container);
@@ -23186,7 +23386,7 @@ async function repairSession(id, opts = {}) {
23186
23386
  try {
23187
23387
  const res = await fetchWithTimeout(`${ep.url}/api/sync`, { method: "POST", headers, body: JSON.stringify({ conversations: [built.conv] }) }, 6e4);
23188
23388
  if (!res.ok) {
23189
- pushed.push({ server: ep.url, before, after: before });
23389
+ pushed.push({ server: ep.url, before, after: before, error: `HTTP ${res.status}` });
23190
23390
  log6(`push to ${ep.url} FAILED: HTTP ${res.status}`);
23191
23391
  continue;
23192
23392
  }
@@ -23195,10 +23395,21 @@ async function repairSession(id, opts = {}) {
23195
23395
  pushed.push({ server: ep.url, before, after: afterN });
23196
23396
  log6(`pushed to ${ep.url}: ${before} \u2192 ${afterN} msg(s)`);
23197
23397
  } catch (e) {
23198
- pushed.push({ server: ep.url, before, after: before });
23199
- log6(`push to ${ep.url} errored: ${e instanceof Error ? e.message : e}`);
23398
+ const error = e instanceof Error ? e.message : String(e);
23399
+ pushed.push({ server: ep.url, before, after: before, error });
23400
+ log6(`push to ${ep.url} errored: ${error}`);
23200
23401
  }
23201
23402
  }
23403
+ if (pushed.every((p) => p.error)) {
23404
+ return {
23405
+ sessionId: id,
23406
+ status: "error",
23407
+ fullestMessages: builtCount,
23408
+ fullestSource: fullest.source,
23409
+ pushed,
23410
+ note: `every push failed: ${pushed.map((p) => `${p.server}: ${p.error}`).join("; ")}`
23411
+ };
23412
+ }
23202
23413
  return { sessionId: id, status: "repaired", fullestMessages: builtCount, fullestSource: fullest.source, pushed };
23203
23414
  }
23204
23415
  async function repairSessions(ids, opts = {}) {
@@ -23693,11 +23904,11 @@ var verify_repair_exports = {};
23693
23904
  __export(verify_repair_exports, {
23694
23905
  forceFullResync: () => forceFullResync
23695
23906
  });
23696
- import { copyFileSync as copyFileSync3, existsSync as existsSync49 } from "fs";
23907
+ import { copyFileSync as copyFileSync3, existsSync as existsSync50 } from "fs";
23697
23908
  function forceFullResync(server, sessionIds) {
23698
23909
  if (sessionIds.length === 0) return 0;
23699
23910
  const path = ledgerFilePath();
23700
- if (existsSync49(path)) {
23911
+ if (existsSync50(path)) {
23701
23912
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").replace(/-/g, "");
23702
23913
  try {
23703
23914
  copyFileSync3(path, `${path}.bak-${stamp}`);
@@ -23900,7 +24111,10 @@ async function resolveCodeindexBin(autoInstall = true) {
23900
24111
  }
23901
24112
  function runMcp(binPath, workspace, calls, timeoutMs = 18e4) {
23902
24113
  return new Promise((resolve3, reject) => {
23903
- const p = spawn3(binPath, ["--mcp", "--workspace", workspace], { stdio: ["pipe", "pipe", "ignore"] });
24114
+ const p = spawn3(binPath, ["--mcp", "--workspace", workspace], {
24115
+ stdio: ["pipe", "pipe", "ignore"],
24116
+ env: { ...process.env, CODEINDEX_NO_DAEMON: "1" }
24117
+ });
23904
24118
  const res = /* @__PURE__ */ new Map();
23905
24119
  let pending = "";
23906
24120
  let failed = false;
@@ -24851,7 +25065,7 @@ __export(project_tasks_exports, {
24851
25065
  pushProjectTaskStatuses: () => pushProjectTaskStatuses,
24852
25066
  renderTeamTasksMd: () => renderTeamTasksMd
24853
25067
  });
24854
- import { existsSync as existsSync50, readFileSync as readFileSync44, writeFileSync as writeFileSync18, mkdirSync as mkdirSync18, statSync as statSync42 } from "node:fs";
25068
+ import { existsSync as existsSync51, readFileSync as readFileSync44, writeFileSync as writeFileSync18, mkdirSync as mkdirSync18, statSync as statSync42 } from "node:fs";
24855
25069
  import { join as join55, dirname as dirname25 } from "node:path";
24856
25070
  import { homedir as homedir21 } from "node:os";
24857
25071
  function isTeamTaskStatus(v) {
@@ -24937,7 +25151,7 @@ async function pushProjectTaskStatuses(base3, authHeaders, opts = {}) {
24937
25151
  }
24938
25152
  for (const project of projects) {
24939
25153
  const file = join55(project, fk.filename);
24940
- if (!existsSync50(file)) continue;
25154
+ if (!existsSync51(file)) continue;
24941
25155
  let mtime;
24942
25156
  try {
24943
25157
  mtime = statSync42(file).mtimeMs;
@@ -25019,7 +25233,7 @@ __export(intent_drain_exports, {
25019
25233
  drainSyncIntents: () => drainSyncIntents,
25020
25234
  instructionTargets: () => instructionTargets
25021
25235
  });
25022
- import { existsSync as existsSync51, readFileSync as readFileSync45, writeFileSync as writeFileSync19, appendFileSync, mkdirSync as mkdirSync19 } from "node:fs";
25236
+ import { existsSync as existsSync52, readFileSync as readFileSync45, writeFileSync as writeFileSync19, appendFileSync, mkdirSync as mkdirSync19 } from "node:fs";
25023
25237
  import { join as join56 } from "node:path";
25024
25238
  import { fileURLToPath as fileURLToPath4 } from "node:url";
25025
25239
  import { dirname as dirname26 } from "node:path";
@@ -25034,7 +25248,7 @@ function instructionTargets(rootPath, isGlobal) {
25034
25248
  const tools = installed.length ? installed : [INSTRUCTION_TOOLS[0]];
25035
25249
  if (isGlobal) return [...new Set(tools.map((t) => instructionsPath(t.id)))];
25036
25250
  if (!rootPath) return [];
25037
- const present = INSTRUCTION_FILENAMES.map((f) => join56(rootPath, f)).filter((p) => existsSync51(p));
25251
+ const present = INSTRUCTION_FILENAMES.map((f) => join56(rootPath, f)).filter((p) => existsSync52(p));
25038
25252
  if (present.length) return present;
25039
25253
  return [...new Set(tools.map((t) => join56(rootPath, instructionsFilename(t.id))))];
25040
25254
  }
@@ -25062,7 +25276,7 @@ function applyCodeRecommendation(intent) {
25062
25276
  mkdirSync19(dir, { recursive: true });
25063
25277
  } catch {
25064
25278
  }
25065
- const existing = existsSync51(file) ? readFileSync45(file, "utf8") : "";
25279
+ const existing = existsSync52(file) ? readFileSync45(file, "utf8") : "";
25066
25280
  if (existing.includes(text)) {
25067
25281
  skipped.push(file);
25068
25282
  continue;
@@ -25240,7 +25454,7 @@ var init_intent_drain = __esm({
25240
25454
  ownVersion2 = JSON.parse(readFileSync45(join56(dir, "../package.json"), "utf-8")).version || ownVersion2;
25241
25455
  } catch {
25242
25456
  try {
25243
- if (true) ownVersion2 = "0.7.0";
25457
+ if (true) ownVersion2 = "0.7.2";
25244
25458
  } catch {
25245
25459
  }
25246
25460
  }
@@ -25441,7 +25655,7 @@ var team_merge_exports = {};
25441
25655
  __export(team_merge_exports, {
25442
25656
  mergePullResult: () => mergePullResult
25443
25657
  });
25444
- import { existsSync as existsSync52, mkdirSync as mkdirSync20, readFileSync as readFileSync46, rmSync as rmSync8, writeFileSync as writeFileSync20, statSync as statSync43 } from "fs";
25658
+ import { existsSync as existsSync53, mkdirSync as mkdirSync20, readFileSync as readFileSync46, rmSync as rmSync8, writeFileSync as writeFileSync20, statSync as statSync43 } from "fs";
25445
25659
  import { dirname as dirname27, join as join57 } from "path";
25446
25660
  import { createHash as createHash14 } from "crypto";
25447
25661
  function installPathFor(art, tool) {
@@ -25558,7 +25772,7 @@ async function mergePullResult(opts) {
25558
25772
  if (!path)
25559
25773
  continue;
25560
25774
  try {
25561
- if (existsSync52(path))
25775
+ if (existsSync53(path))
25562
25776
  rmSync8(path, { force: true });
25563
25777
  await teamInstallsForget(id, row.tool);
25564
25778
  report.removed.push({ artifactId: id, path });
@@ -25576,7 +25790,7 @@ async function mergePullResult(opts) {
25576
25790
  }
25577
25791
  function readFileIfExists(path) {
25578
25792
  try {
25579
- if (!existsSync52(path))
25793
+ if (!existsSync53(path))
25580
25794
  return null;
25581
25795
  const s = statSync43(path);
25582
25796
  if (!s.isFile())
@@ -25662,7 +25876,7 @@ var init_vault_crypto = __esm({
25662
25876
  });
25663
25877
 
25664
25878
  // ../engine/dist/core/vault-source.js
25665
- import { existsSync as existsSync53, readdirSync as readdirSync34, statSync as statSync44 } from "fs";
25879
+ import { existsSync as existsSync54, readdirSync as readdirSync34, statSync as statSync44 } from "fs";
25666
25880
  import { extname, join as join58 } from "path";
25667
25881
  function* walkVaultSources() {
25668
25882
  const t = loadSettings().team.vault;
@@ -25688,7 +25902,7 @@ function safeStat(path) {
25688
25902
  }
25689
25903
  function* walkClaude() {
25690
25904
  const root = claudeBackend.projectsDir();
25691
- if (!existsSync53(root))
25905
+ if (!existsSync54(root))
25692
25906
  return;
25693
25907
  let projectDirs = [];
25694
25908
  try {
@@ -25741,7 +25955,7 @@ function* walkCursor() {
25741
25955
  }
25742
25956
  function* walkCodex() {
25743
25957
  const sessionsRoot = join58(codexBackend.homeDir(), "sessions");
25744
- if (!existsSync53(sessionsRoot))
25958
+ if (!existsSync54(sessionsRoot))
25745
25959
  return;
25746
25960
  const stack = [sessionsRoot];
25747
25961
  while (stack.length) {
@@ -25770,7 +25984,7 @@ function* walkCodex() {
25770
25984
  }
25771
25985
  function* walkAgy() {
25772
25986
  const brainDir = join58(agyBackend.homeDir(), "brain");
25773
- if (!existsSync53(brainDir))
25987
+ if (!existsSync54(brainDir))
25774
25988
  return;
25775
25989
  let sessionDirs = [];
25776
25990
  try {
@@ -25787,10 +26001,10 @@ function* walkAgy() {
25787
26001
  continue;
25788
26002
  }
25789
26003
  let filePath = join58(sessionPath, ".system_generated", "logs", "transcript.jsonl");
25790
- if (!existsSync53(filePath)) {
26004
+ if (!existsSync54(filePath)) {
25791
26005
  filePath = join58(sessionPath, ".system_generated", "logs", "transcript_full.jsonl");
25792
26006
  }
25793
- if (!existsSync53(filePath))
26007
+ if (!existsSync54(filePath))
25794
26008
  continue;
25795
26009
  const s = safeStat(filePath);
25796
26010
  if (!s)
@@ -25826,7 +26040,7 @@ __export(vault_client_exports, {
25826
26040
  vaultStatus: () => vaultStatus,
25827
26041
  vaultSync: () => vaultSync
25828
26042
  });
25829
- import { mkdirSync as mkdirSync21, readFileSync as readFileSync47, writeFileSync as writeFileSync21, existsSync as existsSync54 } from "fs";
26043
+ import { mkdirSync as mkdirSync21, readFileSync as readFileSync47, writeFileSync as writeFileSync21, existsSync as existsSync55 } from "fs";
25830
26044
  import { dirname as dirname28, join as join59 } from "path";
25831
26045
  function context2() {
25832
26046
  const s = loadSettings().team;
@@ -26039,7 +26253,7 @@ async function vaultRestore(passphrase, opts = {}) {
26039
26253
  }
26040
26254
  try {
26041
26255
  const path = importedPathFor(blob.tool, blob.sessionId);
26042
- if (existsSync54(path)) {
26256
+ if (existsSync55(path)) {
26043
26257
  const localStat = await import("fs").then((m) => m.statSync(path));
26044
26258
  if (Math.abs(localStat.mtimeMs - blob.mtimeSourceMs) < 1) {
26045
26259
  report.skipped.push({ sessionId: blob.sessionId, tool: blob.tool, reason: "unchanged" });
@@ -26122,7 +26336,7 @@ __export(keyring_exports, {
26122
26336
  psQuote: () => psQuote,
26123
26337
  shQuote: () => shQuote
26124
26338
  });
26125
- import { execFileSync as execFileSync3 } from "node:child_process";
26339
+ import { execFileSync as execFileSync4 } from "node:child_process";
26126
26340
  function probeKeyring(r = realRunner) {
26127
26341
  if (r.platform === "darwin") {
26128
26342
  try {
@@ -26234,7 +26448,7 @@ var init_keyring = __esm({
26234
26448
  ACCOUNT = "vault-passphrase";
26235
26449
  realRunner = {
26236
26450
  run(file, args, input) {
26237
- return execFileSync3(file, args, {
26451
+ return execFileSync4(file, args, {
26238
26452
  input,
26239
26453
  encoding: "utf-8",
26240
26454
  stdio: input === void 0 ? ["ignore", "pipe", "pipe"] : ["pipe", "pipe", "pipe"]
@@ -26669,7 +26883,7 @@ __export(service_installer_exports, {
26669
26883
  uninstallService: () => uninstallService
26670
26884
  });
26671
26885
  import { execSync as execSync5 } from "child_process";
26672
- import { existsSync as existsSync55, mkdirSync as mkdirSync22, readFileSync as readFileSync48, rmSync as rmSync9, writeFileSync as writeFileSync22 } from "fs";
26886
+ import { existsSync as existsSync56, mkdirSync as mkdirSync22, readFileSync as readFileSync48, rmSync as rmSync9, writeFileSync as writeFileSync22 } from "fs";
26673
26887
  import { homedir as homedir22 } from "os";
26674
26888
  import { join as join60 } from "path";
26675
26889
  import { fileURLToPath as fileURLToPath5 } from "url";
@@ -26696,7 +26910,7 @@ function isServiceRunning() {
26696
26910
  return /com\.chat-recall\.watch/.test(tryCmd("launchctl list 2>/dev/null"));
26697
26911
  }
26698
26912
  if (process.platform === "linux") {
26699
- return /active/.test(tryCmd("systemctl --user is-active chat-recall-watch.service 2>/dev/null"));
26913
+ return /^active$/m.test(tryCmd("systemctl --user is-active chat-recall-watch.service 2>/dev/null"));
26700
26914
  }
26701
26915
  return false;
26702
26916
  }
@@ -26705,7 +26919,7 @@ function refreshServiceDefinition() {
26705
26919
  try {
26706
26920
  const { watchJs, node, logFile } = resolveDaemonPaths();
26707
26921
  const unitPath = join60(homedir22(), ".config", "systemd", "user", "chat-recall-watch.service");
26708
- if (!existsSync55(unitPath)) return false;
26922
+ if (!existsSync56(unitPath)) return false;
26709
26923
  const current = readFileSync48(unitPath, "utf8");
26710
26924
  if (!current.includes(watchJs)) return false;
26711
26925
  const wanted = renderSystemdUnit(watchJs, node, logFile);
@@ -26772,7 +26986,12 @@ function renderSystemdUnit(watchJs, node, logFile) {
26772
26986
  // sawtooth. 1536MB keeps GC honest (768 OOMed on the largest subagent-fanout session) with plenty of headroom for the
26773
26987
  // largest transcripts.
26774
26988
  `ExecStart=${node} --max-old-space-size=1536 ${watchJs}`,
26775
- "Restart=on-failure",
26989
+ // ALWAYS: the collector never ends its own work, so any exit systemd did
26990
+ // not ask for is a fault. Under on-failure a clean exit left it dead: on
26991
+ // 2026-09-16 the unit went "inactive (dead) … status=0/SUCCESS" and stayed
26992
+ // down for 8 days, so nothing synced and the auto-update tick, which runs
26993
+ // inside the daemon, never ran. `systemctl stop` still stops it.
26994
+ "Restart=always",
26776
26995
  "RestartSec=10",
26777
26996
  "Nice=10",
26778
26997
  // NEVER STOP RETRYING. systemd's default start limiter gives up after a
@@ -26868,7 +27087,7 @@ function uninstallService() {
26868
27087
  } catch {
26869
27088
  }
26870
27089
  const unitPath = join60(homedir22(), ".config", "systemd", "user", "chat-recall-watch.service");
26871
- if (existsSync55(unitPath)) {
27090
+ if (existsSync56(unitPath)) {
26872
27091
  rmSync9(unitPath, { force: true });
26873
27092
  removed = true;
26874
27093
  }
@@ -26885,7 +27104,7 @@ function uninstallService() {
26885
27104
  } catch {
26886
27105
  }
26887
27106
  let removed = false;
26888
- if (existsSync55(plistPath)) {
27107
+ if (existsSync56(plistPath)) {
26889
27108
  rmSync9(plistPath, { force: true });
26890
27109
  removed = true;
26891
27110
  }
@@ -27298,7 +27517,7 @@ init_project_resolver();
27298
27517
  import { config } from "dotenv";
27299
27518
  import { Command } from "commander";
27300
27519
  import chalk2 from "chalk";
27301
- import { existsSync as existsSync56, readFileSync as readFileSync49, writeFileSync as writeFileSync23, realpathSync as realpathSync5 } from "fs";
27520
+ import { existsSync as existsSync57, readFileSync as readFileSync49, writeFileSync as writeFileSync23, realpathSync as realpathSync5 } from "fs";
27302
27521
  import { homedir as homedir23 } from "os";
27303
27522
  import { join as join61, resolve as resolve2, dirname as dirname29, basename as basename27 } from "path";
27304
27523
 
@@ -27407,7 +27626,7 @@ function renderEnvNeeds(needs, installedCount) {
27407
27626
 
27408
27627
  // src/star-nudge.ts
27409
27628
  init_paths();
27410
- import { existsSync as existsSync42, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "node:fs";
27629
+ import { existsSync as existsSync43, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "node:fs";
27411
27630
  import { dirname as dirname20, join as join45 } from "node:path";
27412
27631
  var REPO = "https://github.com/munhq/chat-recall";
27413
27632
  function markerPath() {
@@ -27423,7 +27642,7 @@ function shouldAskForStar(resultCount) {
27423
27642
  if (suppressed()) return false;
27424
27643
  if (!process.stderr.isTTY) return false;
27425
27644
  try {
27426
- return !existsSync42(markerPath());
27645
+ return !existsSync43(markerPath());
27427
27646
  } catch {
27428
27647
  return false;
27429
27648
  }
@@ -27464,7 +27683,7 @@ function claudeHookConfigFiles() {
27464
27683
  const out = [];
27465
27684
  const seen = /* @__PURE__ */ new Set();
27466
27685
  for (const home of claudeHomeDirs()) {
27467
- if (!existsSync56(join61(home, "projects"))) continue;
27686
+ if (!existsSync57(join61(home, "projects"))) continue;
27468
27687
  const file = join61(home, "hooks.json");
27469
27688
  let key2;
27470
27689
  try {
@@ -28250,7 +28469,7 @@ memory.command("wake-up").description("Generate wake-up context (high-importance
28250
28469
  try {
28251
28470
  const identityFile = getIdentityFilePath();
28252
28471
  let identity = "AI coding assistant";
28253
- if (existsSync56(identityFile)) {
28472
+ if (existsSync57(identityFile)) {
28254
28473
  identity = readFileSync49(identityFile, "utf-8").trim();
28255
28474
  }
28256
28475
  const lines = [
@@ -28351,7 +28570,7 @@ program.command("feedback").argument("<message...>", "what you want to say \u201
28351
28570
  }
28352
28571
  });
28353
28572
  program.command("doctor").description("Quick health check across login, server, credentials, hooks, MCP server, and codeindex").option("--purge-local", "Remove the legacy local index (cache.db + vector/KG files) left by pre-thin-collector versions \u2014 storage is server-side now. Requires login; keeps credentials, sync ledger, identity, and the audit log.").action(async (opts) => {
28354
- const { existsSync: existsSync57, readFileSync: readFileSync50, statSync: statSync45 } = await import("fs");
28573
+ const { existsSync: existsSync58, readFileSync: readFileSync50, statSync: statSync45 } = await import("fs");
28355
28574
  const { execSync: execSync6 } = await import("child_process");
28356
28575
  const rows = [];
28357
28576
  const note = (ok, label, detail) => rows.push({ ok, label, detail });
@@ -28415,7 +28634,7 @@ program.command("doctor").description("Quick health check across login, server,
28415
28634
  }
28416
28635
  }
28417
28636
  const credFile = join61(getDataDir(), "credentials.json");
28418
- if (!existsSync57(credFile)) {
28637
+ if (!existsSync58(credFile)) {
28419
28638
  note(false, "Credentials file", `missing \u2014 run \`chat-recall login <server-url>\``);
28420
28639
  } else {
28421
28640
  try {
@@ -28430,13 +28649,13 @@ program.command("doctor").description("Quick health check across login, server,
28430
28649
  for (const hooksJson of claudeHookConfigFiles()) {
28431
28650
  const profile = basename27(dirname29(hooksJson));
28432
28651
  const label = `Claude hooks (${profile})`;
28433
- if (!existsSync57(hooksJson)) {
28652
+ if (!existsSync58(hooksJson)) {
28434
28653
  note(false, label, `no ${hooksJson} \u2014 run \`chat-recall install-hooks\``);
28435
28654
  continue;
28436
28655
  }
28437
28656
  try {
28438
28657
  const readHooks = (file) => {
28439
- if (!existsSync57(file)) return {};
28658
+ if (!existsSync58(file)) return {};
28440
28659
  try {
28441
28660
  return JSON.parse(readFileSync50(file, "utf-8")).hooks || {};
28442
28661
  } catch {
@@ -28479,9 +28698,9 @@ program.command("doctor").description("Quick health check across login, server,
28479
28698
  try {
28480
28699
  const reg = await Promise.resolve().then(() => (init_guard_registration(), guard_registration_exports));
28481
28700
  for (const target2 of reg.guardTargets()) {
28482
- if (!existsSync57(target2.homeDir)) continue;
28701
+ if (!existsSync58(target2.homeDir)) continue;
28483
28702
  const label = `Decision guard \u2014 ${target2.label}`;
28484
- if (!existsSync57(target2.configPath)) {
28703
+ if (!existsSync58(target2.configPath)) {
28485
28704
  note(false, label, `not registered \u2014 run \`chat-recall install-hooks\``);
28486
28705
  continue;
28487
28706
  }
@@ -28506,7 +28725,7 @@ program.command("doctor").description("Quick health check across login, server,
28506
28725
  const want = bundledSkillNames2().length;
28507
28726
  for (const t of skillTargets2()) {
28508
28727
  if (!t.available) continue;
28509
- const have = want === 0 ? 0 : bundledSkillNames2().filter((n) => existsSync57(join61(t.dir, n, ".chat-recall-managed"))).length;
28728
+ const have = want === 0 ? 0 : bundledSkillNames2().filter((n) => existsSync58(join61(t.dir, n, ".chat-recall-managed"))).length;
28510
28729
  note(
28511
28730
  have === want,
28512
28731
  `Skills \u2014 ${t.label}`,
@@ -28576,7 +28795,7 @@ program.command("doctor").description("Quick health check across login, server,
28576
28795
  } else if (process.platform === "darwin") {
28577
28796
  watchRunning = /com\.chat-recall\.watch/.test(tryCmd2("launchctl list 2>/dev/null"));
28578
28797
  } else {
28579
- watchRunning = /active/.test(tryCmd2("systemctl --user is-active chat-recall-watch.service 2>/dev/null"));
28798
+ watchRunning = /^active$/m.test(tryCmd2("systemctl --user is-active chat-recall-watch.service 2>/dev/null"));
28580
28799
  }
28581
28800
  note(watchRunning, "Watch daemon", watchRunning ? "running" : "not running (run `chat-recall watch --install-service` to keep collection running)");
28582
28801
  const dataDir = getDataDir();
@@ -28588,7 +28807,7 @@ program.command("doctor").description("Quick health check across login, server,
28588
28807
  join61(dataDir, "index", "knowledge_graph.db"),
28589
28808
  join61(dataDir, "index", "metadata.db")
28590
28809
  ];
28591
- const legacyPresent = legacyPaths.filter((p) => existsSync57(p));
28810
+ const legacyPresent = legacyPaths.filter((p) => existsSync58(p));
28592
28811
  if (opts.purgeLocal) {
28593
28812
  if (!target) {
28594
28813
  note(false, "Purge legacy index", "refused \u2014 log in and `chat-recall sync` first, so your history is on the server before removing local copies");
@@ -29232,10 +29451,10 @@ tasks.command("pull [dir]").description("Write TEAM_TASKS.md for your tasks in t
29232
29451
  try {
29233
29452
  const { resolveProjectId: resolveProjectId2 } = await Promise.resolve().then(() => (init_project_resolver(), project_resolver_exports));
29234
29453
  const { renderTeamTasksMd: renderTeamTasksMd2, parseTeamTasksFile: parseTeamTasksFile2 } = await Promise.resolve().then(() => (init_project_tasks(), project_tasks_exports));
29235
- const { writeFileSync: writeFileSync24, readFileSync: readFileSync50, existsSync: existsSync57 } = await import("node:fs");
29454
+ const { writeFileSync: writeFileSync24, readFileSync: readFileSync50, existsSync: existsSync58 } = await import("node:fs");
29236
29455
  const { join: join62 } = await import("node:path");
29237
29456
  const file = join62(cwd, "TEAM_TASKS.md");
29238
- if (!opts.force && existsSync57(file) && parseTeamTasksFile2(readFileSync50(file, "utf8")).length > 0) {
29457
+ if (!opts.force && existsSync58(file) && parseTeamTasksFile2(readFileSync50(file, "utf8")).length > 0) {
29239
29458
  console.error(chalk2.red("TEAM_TASKS.md has unpushed edits."), "Run", chalk2.bold("chat-recall tasks push"), "first, or", chalk2.bold("tasks pull --force"), "to discard.");
29240
29459
  process.exit(1);
29241
29460
  }
@@ -29254,10 +29473,10 @@ tasks.command("push [dir]").description("Push TEAM_TASKS.md status edits back to
29254
29473
  const cwd = dir || process.cwd();
29255
29474
  try {
29256
29475
  const { parseTeamTasksFile: parseTeamTasksFile2 } = await Promise.resolve().then(() => (init_project_tasks(), project_tasks_exports));
29257
- const { readFileSync: readFileSync50, existsSync: existsSync57 } = await import("node:fs");
29476
+ const { readFileSync: readFileSync50, existsSync: existsSync58 } = await import("node:fs");
29258
29477
  const { join: join62 } = await import("node:path");
29259
29478
  const file = join62(cwd, "TEAM_TASKS.md");
29260
- if (!existsSync57(file)) {
29479
+ if (!existsSync58(file)) {
29261
29480
  console.error(chalk2.red("no TEAM_TASKS.md here \u2014"), "run", chalk2.bold("chat-recall tasks pull"), "first");
29262
29481
  process.exit(1);
29263
29482
  }
@@ -29699,10 +29918,10 @@ program.command("task-hook").description("Move a board card when a prompt or a c
29699
29918
  return;
29700
29919
  }
29701
29920
  if (!opts.close) return;
29702
- const { execFileSync: execFileSync4 } = await import("node:child_process");
29921
+ const { execFileSync: execFileSync5 } = await import("node:child_process");
29703
29922
  const git2 = (args) => {
29704
29923
  try {
29705
- return execFileSync4("git", args, {
29924
+ return execFileSync5("git", args, {
29706
29925
  cwd,
29707
29926
  encoding: "utf8",
29708
29927
  timeout: 5e3,
@@ -29744,7 +29963,7 @@ program.command("task-hook").description("Move a board card when a prompt or a c
29744
29963
  }
29745
29964
  });
29746
29965
  program.command("install-hooks").description("Install Claude Code hooks (Stop + PreCompact + UserPromptSubmit + SessionEnd + SessionStart) into every Claude profile").option("--uninstall", "Remove hooks instead of installing them").option("--no-resume-hint", "Don't install the UserPromptSubmit resume-hint hook").option("--no-escalate", "Don't install the SessionEnd learnings-escalation hook").option("--no-wakeup", "Don't install the SessionStart wake-up hook").option("--no-guard", "Don't install the PreToolUse decision guard").option("--no-task-board", "Don't install the hooks that claim and close board cards").action(async (opts) => {
29747
- const { mkdirSync: mkdirSync23, copyFileSync: copyFileSync4, chmodSync: chmodSync5, existsSync: existsSync57, readFileSync: readFileSync50, writeFileSync: writeFileSync24, statSync: statSync45, unlinkSync: unlinkSync4 } = await import("fs");
29966
+ const { mkdirSync: mkdirSync23, copyFileSync: copyFileSync4, chmodSync: chmodSync5, existsSync: existsSync58, readFileSync: readFileSync50, writeFileSync: writeFileSync24, statSync: statSync45, unlinkSync: unlinkSync4 } = await import("fs");
29748
29967
  const { fileURLToPath: fileURLToPath6 } = await import("url");
29749
29968
  const { boardHookEntries: boardHookEntries2, isBoardHookEntry: isBoardHookEntry2, registerBoardHooks: registerBoardHooks2, resolveCliEntry: resolveCliEntry2 } = await Promise.resolve().then(() => (init_board_hooks(), board_hooks_exports));
29750
29969
  const here = fileURLToPath6(new URL(".", import.meta.url));
@@ -29757,7 +29976,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
29757
29976
  // freshly added script is only at the root until that build runs.
29758
29977
  join61(here, "..", "..", "..", "hooks", name)
29759
29978
  ];
29760
- return candidates2.find((p) => existsSync57(p));
29979
+ return candidates2.find((p) => existsSync58(p));
29761
29980
  };
29762
29981
  const sourceSaveHook = findHook("chat_recall_save_hook.sh");
29763
29982
  const sourceGuardHook = findHook("chat_recall_guard_hook.sh");
@@ -29776,7 +29995,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
29776
29995
  const installedWakeupHook = join61(hooksDir, "chat_recall_wakeup_hook.sh");
29777
29996
  const wakeupWiredInSettings = (home) => {
29778
29997
  const settings2 = join61(home, "settings.json");
29779
- if (!existsSync57(settings2)) return false;
29998
+ if (!existsSync58(settings2)) return false;
29780
29999
  try {
29781
30000
  const cfg = JSON.parse(readFileSync50(settings2, "utf-8"));
29782
30001
  const arr = Array.isArray(cfg.hooks?.SessionStart) ? cfg.hooks.SessionStart : [];
@@ -29787,7 +30006,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
29787
30006
  };
29788
30007
  const hookConfigFiles = claudeHookConfigFiles();
29789
30008
  const readHookConfig = (file) => {
29790
- if (!existsSync57(file)) return { hooks: {} };
30009
+ if (!existsSync58(file)) return { hooks: {} };
29791
30010
  try {
29792
30011
  const c = JSON.parse(readFileSync50(file, "utf-8"));
29793
30012
  if (!c.hooks || typeof c.hooks !== "object") c.hooks = {};
@@ -29804,7 +30023,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
29804
30023
  };
29805
30024
  const HOOK_EVENTS = ["Stop", "PreCompact", "UserPromptSubmit", "SessionEnd", "SessionStart", "PreToolUse"];
29806
30025
  const readJson4 = (file) => {
29807
- if (!existsSync57(file)) return {};
30026
+ if (!existsSync58(file)) return {};
29808
30027
  try {
29809
30028
  return JSON.parse(readFileSync50(file, "utf-8"));
29810
30029
  } catch {
@@ -29815,7 +30034,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
29815
30034
  const reg = await Promise.resolve().then(() => (init_guard_registration(), guard_registration_exports));
29816
30035
  const notes = [];
29817
30036
  for (const target of reg.guardTargets()) {
29818
- if (!existsSync57(target.homeDir)) continue;
30037
+ if (!existsSync58(target.homeDir)) continue;
29819
30038
  const wrapper = join61(hooksDir, reg.guardWrapperName(target.tool));
29820
30039
  writeFileSync24(wrapper, reg.guardWrapperSource(target.tool, hookPath));
29821
30040
  chmodSync5(wrapper, 493);
@@ -29836,7 +30055,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
29836
30055
  const reg = await Promise.resolve().then(() => (init_guard_registration(), guard_registration_exports));
29837
30056
  let removed = 0;
29838
30057
  for (const target of reg.guardTargets()) {
29839
- if (!existsSync57(target.configPath)) continue;
30058
+ if (!existsSync58(target.configPath)) continue;
29840
30059
  if (target.tool === "opencode") {
29841
30060
  const body = readFileSync50(target.configPath, "utf-8");
29842
30061
  if (!body.includes("chat-recall install-hooks")) continue;
@@ -29864,7 +30083,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
29864
30083
  if (filtered.length) config2.hooks[event] = filtered;
29865
30084
  else delete config2.hooks[event];
29866
30085
  }
29867
- if (removed === 0 && !existsSync57(hooksJson)) continue;
30086
+ if (removed === 0 && !existsSync58(hooksJson)) continue;
29868
30087
  writeFileSync24(hooksJson, JSON.stringify(config2, null, 2) + "\n");
29869
30088
  total += removed;
29870
30089
  console.log(chalk2.green(`\u2713 Removed ${removed} chat-recall hook entr${removed === 1 ? "y" : "ies"} from ${hooksJson}`));
@@ -30737,8 +30956,8 @@ npm has ${version2}; the server you sync to still serves ${check.serverVersion}.
30737
30956
  console.log(chalk2.dim(`
30738
30957
  Installing ${version2} from npm\u2026`));
30739
30958
  try {
30740
- const { execFileSync: execFileSync4 } = await import("node:child_process");
30741
- execFileSync4("npm", ["install", "-g", `chat-recall@${version2}`], { stdio: "inherit" });
30959
+ const { execFileSync: execFileSync5 } = await import("node:child_process");
30960
+ execFileSync5("npm", ["install", "-g", `chat-recall@${version2}`], { stdio: "inherit" });
30742
30961
  const { installedVersion: installedVersion2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
30743
30962
  const landed = installedVersion2();
30744
30963
  if (landed && compareVersions2(landed, version2) >= 0) {