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/mcp.js CHANGED
@@ -4585,12 +4585,13 @@ function resolveProjectId(inputPath) {
4585
4585
  if (!isAbsolute(inputPath)) {
4586
4586
  return { id: `path:${inputPath}`, displayName: inputPath, source: "path" };
4587
4587
  }
4588
+ const known = inputCache.get(inputPath);
4589
+ if (known)
4590
+ return known;
4588
4591
  const realPath = safeRealpath(inputPath);
4589
- const cached = resolveCache.get(realPath);
4590
- if (cached)
4591
- return cached;
4592
- const result = doResolve(realPath);
4592
+ const result = resolveCache.get(realPath) ?? doResolve(realPath);
4593
4593
  resolveCache.set(realPath, result);
4594
+ inputCache.set(inputPath, result);
4594
4595
  return result;
4595
4596
  }
4596
4597
  function resolveWorkspaceId(inputPath) {
@@ -4891,7 +4892,7 @@ function globToRegex(glob) {
4891
4892
  function escapeRe(s) {
4892
4893
  return s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
4893
4894
  }
4894
- var DEFAULT_AUTO_WORKSPACE_MIN_REPOS, BOT_WORKTREE_MARKERS, resolveCache, gitCache, workspaceCache, wsIdCache, cachedConfig, GIT_TIMEOUT_MS, AUTO_WORKSPACE_MAX_DEPTH;
4895
+ var DEFAULT_AUTO_WORKSPACE_MIN_REPOS, BOT_WORKTREE_MARKERS, resolveCache, inputCache, gitCache, workspaceCache, wsIdCache, cachedConfig, GIT_TIMEOUT_MS, AUTO_WORKSPACE_MAX_DEPTH;
4895
4896
  var init_project_resolver = __esm({
4896
4897
  "../engine/dist/core/project-resolver.js"() {
4897
4898
  "use strict";
@@ -4899,6 +4900,7 @@ var init_project_resolver = __esm({
4899
4900
  DEFAULT_AUTO_WORKSPACE_MIN_REPOS = 3;
4900
4901
  BOT_WORKTREE_MARKERS = [".claude-pr-bot"];
4901
4902
  resolveCache = /* @__PURE__ */ new Map();
4903
+ inputCache = /* @__PURE__ */ new Map();
4902
4904
  gitCache = /* @__PURE__ */ new Map();
4903
4905
  workspaceCache = /* @__PURE__ */ new Map();
4904
4906
  wsIdCache = /* @__PURE__ */ new Map();
@@ -7156,7 +7158,7 @@ var init_raw = __esm({
7156
7158
  // ../engine/dist/transcript/shadow.js
7157
7159
  import { gzipSync as gzipSync2, gunzipSync as gunzipSync2 } from "zlib";
7158
7160
  import { createHash as createHash4 } from "crypto";
7159
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync as renameSync2, statSync as statSync7 } from "fs";
7161
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync as renameSync2, statSync as statSync7, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
7160
7162
  import { dirname as dirname9, join as join11 } from "path";
7161
7163
  function shadowRoot() {
7162
7164
  return join11(getDataDir(), "shadow");
@@ -7332,6 +7334,29 @@ function updateShadow(sessionId, exp, prebuilt) {
7332
7334
  path: path2
7333
7335
  };
7334
7336
  }
7337
+ function shadowUncompressedBytes(tool, rawId) {
7338
+ const path2 = shadowFileFor(tool, rawId);
7339
+ let fd = null;
7340
+ try {
7341
+ const size = statSync7(path2).size;
7342
+ if (size < 18)
7343
+ return 0;
7344
+ fd = openSync2(path2, "r");
7345
+ const tail = Buffer.alloc(4);
7346
+ if (readSync2(fd, tail, 0, 4, size - 4) !== 4)
7347
+ return 0;
7348
+ return tail.readUInt32LE(0);
7349
+ } catch {
7350
+ return 0;
7351
+ } finally {
7352
+ if (fd !== null) {
7353
+ try {
7354
+ closeSync2(fd);
7355
+ } catch {
7356
+ }
7357
+ }
7358
+ }
7359
+ }
7335
7360
  var WHOLE_FILE_TOOLS, SINGLETON_META;
7336
7361
  var init_shadow = __esm({
7337
7362
  "../engine/dist/transcript/shadow.js"() {
@@ -7344,7 +7369,7 @@ var init_shadow = __esm({
7344
7369
  });
7345
7370
 
7346
7371
  // ../engine/dist/core/live-session-scan.js
7347
- import { existsSync as existsSync8, readdirSync as readdirSync8, readFileSync as readFileSync6, statSync as statSync8, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
7372
+ import { existsSync as existsSync8, readdirSync as readdirSync8, readFileSync as readFileSync6, statSync as statSync8, openSync as openSync3, readSync as readSync3, closeSync as closeSync3 } from "fs";
7348
7373
  import { join as join12, basename as basename7 } from "path";
7349
7374
  function detectTool(sessionId) {
7350
7375
  return getBackendForId(sessionId)?.id ?? "claude";
@@ -7371,6 +7396,15 @@ function sessionFileIndex() {
7371
7396
  return indexCache;
7372
7397
  const byId = /* @__PURE__ */ new Map();
7373
7398
  const seenRoots = /* @__PURE__ */ new Set();
7399
+ const projectPaths = /* @__PURE__ */ new Map();
7400
+ const projectPathOf = (dirName) => {
7401
+ let p = projectPaths.get(dirName);
7402
+ if (p === void 0) {
7403
+ p = resolveProjectDirName(dirName);
7404
+ projectPaths.set(dirName, p);
7405
+ }
7406
+ return p;
7407
+ };
7374
7408
  for (const root of claudeProjectDirs()) {
7375
7409
  if (seenRoots.has(root) || !existsSync8(root))
7376
7410
  continue;
@@ -7398,7 +7432,7 @@ function sessionFileIndex() {
7398
7432
  const located = {
7399
7433
  path: join12(dir, name),
7400
7434
  projectDir: entry.name,
7401
- projectPath: resolveProjectDirName(entry.name)
7435
+ projectPath: projectPathOf(entry.name)
7402
7436
  };
7403
7437
  const prior = byId.get(id);
7404
7438
  if (prior)
@@ -8080,7 +8114,7 @@ var init_flat_string = __esm({
8080
8114
  });
8081
8115
 
8082
8116
  // ../engine/dist/core/first-prompt.js
8083
- import { createReadStream as createReadStream2, existsSync as existsSync10, openSync as openSync3, readSync as readSync3, closeSync as closeSync3 } from "fs";
8117
+ import { createReadStream as createReadStream2, existsSync as existsSync10, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
8084
8118
  import { StringDecoder } from "string_decoder";
8085
8119
  function stripBanners2(s) {
8086
8120
  let out = s;
@@ -8134,7 +8168,7 @@ function extractFirstUserPromptSync(filePath, options = {}) {
8134
8168
  const maxBytes = options.maxBytes ?? 4 * 1024 * 1024;
8135
8169
  let fd;
8136
8170
  try {
8137
- fd = openSync3(filePath, "r");
8171
+ fd = openSync4(filePath, "r");
8138
8172
  } catch {
8139
8173
  return "";
8140
8174
  }
@@ -8146,7 +8180,7 @@ function extractFirstUserPromptSync(filePath, options = {}) {
8146
8180
  let pos = 0;
8147
8181
  let lineNo = 0;
8148
8182
  for (; ; ) {
8149
- const n = readSync3(fd, buf, 0, CHUNK, pos);
8183
+ const n = readSync4(fd, buf, 0, CHUNK, pos);
8150
8184
  if (n <= 0)
8151
8185
  break;
8152
8186
  pos += n;
@@ -8172,7 +8206,7 @@ function extractFirstUserPromptSync(filePath, options = {}) {
8172
8206
  return "";
8173
8207
  } finally {
8174
8208
  try {
8175
- closeSync3(fd);
8209
+ closeSync4(fd);
8176
8210
  } catch {
8177
8211
  }
8178
8212
  }
@@ -8191,8 +8225,8 @@ var init_first_prompt = __esm({
8191
8225
  });
8192
8226
 
8193
8227
  // ../engine/dist/core/backends/tail-read.js
8194
- import { openSync as openSync4, readSync as readSync4, closeSync as closeSync4, statSync as statSync9 } from "node:fs";
8195
- function readTailFromOffset(path2, offset) {
8228
+ import { openSync as openSync5, readSync as readSync5, closeSync as closeSync5, statSync as statSync9 } from "node:fs";
8229
+ function readTailFromOffset(path2, offset, maxBytes = TAIL_READ_MAX_BYTES) {
8196
8230
  let size = 0;
8197
8231
  try {
8198
8232
  size = statSync9(path2).size;
@@ -8201,14 +8235,14 @@ function readTailFromOffset(path2, offset) {
8201
8235
  }
8202
8236
  if (size <= offset)
8203
8237
  return { text: "", newOffset: offset };
8204
- const len = Math.min(size - offset, 16 * 1024 * 1024);
8238
+ const len = Math.min(size - offset, Math.max(1, maxBytes));
8205
8239
  const buf = Buffer.allocUnsafe(len);
8206
8240
  let fd = null;
8207
8241
  try {
8208
- fd = openSync4(path2, "r");
8242
+ fd = openSync5(path2, "r");
8209
8243
  let got = 0;
8210
8244
  while (got < len) {
8211
- const n = readSync4(fd, buf, got, len - got, offset + got);
8245
+ const n = readSync5(fd, buf, got, len - got, offset + got);
8212
8246
  if (n <= 0)
8213
8247
  break;
8214
8248
  got += n;
@@ -8217,25 +8251,47 @@ function readTailFromOffset(path2, offset) {
8217
8251
  return { text: "", newOffset: offset };
8218
8252
  const slice = got < len ? buf.subarray(0, got) : buf;
8219
8253
  const lastNl = slice.lastIndexOf(10);
8220
- if (lastNl < 0) {
8221
- return { text: "", newOffset: offset };
8254
+ if (lastNl >= 0) {
8255
+ const text = slice.subarray(0, lastNl + 1).toString("utf-8");
8256
+ return { text, newOffset: offset + lastNl + 1 };
8222
8257
  }
8223
- const text = slice.subarray(0, lastNl + 1).toString("utf-8");
8224
- return { text, newOffset: offset + lastNl + 1 };
8258
+ if (offset + got >= size)
8259
+ return { text: "", newOffset: offset };
8260
+ const end = findNewline(fd, offset + got, size);
8261
+ if (end < 0)
8262
+ return { text: "", newOffset: offset };
8263
+ return { text: "", newOffset: end + 1, skippedBytes: end + 1 - offset };
8225
8264
  } catch {
8226
8265
  return { text: "", newOffset: offset };
8227
8266
  } finally {
8228
8267
  if (fd !== null) {
8229
8268
  try {
8230
- closeSync4(fd);
8269
+ closeSync5(fd);
8231
8270
  } catch {
8232
8271
  }
8233
8272
  }
8234
8273
  }
8235
8274
  }
8275
+ function findNewline(fd, from, size) {
8276
+ const block = Buffer.allocUnsafe(SKIP_SCAN_BYTES);
8277
+ let pos = from;
8278
+ while (pos < size) {
8279
+ const n = readSync5(fd, block, 0, Math.min(SKIP_SCAN_BYTES, size - pos), pos);
8280
+ if (n <= 0)
8281
+ return -1;
8282
+ const i = block.subarray(0, n).indexOf(10);
8283
+ if (i >= 0)
8284
+ return pos + i;
8285
+ pos += n;
8286
+ }
8287
+ return -1;
8288
+ }
8289
+ var TAIL_READ_MAX_BYTES, SKIP_SCAN_BYTES;
8236
8290
  var init_tail_read = __esm({
8237
8291
  "../engine/dist/core/backends/tail-read.js"() {
8238
8292
  "use strict";
8293
+ TAIL_READ_MAX_BYTES = 16 * 1024 * 1024;
8294
+ SKIP_SCAN_BYTES = 1024 * 1024;
8239
8295
  }
8240
8296
  });
8241
8297
 
@@ -8512,7 +8568,7 @@ var init_generic_engine = __esm({
8512
8568
  });
8513
8569
 
8514
8570
  // ../engine/dist/core/backends/claude.js
8515
- import { existsSync as existsSync11, readdirSync as readdirSync9, readFileSync as readFileSync7, statSync as statSync10, openSync as openSync5, readSync as readSync5, closeSync as closeSync5 } from "fs";
8571
+ import { existsSync as existsSync11, readdirSync as readdirSync9, readFileSync as readFileSync7, statSync as statSync10, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
8516
8572
  import { join as join13, basename as basename10 } from "path";
8517
8573
  function stripReminders(text) {
8518
8574
  return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
@@ -8609,13 +8665,13 @@ var init_claude2 = __esm({
8609
8665
  return null;
8610
8666
  let head;
8611
8667
  try {
8612
- const fd = openSync5(located.path, "r");
8668
+ const fd = openSync6(located.path, "r");
8613
8669
  try {
8614
8670
  const buf = Buffer.alloc(65536);
8615
- const n = readSync5(fd, buf, 0, buf.length, 0);
8671
+ const n = readSync6(fd, buf, 0, buf.length, 0);
8616
8672
  head = buf.toString("utf-8", 0, n);
8617
8673
  } finally {
8618
- closeSync5(fd);
8674
+ closeSync6(fd);
8619
8675
  }
8620
8676
  } catch {
8621
8677
  return null;
@@ -8660,12 +8716,12 @@ var init_claude2 = __esm({
8660
8716
  const seen = /* @__PURE__ */ new Set();
8661
8717
  const out = [];
8662
8718
  for (const root of claudeProjectDirs()) {
8663
- this.collectSessionsFromRoot(root, cutoff, filter, seen, out);
8719
+ this.collectSessionsFromRoot(root, cutoff, filter, seen, out, opts.previews !== false);
8664
8720
  }
8665
8721
  out.sort((a, b) => b.mtime - a.mtime);
8666
8722
  return opts.limit ? out.slice(0, opts.limit) : out;
8667
8723
  }
8668
- collectSessionsFromRoot(root, cutoff, filter, seen, out) {
8724
+ collectSessionsFromRoot(root, cutoff, filter, seen, out, previews) {
8669
8725
  if (!existsSync11(root))
8670
8726
  return;
8671
8727
  for (const proj of readdirSync9(root, { withFileTypes: true })) {
@@ -8724,7 +8780,7 @@ var init_claude2 = __esm({
8724
8780
  }
8725
8781
  if (stat3.mtimeMs < cutoff)
8726
8782
  continue;
8727
- const firstPrompt = extractFirstUserPromptSync(fullPath, { maxLength: 200 });
8783
+ const firstPrompt = previews ? extractFirstUserPromptSync(fullPath, { maxLength: 200 }) : "";
8728
8784
  out.push({
8729
8785
  toolId: "claude",
8730
8786
  rawId: sessionId,
@@ -9087,14 +9143,14 @@ var init_claude2 = __esm({
9087
9143
  spansMultipleSources(prefixedId) {
9088
9144
  return findSessionFiles(this.toRawId(prefixedId)).length > 1;
9089
9145
  }
9090
- async readFromOffset(prefixedId, offset) {
9146
+ async readFromOffset(prefixedId, offset, maxBytes) {
9091
9147
  const rawId = this.toRawId(prefixedId);
9092
9148
  const copies = findSessionFiles(rawId);
9093
9149
  if (copies.length === 0)
9094
9150
  return { text: "", newOffset: offset };
9095
9151
  if (copies.length > 1)
9096
9152
  return { text: "", newOffset: offset };
9097
- return readTailFromOffset(copies[0].path, offset);
9153
+ return readTailFromOffset(copies[0].path, offset, maxBytes);
9098
9154
  }
9099
9155
  };
9100
9156
  claudeBackend = new ClaudeBackend();
@@ -9322,23 +9378,8 @@ var init_opencode = __esm({
9322
9378
  const projectPath = row.project_path || row.directory || "";
9323
9379
  if (filter && !projectPath.toLowerCase().includes(filter))
9324
9380
  continue;
9325
- const firstUser = db.prepare(`
9326
- SELECT p.data
9327
- FROM part p JOIN message m ON m.id = p.message_id
9328
- WHERE p.session_id = ?
9329
- AND p.data LIKE '%"type":"text"%'
9330
- AND m.data LIKE '%"role":"user"%'
9331
- ORDER BY p.time_created ASC LIMIT 1
9332
- `).get(row.id);
9333
- let firstPrompt = "";
9334
- if (firstUser) {
9335
- try {
9336
- const parsed = JSON.parse(firstUser.data);
9337
- firstPrompt = flatString(String(parsed?.text || "").slice(0, 200));
9338
- } catch {
9339
- }
9340
- }
9341
- const messageCount = db.prepare("SELECT COUNT(*) as c FROM message WHERE session_id = ?").get(row.id)?.c ?? 0;
9381
+ const firstPrompt = opts.previews === false ? "" : this.firstUserPrompt(db, row.id);
9382
+ const messageCount = opts.previews === false ? 0 : db.prepare("SELECT COUNT(*) as c FROM message WHERE session_id = ?").get(row.id)?.c ?? 0;
9342
9383
  const created = new Date(row.time_created || row.time_updated).toISOString();
9343
9384
  const modified = new Date(row.time_updated).toISOString();
9344
9385
  out.push({
@@ -9362,6 +9403,45 @@ var init_opencode = __esm({
9362
9403
  db.close();
9363
9404
  }
9364
9405
  }
9406
+ /**
9407
+ * The first user text part of a session.
9408
+ *
9409
+ * Walks the session's user messages oldest first through the
9410
+ * (session_id, time_created, id) index and stops at the first one with a
9411
+ * text part. The role and the text are read by SQLite: a first message can
9412
+ * carry whole diffs in `summary` (107 MB on one row), and parsing that in JS
9413
+ * to read one field took the process to 510 MB. The earlier query filtered
9414
+ * every part and message with LIKE and then sorted, so it read all of a
9415
+ * session's data: 423 MB for a 236-session listing.
9416
+ */
9417
+ firstUserPrompt(db, sessionId) {
9418
+ try {
9419
+ const has = (table, col) => !!db.prepare(`SELECT 1 FROM pragma_table_info(?) WHERE name = ?`).get(table, col);
9420
+ const msgOrder = has("message", "time_created") ? "time_created, id" : "rowid";
9421
+ const partOrder = has("part", "time_created") ? "time_created, id" : "rowid";
9422
+ const nextUser = db.prepare(`SELECT id, ${msgOrder === "rowid" ? "rowid AS k1, 0 AS k2" : "time_created AS k1, id AS k2"} FROM message
9423
+ WHERE session_id = ? AND (${msgOrder === "rowid" ? "rowid, 0" : "time_created, id"}) > (?, ?)
9424
+ AND json_extract(data, '$.role') = 'user'
9425
+ ORDER BY ${msgOrder} LIMIT 1`);
9426
+ const firstText = db.prepare(`SELECT substr(json_extract(data, '$.text'), 1, 400) AS text FROM part
9427
+ WHERE message_id = ? AND json_extract(data, '$.type') = 'text'
9428
+ AND length(trim(json_extract(data, '$.text'))) > 0
9429
+ ORDER BY ${partOrder} LIMIT 1`);
9430
+ let k1 = Number.MIN_SAFE_INTEGER, k2 = msgOrder === "rowid" ? 0 : "";
9431
+ for (; ; ) {
9432
+ const m = nextUser.get(sessionId, k1, k2);
9433
+ if (!m)
9434
+ return "";
9435
+ k1 = m.k1;
9436
+ k2 = m.k2;
9437
+ const t = firstText.get(m.id);
9438
+ if (t?.text)
9439
+ return flatString(t.text.slice(0, 200));
9440
+ }
9441
+ } catch {
9442
+ return "";
9443
+ }
9444
+ }
9365
9445
  /** OpenCode auto-generates a session title (model-written, distinct from the
9366
9446
  * first prompt — e.g. "Replacing Claude with Gemini CLI"). It's a plain
9367
9447
  * column on the session row. */
@@ -9678,7 +9758,7 @@ var init_opencode = __esm({
9678
9758
  });
9679
9759
 
9680
9760
  // ../engine/dist/core/backends/codex.js
9681
- import { readdirSync as readdirSync10, readFileSync as readFileSync8, statSync as statSync11, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
9761
+ import { readdirSync as readdirSync10, readFileSync as readFileSync8, statSync as statSync11, openSync as openSync7, readSync as readSync7, closeSync as closeSync7 } from "fs";
9682
9762
  import { basename as basename11, dirname as dirname12, join as join15 } from "path";
9683
9763
  function parseShellWrites(cmd) {
9684
9764
  const out = [];
@@ -9924,28 +10004,29 @@ var init_codex2 = __esm({
9924
10004
  continue;
9925
10005
  let firstPrompt = "";
9926
10006
  let messageCount = 0;
9927
- try {
9928
- const lines = readFileSync8(filePath, "utf-8").split("\n");
9929
- for (const line of lines) {
9930
- if (!line.trim())
9931
- continue;
9932
- let ev;
9933
- try {
9934
- ev = JSON.parse(line);
9935
- } catch {
9936
- continue;
9937
- }
9938
- if (ev.type === "event_msg" && ev.payload?.type === "user_message") {
9939
- messageCount++;
9940
- if (!firstPrompt && ev.payload.message) {
9941
- firstPrompt = flatString(String(ev.payload.message).slice(0, 200));
10007
+ if (opts.previews !== false)
10008
+ try {
10009
+ const lines = readFileSync8(filePath, "utf-8").split("\n");
10010
+ for (const line of lines) {
10011
+ if (!line.trim())
10012
+ continue;
10013
+ let ev;
10014
+ try {
10015
+ ev = JSON.parse(line);
10016
+ } catch {
10017
+ continue;
10018
+ }
10019
+ if (ev.type === "event_msg" && ev.payload?.type === "user_message") {
10020
+ messageCount++;
10021
+ if (!firstPrompt && ev.payload.message) {
10022
+ firstPrompt = flatString(String(ev.payload.message).slice(0, 200));
10023
+ }
10024
+ } else if (ev.type === "response_item" && ev.payload?.type === "message") {
10025
+ messageCount++;
9942
10026
  }
9943
- } else if (ev.type === "response_item" && ev.payload?.type === "message") {
9944
- messageCount++;
9945
10027
  }
10028
+ } catch {
9946
10029
  }
9947
- } catch {
9948
- }
9949
10030
  const prior = seen.get(rawId);
9950
10031
  if (prior) {
9951
10032
  if (stat3.mtimeMs > prior.mtime) {
@@ -10352,10 +10433,10 @@ var init_codex2 = __esm({
10352
10433
  if (p === loc.path)
10353
10434
  continue;
10354
10435
  try {
10355
- const fd = openSync6(p, "r");
10436
+ const fd = openSync7(p, "r");
10356
10437
  const buf = Buffer.alloc(4096);
10357
- const n = readSync6(fd, buf, 0, 4096, 0);
10358
- closeSync6(fd);
10438
+ const n = readSync7(fd, buf, 0, 4096, 0);
10439
+ closeSync7(fd);
10359
10440
  const head = buf.toString("utf-8", 0, n);
10360
10441
  if (head.includes(parentId) && head.includes("parent_thread_id"))
10361
10442
  push(p, `subagents/${f}`);
@@ -10380,11 +10461,11 @@ var init_codex2 = __esm({
10380
10461
  return 0;
10381
10462
  }
10382
10463
  }
10383
- async readFromOffset(prefixedId, offset) {
10464
+ async readFromOffset(prefixedId, offset, maxBytes) {
10384
10465
  const loc = this.findSession(prefixedId);
10385
10466
  if (!loc)
10386
10467
  return { text: "", newOffset: offset };
10387
- return readTailFromOffset(loc.path, offset);
10468
+ return readTailFromOffset(loc.path, offset, maxBytes);
10388
10469
  }
10389
10470
  };
10390
10471
  codexBackend = new CodexBackend();
@@ -10635,21 +10716,22 @@ var init_agy = __esm({
10635
10716
  const projectDir = projectPath ? basename12(projectPath) : "";
10636
10717
  let firstPrompt = "";
10637
10718
  let messageCount = 0;
10638
- try {
10639
- const lines = readFileSync9(filePath, "utf-8").split("\n").filter(Boolean);
10640
- messageCount = lines.length;
10641
- for (const line of lines) {
10642
- const obj = JSON.parse(line);
10643
- if (obj.source === "USER_EXPLICIT" && obj.type === "USER_INPUT" && typeof obj.content === "string") {
10644
- const content = obj.content;
10645
- const match = content.match(/<USER_REQUEST>([\s\S]*?)<\/USER_REQUEST>/);
10646
- const rawText = match ? match[1].trim() : content.trim();
10647
- firstPrompt = flatString(rawText.slice(0, 200));
10648
- break;
10719
+ if (opts.previews !== false)
10720
+ try {
10721
+ const lines = readFileSync9(filePath, "utf-8").split("\n").filter(Boolean);
10722
+ messageCount = lines.length;
10723
+ for (const line of lines) {
10724
+ const obj = JSON.parse(line);
10725
+ if (obj.source === "USER_EXPLICIT" && obj.type === "USER_INPUT" && typeof obj.content === "string") {
10726
+ const content = obj.content;
10727
+ const match = content.match(/<USER_REQUEST>([\s\S]*?)<\/USER_REQUEST>/);
10728
+ const rawText = match ? match[1].trim() : content.trim();
10729
+ firstPrompt = flatString(rawText.slice(0, 200));
10730
+ break;
10731
+ }
10649
10732
  }
10733
+ } catch {
10650
10734
  }
10651
- } catch {
10652
- }
10653
10735
  if (seenIds.has(rawId))
10654
10736
  continue;
10655
10737
  seenIds.add(rawId);
@@ -10945,11 +11027,11 @@ var init_agy = __esm({
10945
11027
  return 0;
10946
11028
  }
10947
11029
  }
10948
- async readFromOffset(prefixedId, offset) {
11030
+ async readFromOffset(prefixedId, offset, maxBytes) {
10949
11031
  const loc = this.findSession(prefixedId);
10950
11032
  if (!loc)
10951
11033
  return { text: "", newOffset: offset };
10952
- return readTailFromOffset(loc.path, offset);
11034
+ return readTailFromOffset(loc.path, offset, maxBytes);
10953
11035
  }
10954
11036
  };
10955
11037
  agyBackend = new AgyBackend();
@@ -13340,7 +13422,10 @@ async function resolveCodeindexBin(autoInstall = true) {
13340
13422
  }
13341
13423
  function runMcp(binPath, workspace, calls, timeoutMs = 18e4) {
13342
13424
  return new Promise((resolve3, reject) => {
13343
- const p = spawn(binPath, ["--mcp", "--workspace", workspace], { stdio: ["pipe", "pipe", "ignore"] });
13425
+ const p = spawn(binPath, ["--mcp", "--workspace", workspace], {
13426
+ stdio: ["pipe", "pipe", "ignore"],
13427
+ env: { ...process.env, CODEINDEX_NO_DAEMON: "1" }
13428
+ });
13344
13429
  const res = /* @__PURE__ */ new Map();
13345
13430
  let pending2 = "";
13346
13431
  let failed = false;
@@ -14715,10 +14800,10 @@ var init_types = __esm({
14715
14800
 
14716
14801
  // ../engine/dist/transcript/opencode.js
14717
14802
  async function parseOpenCodeSubagents(parentSessionId) {
14718
- const { existsSync: existsSync47 } = await import("fs");
14803
+ const { existsSync: existsSync48 } = await import("fs");
14719
14804
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
14720
14805
  const dbPath = opencodeBackend2.dbPath();
14721
- if (!existsSync47(dbPath))
14806
+ if (!existsSync48(dbPath))
14722
14807
  return [];
14723
14808
  const realParent = parentSessionId.replace("opencode_", "");
14724
14809
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -14759,10 +14844,10 @@ async function parseOpenCodeSubagents(parentSessionId) {
14759
14844
  }
14760
14845
  }
14761
14846
  async function parseOpenCodeTranscript(sessionId) {
14762
- const { existsSync: existsSync47 } = await import("fs");
14847
+ const { existsSync: existsSync48 } = await import("fs");
14763
14848
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
14764
14849
  const dbPath = opencodeBackend2.dbPath();
14765
- if (!existsSync47(dbPath))
14850
+ if (!existsSync48(dbPath))
14766
14851
  return [];
14767
14852
  const realId = sessionId.replace("opencode_", "");
14768
14853
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -19248,7 +19333,7 @@ var init_sync_fields = __esm({
19248
19333
  });
19249
19334
 
19250
19335
  // ../engine/dist/core/index-lock.js
19251
- import { existsSync as existsSync43, mkdirSync as mkdirSync11, openSync as openSync8, writeFileSync as writeFileSync8, readFileSync as readFileSync35, rmSync as rmSync4, statSync as statSync40, closeSync as closeSync8 } from "fs";
19336
+ import { existsSync as existsSync43, mkdirSync as mkdirSync11, openSync as openSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync35, rmSync as rmSync4, statSync as statSync40, closeSync as closeSync9 } from "fs";
19252
19337
  import { dirname as dirname18, join as join47 } from "path";
19253
19338
  function acquireIndexLock(opts) {
19254
19339
  const lockPath = lockFilePath();
@@ -19260,7 +19345,7 @@ function acquireIndexLock(opts) {
19260
19345
  }
19261
19346
  let fd;
19262
19347
  try {
19263
- fd = openSync8(lockPath, "wx", 384);
19348
+ fd = openSync9(lockPath, "wx", 384);
19264
19349
  } catch (err) {
19265
19350
  if (err.code === "EEXIST")
19266
19351
  return null;
@@ -19270,7 +19355,7 @@ function acquireIndexLock(opts) {
19270
19355
  try {
19271
19356
  writeFileSync8(fd, JSON.stringify(holder));
19272
19357
  } finally {
19273
- closeSync8(fd);
19358
+ closeSync9(fd);
19274
19359
  }
19275
19360
  let released = false;
19276
19361
  return {
@@ -19364,7 +19449,7 @@ var init_http = __esm({
19364
19449
  CLI_VERSION = JSON.parse(readFileSync36(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
19365
19450
  } catch {
19366
19451
  try {
19367
- if (true) CLI_VERSION = "0.7.0";
19452
+ if (true) CLI_VERSION = "0.7.2";
19368
19453
  } catch {
19369
19454
  }
19370
19455
  }
@@ -19863,7 +19948,7 @@ var init_telemetry = __esm({
19863
19948
  init_sync_client();
19864
19949
  cliVersion2 = () => {
19865
19950
  try {
19866
- return true ? "0.7.0" : "dev";
19951
+ return true ? "0.7.2" : "dev";
19867
19952
  } catch {
19868
19953
  return "dev";
19869
19954
  }
@@ -20023,16 +20108,17 @@ __export(auto_update_exports, {
20023
20108
  noteUpdateFailure: () => noteUpdateFailure,
20024
20109
  planAutoUpdate: () => planAutoUpdate,
20025
20110
  readUpdateState: () => readUpdateState,
20111
+ resolveNpm: () => resolveNpm,
20026
20112
  runAutoUpdate: () => runAutoUpdate,
20027
20113
  runningPrefix: () => runningPrefix,
20028
20114
  sweepStaleStaging: () => sweepStaleStaging,
20029
20115
  tarballUrl: () => tarballUrl
20030
20116
  });
20031
- import { execSync as execSync4 } from "node:child_process";
20032
- import { readFileSync as readFileSync37 } from "node:fs";
20117
+ import { execSync as execSync4, execFileSync as execFileSync3 } from "node:child_process";
20118
+ import { readFileSync as readFileSync37, existsSync as existsSync45 } from "node:fs";
20033
20119
  import { fileURLToPath as fileURLToPath2 } from "node:url";
20034
20120
  import { writeFileSync as writeFileSync9, mkdtempSync as mkdtempSync3, rmSync as rmSync5, readdirSync as readdirSync32, statSync as statSync41, mkdirSync as mkdirSync12 } from "node:fs";
20035
- import { join as join48, dirname as dirname19, sep as sep3 } from "node:path";
20121
+ import { join as join48, dirname as dirname19, sep as sep3, posix as pathPosix, win32 as pathWin32 } from "node:path";
20036
20122
  import { tmpdir as tmpdir3, homedir as homedir17 } from "node:os";
20037
20123
  import { createHash as createHash10 } from "node:crypto";
20038
20124
  function compareVersions(a, b) {
@@ -20147,25 +20233,46 @@ function runningPrefix(moduleUrl = import.meta.url) {
20147
20233
  const beforeNodeModules = here.slice(0, at);
20148
20234
  return beforeNodeModules.endsWith(`${sep3}lib`) ? beforeNodeModules.slice(0, -4) : beforeNodeModules;
20149
20235
  }
20150
- function npmInstall(cmd) {
20236
+ function npmInstall(npm, args) {
20151
20237
  try {
20152
- execSync4(cmd, { stdio: ["ignore", "ignore", "pipe"] });
20238
+ const opts = { stdio: ["ignore", "ignore", "pipe"], env: npm.env };
20239
+ if (npm.shell) execSync4([npm.file, ...npm.args, ...args].map((a) => `"${a}"`).join(" "), opts);
20240
+ else execFileSync3(npm.file, [...npm.args, ...args], opts);
20153
20241
  } catch (e) {
20154
20242
  const err = e;
20155
20243
  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);
20156
20244
  throw new Error(detail ? `${err.message ?? "install failed"} \u2014 ${detail}` : err.message ?? "install failed");
20157
20245
  }
20158
20246
  }
20247
+ function resolveNpm(loc) {
20248
+ const win = loc.platform === "win32";
20249
+ const p = win ? pathWin32 : pathPosix;
20250
+ const nodeDir = p.dirname(loc.execPath);
20251
+ 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");
20252
+ const candidates2 = [
20253
+ cliUnder(win ? nodeDir : p.join(nodeDir, "..")),
20254
+ ...loc.prefix ? [cliUnder(loc.prefix)] : []
20255
+ ];
20256
+ const pathKey = Object.keys(loc.env).find((k) => k.toUpperCase() === "PATH") ?? "PATH";
20257
+ const binDirs = [nodeDir, ...loc.prefix ? [win ? loc.prefix : p.join(loc.prefix, "bin")] : []];
20258
+ const delimiter3 = win ? ";" : ":";
20259
+ const current = loc.env[pathKey] ?? "";
20260
+ const env = { ...loc.env, [pathKey]: [...binDirs, ...current ? [current] : []].join(delimiter3) };
20261
+ const cli = candidates2.find((c) => loc.exists(c));
20262
+ if (cli) return { file: loc.execPath, args: [cli], env, shell: false };
20263
+ return { file: "npm", args: [], env, shell: true };
20264
+ }
20159
20265
  function realInstall(tgz) {
20160
20266
  const prefix = runningPrefix();
20267
+ const npm = resolveNpm({ execPath: process.execPath, prefix, platform: process.platform, env: process.env, exists: existsSync45 });
20161
20268
  if (prefix) {
20162
- npmInstall(`npm install -g --prefix "${prefix}" "${tgz}"`);
20269
+ npmInstall(npm, ["install", "-g", "--prefix", prefix, tgz]);
20163
20270
  return;
20164
20271
  }
20165
20272
  try {
20166
- npmInstall(`npm install -g "${tgz}"`);
20273
+ npmInstall(npm, ["install", "-g", tgz]);
20167
20274
  } catch {
20168
- npmInstall(`npm install -g --prefix "${process.env.HOME}/.local" "${tgz}"`);
20275
+ npmInstall(npm, ["install", "-g", "--prefix", join48(homedir17(), ".local"), tgz]);
20169
20276
  }
20170
20277
  }
20171
20278
  function realRestart(platform2) {
@@ -20266,7 +20373,9 @@ var init_auto_update = __esm({
20266
20373
  var sync_client_exports = {};
20267
20374
  __export(sync_client_exports, {
20268
20375
  BATCH_SCAN_MAX_BYTES: () => BATCH_SCAN_MAX_BYTES,
20376
+ DERIVED_MAX_BYTES: () => DERIVED_MAX_BYTES,
20269
20377
  FULL_BUILD_MAX_BYTES: () => FULL_BUILD_MAX_BYTES,
20378
+ SYNC_CHUNK_BYTES: () => SYNC_CHUNK_BYTES,
20270
20379
  _acceptedUploads: () => _acceptedUploads,
20271
20380
  _fetchTenantSyncConfigForTests: () => _fetchTenantSyncConfigForTests,
20272
20381
  _resetCapsCacheForTests: () => _resetCapsCacheForTests,
@@ -20285,8 +20394,10 @@ __export(sync_client_exports, {
20285
20394
  reconcileFields: () => reconcileFields,
20286
20395
  removeCredentials: () => removeCredentials,
20287
20396
  saveCredentials: () => saveCredentials,
20397
+ scanTranscriptFindingsChunked: () => scanTranscriptFindingsChunked,
20288
20398
  serverAcceptsGzipBody: () => serverAcceptsGzipBody,
20289
20399
  serverIngestConcurrency: () => serverIngestConcurrency,
20400
+ sessionBuildBytes: () => sessionBuildBytes,
20290
20401
  sessionFileBytes: () => sessionFileBytes,
20291
20402
  setServerIngestConcurrency: () => setServerIngestConcurrency,
20292
20403
  syncIncremental: () => syncIncremental,
@@ -20676,7 +20787,7 @@ async function reconcileFields(opts = {}) {
20676
20787
  let cached = null;
20677
20788
  const fullRefs = () => cached ??= listAvailableBackends().flatMap((b) => {
20678
20789
  try {
20679
- return b.listSessions({});
20790
+ return b.listSessions({ previews: false });
20680
20791
  } catch {
20681
20792
  return [];
20682
20793
  }
@@ -20839,7 +20950,7 @@ async function syncToTargetInScope(cred, opts = {}) {
20839
20950
  const listSinceMs = changedWalk ? Math.max(0, (opts.sinceMs ?? 0) - CHANGED_WALK_OVERLAP_MS) : opts.useLedger ? void 0 : opts.sinceMs;
20840
20951
  const refs = listAvailableBackends().flatMap((b) => {
20841
20952
  try {
20842
- return b.listSessions({ sinceMs: listSinceMs });
20953
+ return b.listSessions({ sinceMs: listSinceMs, previews: false });
20843
20954
  } catch {
20844
20955
  return [];
20845
20956
  }
@@ -21058,16 +21169,53 @@ async function syncToTargetInScope(cred, opts = {}) {
21058
21169
  clearTimeout(timer);
21059
21170
  }
21060
21171
  };
21172
+ const postChunkedHead = async (conv, fileSize) => {
21173
+ const headers = { "content-type": "application/json" };
21174
+ if (cred.token) headers.authorization = `Bearer ${cred.token}`;
21175
+ const ac = new AbortController();
21176
+ const timer = setTimeout(() => ac.abort(new Error(`upload timed out after ${UPLOAD_TIMEOUT_MS}ms`)), UPLOAD_TIMEOUT_MS);
21177
+ try {
21178
+ const res = await fetch(`${base}/api/sync`, {
21179
+ method: "POST",
21180
+ headers,
21181
+ body: JSON.stringify({ conversations: [conv] }),
21182
+ signal: ac.signal
21183
+ });
21184
+ if (!res.ok) throw new Error(`chunked head sync failed: HTTP ${res.status} ${await res.text().catch(() => "")}`);
21185
+ const body = await res.json().catch(() => ({}));
21186
+ const id = conv.session_id;
21187
+ const guarded = (body.shrink_guarded ?? []).find((g) => g.session_id === id);
21188
+ const mtime = conv.mtime;
21189
+ try {
21190
+ if (!guarded) {
21191
+ markSynced(base, [{ id, mtime, offset: conv.from_offset, size: conv.from_offset, acked: true }]);
21192
+ } else if (typeof guarded.o === "number" && guarded.o > 0 && guarded.o <= fileSize) {
21193
+ markSynced(base, [{ id, mtime, offset: guarded.o, size: guarded.o, acked: true }]);
21194
+ } else {
21195
+ markSynced(base, [{ id, mtime }]);
21196
+ }
21197
+ } catch {
21198
+ }
21199
+ appendResults.uploaded++;
21200
+ } finally {
21201
+ clearTimeout(timer);
21202
+ }
21203
+ };
21061
21204
  let appendBatch = [];
21205
+ let appendBatchBytes = 0;
21062
21206
  const APPEND_BATCH_SIZE = 50;
21207
+ const APPEND_BATCH_MAX_BYTES = SYNC_CHUNK_BYTES;
21063
21208
  const appendFlush = async () => {
21064
21209
  if (appendBatch.length === 0) return;
21065
21210
  const batch = appendBatch;
21066
21211
  appendBatch = [];
21212
+ appendBatchBytes = 0;
21067
21213
  await postAppend(batch);
21068
21214
  };
21069
- const appendAdd = async (conv) => {
21215
+ const appendAdd = async (conv, bytes = 0) => {
21216
+ if (appendBatch.length > 0 && appendBatchBytes + bytes > APPEND_BATCH_MAX_BYTES) await appendFlush();
21070
21217
  appendBatch.push(conv);
21218
+ appendBatchBytes += bytes;
21071
21219
  if (appendBatch.length >= APPEND_BATCH_SIZE) await appendFlush();
21072
21220
  };
21073
21221
  const itemsBatch = makeBatcher("items", 100);
@@ -21117,7 +21265,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21117
21265
  cred,
21118
21266
  () => listAvailableBackends().flatMap((b) => {
21119
21267
  try {
21120
- return b.listSessions({});
21268
+ return b.listSessions({ previews: false });
21121
21269
  } catch {
21122
21270
  return [];
21123
21271
  }
@@ -21146,7 +21294,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21146
21294
  let externalFindings = null;
21147
21295
  if (scanSecrets) {
21148
21296
  externalFindings = /* @__PURE__ */ new Map();
21149
- const toScan = slice.filter((ref) => modeOf(ref) === "full" && sessionFileBytes(ref) <= FULL_BUILD_MAX_BYTES);
21297
+ const toScan = slice.filter((ref) => modeOf(ref) === "full" && sessionBuildBytes(ref) <= FULL_BUILD_MAX_BYTES);
21150
21298
  trace?.(`batch secret scan start: ${toScan.length} session(s), \u2264${(BATCH_SCAN_MAX_BYTES / 1048576).toFixed(0)}MB per slice`);
21151
21299
  const batch = batchScanExternal(
21152
21300
  toScan.map((ref) => ({
@@ -21190,11 +21338,17 @@ async function syncToTargetInScope(cred, opts = {}) {
21190
21338
  const ack = ledger.get(ref.prefixedId);
21191
21339
  try {
21192
21340
  const tail = await buildConversationTail(ref, ack.o ?? 0, { mapPath, tenantRules });
21193
- if (tail && tail.findings.length > 0) {
21341
+ const chunked = sessionBuildBytes(ref) > FULL_BUILD_MAX_BYTES;
21342
+ if (tail && tail.findings.length > 0 && !chunked) {
21194
21343
  promotedForFindings = true;
21195
21344
  } else if (tail) {
21196
21345
  redactions += tail.redactions;
21197
- await appendAdd(tail.conv);
21346
+ await appendAdd(tail.conv, tail.bytes);
21347
+ if (tail.findings.length > 0) {
21348
+ await appendFlush();
21349
+ const all = await scanTranscriptFindingsChunked(ref, tenantRules);
21350
+ if (all.length > 0) await findingsBatch.add(all);
21351
+ }
21198
21352
  walked++;
21199
21353
  if (walked % 250 === 0) console.error(`[sync] ${walked}/${slice.length} sessions\u2026`);
21200
21354
  continue;
@@ -21242,12 +21396,13 @@ async function syncToTargetInScope(cred, opts = {}) {
21242
21396
  }
21243
21397
  redactions += built.redactions;
21244
21398
  if (built.srcHash) shippedHash.set(ref.prefixedId, built.srcHash);
21245
- await convBatch.add(built.conv);
21399
+ if (built.conv.chunked) await postChunkedHead(built.conv, sessionFileBytes(ref));
21400
+ else await convBatch.add(built.conv);
21246
21401
  if (built.findings.length > 0) await findingsBatch.add(built.findings);
21247
21402
  if (upload.sessionMeta) {
21248
21403
  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);
21249
21404
  }
21250
- if (upload.sessionMeta && sessionFileBytes(ref) <= FULL_BUILD_MAX_BYTES) {
21405
+ if (upload.sessionMeta && sessionFileBytes(ref) <= DERIVED_MAX_BYTES) {
21251
21406
  try {
21252
21407
  const count = { redactions: 0 };
21253
21408
  const offloaded = await scanPool().runDerived({
@@ -21427,35 +21582,70 @@ async function syncToTargetInScope(cred, opts = {}) {
21427
21582
  scanMs: Math.round(scanMs)
21428
21583
  };
21429
21584
  }
21585
+ async function scanTranscriptFindingsChunked(ref, tenantRules) {
21586
+ const backend = getBackendForId(ref.prefixedId);
21587
+ const findings = [];
21588
+ if (!backend?.readFromOffset) return findings;
21589
+ let offset = 0;
21590
+ let lineBase = 0;
21591
+ for (; ; ) {
21592
+ const chunk = await backend.readFromOffset(ref.prefixedId, offset, SYNC_CHUNK_BYTES);
21593
+ if (chunk.newOffset <= offset) break;
21594
+ offset = chunk.newOffset;
21595
+ if (chunk.skippedBytes) {
21596
+ lineBase += 1;
21597
+ continue;
21598
+ }
21599
+ try {
21600
+ for (const f of scanTextForFindings(chunk.text)) {
21601
+ findings.push({ session_id: ref.prefixedId, detector: "builtin", rule: f.rule, line: lineBase + f.line, preview: f.preview });
21602
+ }
21603
+ if (tenantRules && tenantRules.length > 0) {
21604
+ for (const f of scanTenantRules(chunk.text, tenantRules)) {
21605
+ findings.push({ session_id: ref.prefixedId, detector: f.detector, rule: f.rule, line: lineBase + f.line, preview: f.preview });
21606
+ }
21607
+ }
21608
+ } catch {
21609
+ }
21610
+ for (let i = chunk.text.indexOf("\n"); i >= 0; i = chunk.text.indexOf("\n", i + 1)) lineBase++;
21611
+ }
21612
+ return dropFuzzyFindings(findings, (f) => ({ detector: f.detector, rule: f.rule }));
21613
+ }
21430
21614
  function sessionFileBytes(ref) {
21431
21615
  const backend = getBackendForId(ref.prefixedId);
21432
21616
  if (!backend?.isAppendOnly?.()) return 0;
21433
21617
  return backend.fileSize?.(ref.prefixedId) ?? 0;
21434
21618
  }
21619
+ function sessionBuildBytes(ref) {
21620
+ const main2 = sessionFileBytes(ref);
21621
+ if (main2 === 0) return 0;
21622
+ return Math.max(main2, shadowUncompressedBytes(ref.toolId, ref.rawId));
21623
+ }
21435
21624
  async function buildConversationSync(ref, mtime, opts = {}) {
21436
21625
  const mapPath = opts.mapPath ?? ((p) => p);
21437
21626
  let includeRaw = opts.includeRaw !== false;
21438
21627
  let includeMeta = opts.includeMeta !== false;
21439
21628
  let srcHash;
21440
- const fileBytes = sessionFileBytes(ref);
21441
- const oversized = fileBytes > FULL_BUILD_MAX_BYTES;
21442
- let tailText = "";
21443
- let tailOffsetEnd = 0;
21629
+ const oversized = sessionBuildBytes(ref) > FULL_BUILD_MAX_BYTES;
21630
+ let headOffsetEnd = 0;
21631
+ let oversizedFindings = null;
21444
21632
  let transcript = null;
21445
21633
  let fullestContainer = null;
21446
21634
  if (oversized) {
21447
21635
  const backend = getBackendForId(ref.prefixedId);
21448
21636
  if (!backend?.readFromOffset) return null;
21449
21637
  try {
21450
- const tail = await backend.readFromOffset(ref.prefixedId, fileBytes - FULL_BUILD_TAIL_BYTES);
21451
- tailText = tail.text;
21452
- tailOffsetEnd = tail.newOffset;
21453
- const messages = parseTailMessages(ref.toolId, tailText);
21638
+ let head = await backend.readFromOffset(ref.prefixedId, 0, SYNC_CHUNK_BYTES);
21639
+ for (let hops = 0; head.skippedBytes && hops < 8; hops++) {
21640
+ head = await backend.readFromOffset(ref.prefixedId, head.newOffset, SYNC_CHUNK_BYTES);
21641
+ }
21642
+ headOffsetEnd = head.newOffset;
21643
+ const messages = parseTailMessages(ref.toolId, head.text);
21454
21644
  if (messages.length > 0) transcript = { messages, subagents: [] };
21455
21645
  } catch {
21456
21646
  }
21457
21647
  includeRaw = false;
21458
- includeMeta = false;
21648
+ oversizedFindings = await scanTranscriptFindingsChunked(ref, opts.tenantRules);
21459
21649
  } else {
21460
21650
  try {
21461
21651
  const backend = getBackendForId(ref.prefixedId) ?? getBackend("claude");
@@ -21516,7 +21706,6 @@ async function buildConversationSync(ref, mtime, opts = {}) {
21516
21706
  );
21517
21707
  let projectPath = ref.projectPath;
21518
21708
  const meta = { messageCount: textMessages.length };
21519
- if (oversized) meta.truncated = true;
21520
21709
  if (textMessages.filter((m) => m.role === "user").length <= 1) meta.oneShot = true;
21521
21710
  try {
21522
21711
  const cwd = ref.fullPath ? readCwdFromJsonl(ref.fullPath) : "";
@@ -21630,21 +21819,7 @@ async function buildConversationSync(ref, mtime, opts = {}) {
21630
21819
  }
21631
21820
  scanMs = performance.now() - t0;
21632
21821
  }
21633
- if (oversized && tailText) {
21634
- const t0 = performance.now();
21635
- try {
21636
- for (const f of scanTextForFindings(tailText)) {
21637
- findings.push({ session_id: ref.prefixedId, detector: "builtin", rule: f.rule, line: f.line, preview: f.preview });
21638
- }
21639
- if (opts.tenantRules && opts.tenantRules.length > 0) {
21640
- for (const f of scanTenantRules(tailText, opts.tenantRules)) {
21641
- findings.push({ session_id: ref.prefixedId, detector: f.detector, rule: f.rule, line: f.line, preview: f.preview });
21642
- }
21643
- }
21644
- } catch {
21645
- }
21646
- scanMs = performance.now() - t0;
21647
- }
21822
+ if (oversizedFindings) findings.push(...oversizedFindings);
21648
21823
  const keptFindings = dropFuzzyFindings(findings, (f) => ({ detector: f.detector, rule: f.rule }));
21649
21824
  findings.length = 0;
21650
21825
  findings.push(...keptFindings);
@@ -21690,7 +21865,10 @@ async function buildConversationSync(ref, mtime, opts = {}) {
21690
21865
  // (convBatch onFlush uses this, not a recomputed size) so the next
21691
21866
  // append's base_offset matches exactly — no spurious continuity misses on
21692
21867
  // an actively-growing session. undefined for non-append-only backends.
21693
- from_offset: oversized ? tailOffsetEnd : getBackendForId(ref.prefixedId)?.fileSize?.(ref.rawId),
21868
+ from_offset: oversized ? headOffsetEnd : getBackendForId(ref.prefixedId)?.fileSize?.(ref.rawId),
21869
+ // The head of a chunked session. The server reports back when its shrink
21870
+ // guard keeps a fuller stored copy, so the client appends from there.
21871
+ ...oversized ? { chunked: true } : {},
21694
21872
  meta,
21695
21873
  mtime
21696
21874
  },
@@ -21712,12 +21890,30 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
21712
21890
  const backend = getBackendForId(ref.prefixedId);
21713
21891
  if (!backend?.readFromOffset) return null;
21714
21892
  const mapPath = opts.mapPath ?? ((p) => p);
21715
- const { text: tailText, newOffset } = await backend.readFromOffset(ref.prefixedId, fromOffset);
21716
- if (!tailText.trim()) return null;
21717
- const messages = parseTailMessages(ref.toolId, tailText);
21718
- if (messages.length === 0) return null;
21893
+ const { text: tailText, newOffset, skippedBytes } = await backend.readFromOffset(ref.prefixedId, fromOffset, SYNC_CHUNK_BYTES);
21894
+ if (newOffset <= fromOffset) return null;
21895
+ const messages = skippedBytes ? [] : parseTailMessages(ref.toolId, tailText);
21719
21896
  const textMessages = messages.filter((m) => m.role !== "summary" && m.content?.trim());
21720
- if (textMessages.length === 0 && !messages.some((m) => m.toolCalls?.length)) return null;
21897
+ const hasContent = textMessages.length > 0 || messages.some((m) => m.toolCalls?.length);
21898
+ if (!hasContent) {
21899
+ return {
21900
+ conv: {
21901
+ session_id: ref.prefixedId,
21902
+ tool: ref.toolId,
21903
+ project_path: mapPath(ref.projectPath),
21904
+ append: true,
21905
+ base_offset: fromOffset,
21906
+ from_offset: newOffset,
21907
+ redacted_text: "",
21908
+ envelope: { v: TRANSCRIPT_VERSION, messages: [], subagents: [] },
21909
+ mtime: Math.floor(ref.mtime) || 0
21910
+ },
21911
+ redactions: 0,
21912
+ newOffset,
21913
+ findings: [],
21914
+ bytes: 0
21915
+ };
21916
+ }
21721
21917
  const firstUser = textMessages.find((m) => m.role === "user");
21722
21918
  if (isInternalToolPrompt(firstUser?.content)) return null;
21723
21919
  const count = { redactions: 0 };
@@ -21760,7 +21956,8 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
21760
21956
  },
21761
21957
  redactions: count.redactions,
21762
21958
  newOffset,
21763
- findings
21959
+ findings,
21960
+ bytes: Buffer.byteLength(tailText)
21764
21961
  };
21765
21962
  }
21766
21963
  function isSyncSkip(r) {
@@ -21790,7 +21987,7 @@ async function syncIncremental(opts = {}) {
21790
21987
  lock.release();
21791
21988
  }
21792
21989
  }
21793
- 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;
21990
+ 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;
21794
21991
  var init_sync_client = __esm({
21795
21992
  "src/sync-client.ts"() {
21796
21993
  "use strict";
@@ -21835,7 +22032,7 @@ var init_sync_client = __esm({
21835
22032
  ownVersion = JSON.parse(readFileSync38(join49(dir, "../package.json"), "utf-8")).version || ownVersion;
21836
22033
  } catch {
21837
22034
  try {
21838
- if (true) ownVersion = "0.7.0";
22035
+ if (true) ownVersion = "0.7.2";
21839
22036
  } catch {
21840
22037
  }
21841
22038
  }
@@ -21862,8 +22059,9 @@ var init_sync_client = __esm({
21862
22059
  capsOk = /* @__PURE__ */ new Map();
21863
22060
  advertisedIngestConc = /* @__PURE__ */ new Map();
21864
22061
  advertisedGzipBody = /* @__PURE__ */ new Set();
21865
- FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "64", 10)) * 1024 * 1024;
21866
- FULL_BUILD_TAIL_BYTES = 16 * 1024 * 1024;
22062
+ FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "24", 10)) * 1024 * 1024;
22063
+ DERIVED_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_DERIVED_MAX_MB || "64", 10)) * 1024 * 1024;
22064
+ SYNC_CHUNK_BYTES = 8 * 1024 * 1024;
21867
22065
  BATCH_SCAN_MAX_BYTES = Math.max(1, parseInt(process.env.CHAT_RECALL_BATCHSCAN_MAX_MB || "64", 10)) * 1024 * 1024;
21868
22066
  }
21869
22067
  });
@@ -22048,306 +22246,101 @@ var init_update_notice = __esm({
22048
22246
  }
22049
22247
  });
22050
22248
 
22051
- // src/install-skills.ts
22052
- var install_skills_exports = {};
22053
- __export(install_skills_exports, {
22054
- bundledSkillNames: () => bundledSkillNames,
22055
- installSkills: () => installSkills,
22056
- skillStatus: () => skillStatus,
22057
- skillTargets: () => skillTargets,
22058
- skillsNeedRefresh: () => skillsNeedRefresh,
22059
- skillsSourceDir: () => skillsSourceDir,
22060
- uninstallSkills: () => uninstallSkills
22249
+ // src/service-installer.ts
22250
+ var service_installer_exports = {};
22251
+ __export(service_installer_exports, {
22252
+ ServiceInstallError: () => ServiceInstallError,
22253
+ installService: () => installService,
22254
+ isServiceRunning: () => isServiceRunning,
22255
+ platformName: () => platformName,
22256
+ refreshServiceDefinition: () => refreshServiceDefinition,
22257
+ renderLaunchdPlist: () => renderLaunchdPlist,
22258
+ renderSystemdUnit: () => renderSystemdUnit,
22259
+ renderWindowsTaskCommand: () => renderWindowsTaskCommand,
22260
+ uninstallService: () => uninstallService
22061
22261
  });
22062
- import { readdirSync as readdirSync34, existsSync as existsSync45, mkdirSync as mkdirSync14, rmSync as rmSync7, cpSync, writeFileSync as writeFileSync12, readFileSync as readFileSync41, statSync as statSync43, realpathSync as realpathSync4 } from "fs";
22063
- import { createHash as createHash12 } from "crypto";
22064
- import { join as join51, basename as basename23 } from "path";
22065
- function skillsSourceDir() {
22066
- return join51(import.meta.dirname, "..", "skills");
22262
+ import { execSync as execSync5 } from "child_process";
22263
+ import { existsSync as existsSync46, mkdirSync as mkdirSync14, readFileSync as readFileSync41, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
22264
+ import { homedir as homedir18 } from "os";
22265
+ import { join as join51 } from "path";
22266
+ import { fileURLToPath as fileURLToPath4 } from "url";
22267
+ function resolveDaemonPaths() {
22268
+ const watchJs = fileURLToPath4(new URL("./watch.js", import.meta.url));
22269
+ const node = process.execPath;
22270
+ const dataDir = join51(homedir18(), ".chat-recall");
22271
+ const logFile = join51(dataDir, "watch.log");
22272
+ return { watchJs, node, logFile, dataDir };
22067
22273
  }
22068
- function version() {
22274
+ function tryCmd(cmd) {
22069
22275
  try {
22070
- return JSON.parse(readFileSync41(new URL("../package.json", import.meta.url), "utf-8")).version || "0.0.0";
22276
+ return execSync5(cmd, { stdio: ["ignore", "pipe", "ignore"] }).toString();
22071
22277
  } catch {
22072
- return "0.0.0";
22278
+ return "";
22073
22279
  }
22074
22280
  }
22075
- function skillsFingerprint() {
22076
- const src = skillsSourceDir();
22077
- const h = createHash12("sha256");
22078
- for (const name of bundledSkillNames()) {
22079
- for (const rel of filesUnder(join51(src, name)).sort()) {
22080
- h.update(name + "/" + rel);
22081
- h.update(readFileSync41(join51(src, name, rel)));
22082
- }
22281
+ function isServiceRunning() {
22282
+ if (process.platform === "win32") {
22283
+ if (/Running/i.test(tryCmd("schtasks /query /tn chat-recall-watch /fo LIST /v 2>nul"))) return true;
22284
+ return /watch\.js/i.test(tryCmd(`wmic process where "name='node.exe'" get commandline 2>nul`));
22083
22285
  }
22084
- return h.digest("hex").slice(0, 12);
22085
- }
22086
- function filesUnder(dir, prefix = "") {
22087
- const out = [];
22088
- for (const e of readdirSync34(dir, { withFileTypes: true })) {
22089
- if (e.name === MARKER) continue;
22090
- const rel = prefix ? `${prefix}/${e.name}` : e.name;
22091
- if (e.isDirectory()) out.push(...filesUnder(join51(dir, e.name), rel));
22092
- else out.push(rel);
22286
+ if (process.platform === "darwin") {
22287
+ return /com\.chat-recall\.watch/.test(tryCmd("launchctl list 2>/dev/null"));
22093
22288
  }
22094
- return out;
22095
- }
22096
- function skillStamp() {
22097
- return `${version()} ${skillsFingerprint()}`;
22098
- }
22099
- function bundledSkillNames() {
22100
- const src = skillsSourceDir();
22101
- if (!existsSync45(src)) return [];
22102
- return readdirSync34(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync45(join51(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
22289
+ if (process.platform === "linux") {
22290
+ return /^active$/m.test(tryCmd("systemctl --user is-active chat-recall-watch.service 2>/dev/null"));
22291
+ }
22292
+ return false;
22103
22293
  }
22104
- function realPathOrSelf(p) {
22294
+ function refreshServiceDefinition() {
22295
+ if (process.platform !== "linux") return false;
22105
22296
  try {
22106
- return realpathSync4(p);
22297
+ const { watchJs, node, logFile } = resolveDaemonPaths();
22298
+ const unitPath = join51(homedir18(), ".config", "systemd", "user", "chat-recall-watch.service");
22299
+ if (!existsSync46(unitPath)) return false;
22300
+ const current = readFileSync41(unitPath, "utf8");
22301
+ if (!current.includes(watchJs)) return false;
22302
+ const wanted = renderSystemdUnit(watchJs, node, logFile);
22303
+ if (readFileSync41(unitPath, "utf8") === wanted) return false;
22304
+ writeFileSync12(unitPath, wanted);
22305
+ execSync5("systemctl --user daemon-reload", { stdio: "ignore" });
22306
+ return true;
22107
22307
  } catch {
22108
- return p;
22308
+ return false;
22109
22309
  }
22110
22310
  }
22111
- function claudeSkillTargets() {
22112
- const targets = [];
22113
- const seen = /* @__PURE__ */ new Set();
22114
- for (const home of claudeHomeDirs()) {
22115
- const dir = join51(home, "skills");
22116
- const key2 = realPathOrSelf(dir);
22117
- if (seen.has(key2)) continue;
22118
- seen.add(key2);
22119
- const primary = targets.length === 0;
22120
- targets.push({
22121
- id: primary ? "claude" : `claude:${basename23(home)}`,
22122
- label: primary ? "Claude Code" : `Claude Code (${basename23(home)})`,
22123
- dir,
22124
- // Judge each profile on ITS OWN transcripts, not the primary's: a home
22125
- // with no projects/ is a directory someone made, not a Claude install.
22126
- available: existsSync45(join51(home, "projects"))
22127
- });
22311
+ function installService() {
22312
+ const { watchJs, node, logFile, dataDir } = resolveDaemonPaths();
22313
+ mkdirSync14(dataDir, { recursive: true });
22314
+ if (process.platform === "linux") {
22315
+ return installSystemd(watchJs, node, logFile);
22128
22316
  }
22129
- if (targets.length === 0) {
22130
- targets.push({ id: "claude", label: "Claude Code", dir: claudeBackend.skillsDir(), available: claudeBackend.isAvailable() });
22317
+ if (process.platform === "darwin") {
22318
+ return installLaunchd(watchJs, node, logFile);
22131
22319
  }
22132
- return targets;
22133
- }
22134
- function skillTargets() {
22135
- return [
22136
- // Per-tool dirs ONLY. We deliberately do NOT also write ~/.agents/skills:
22137
- // some tools read BOTH ~/.agents/skills and their own dir, so installing to
22138
- // both produces "skill conflict" duplicate warnings. Every tool we support
22139
- // has its own dir, so per-tool coverage is complete.
22140
- ...claudeSkillTargets(),
22141
- { id: "agy", label: "Antigravity", dir: join51(agyBackend.homeDir(), "skills"), available: agyBackend.isAvailable() },
22142
- { id: "codex", label: "Codex", dir: codexBackend.skillsDir(), available: codexBackend.isAvailable() },
22143
- { id: "opencode", label: "OpenCode", dir: opencodeBackend.skillsDir(), available: opencodeBackend.isAvailable() },
22144
- // Cursor DOES have its own dir (~/.cursor/skills). `skills-cursor` next to
22145
- // it is Cursor's bundled set — read-only, never an install target.
22146
- { id: "cursor", label: "Cursor", dir: join51(cursorHomeDir(), "skills"), available: cursorBackend.isAvailable() }
22147
- ];
22320
+ if (process.platform === "win32") {
22321
+ return installWindowsTask(watchJs, node, logFile);
22322
+ }
22323
+ throw new ServiceInstallError(
22324
+ `--install-service: unsupported platform '${process.platform}'.`,
22325
+ "Run `chat-recall watch` in the foreground, or point your OS scheduler at `chat-recall-watch`."
22326
+ );
22148
22327
  }
22149
- function isManaged(dir) {
22150
- return existsSync45(join51(dir, MARKER));
22151
- }
22152
- function installSkills(opts = {}) {
22153
- const onlyAvailable = opts.onlyAvailable ?? true;
22154
- const src = skillsSourceDir();
22155
- const names = bundledSkillNames();
22156
- const v = version();
22157
- const stamp = skillStamp();
22158
- const perTarget = [];
22159
- for (const t of skillTargets()) {
22160
- const installed = [];
22161
- const skippedUserOwned = [];
22162
- if (onlyAvailable && !t.available) {
22163
- perTarget.push({ ...t, installed, skippedUserOwned });
22164
- continue;
22165
- }
22166
- for (const name of names) {
22167
- const dest = join51(t.dir, name);
22168
- if (existsSync45(dest) && !isManaged(dest)) {
22169
- skippedUserOwned.push(name);
22170
- continue;
22171
- }
22172
- mkdirSync14(t.dir, { recursive: true });
22173
- rmSync7(dest, { recursive: true, force: true });
22174
- cpSync(join51(src, name), dest, { recursive: true });
22175
- writeFileSync12(join51(dest, MARKER), `${stamp}
22176
- `);
22177
- installed.push(name);
22178
- }
22179
- perTarget.push({ ...t, installed, skippedUserOwned });
22180
- }
22181
- return { version: v, perTarget };
22182
- }
22183
- function uninstallSkills() {
22184
- const names = new Set(bundledSkillNames());
22185
- const perTarget = [];
22186
- for (const t of skillTargets()) {
22187
- const removed = [];
22188
- if (!existsSync45(t.dir)) {
22189
- perTarget.push({ id: t.id, label: t.label, removed });
22190
- continue;
22191
- }
22192
- for (const entry of readdirSync34(t.dir, { withFileTypes: true })) {
22193
- if (!entry.isDirectory()) continue;
22194
- const dir = join51(t.dir, entry.name);
22195
- if (isManaged(dir) && (names.has(entry.name) || entry.name.startsWith("chat-recall"))) {
22196
- rmSync7(dir, { recursive: true, force: true });
22197
- removed.push(entry.name);
22198
- }
22199
- }
22200
- perTarget.push({ id: t.id, label: t.label, removed });
22201
- }
22202
- return { perTarget };
22203
- }
22204
- function skillsNeedRefresh() {
22205
- const cur = skillStamp();
22206
- const names = bundledSkillNames();
22207
- if (names.length === 0) return false;
22208
- for (const t of skillTargets()) {
22209
- if (!t.available) continue;
22210
- for (const name of names) {
22211
- const marker = join51(t.dir, name, MARKER);
22212
- if (!existsSync45(marker)) return true;
22213
- try {
22214
- if (readFileSync41(marker, "utf-8").trim() !== cur) return true;
22215
- } catch {
22216
- return true;
22217
- }
22218
- }
22219
- }
22220
- return false;
22221
- }
22222
- function skillStatus() {
22223
- const names = bundledSkillNames();
22224
- const cur = skillStamp();
22225
- return skillTargets().map((t) => {
22226
- let installed = 0;
22227
- let ver = null;
22228
- for (const name of names) {
22229
- const marker = join51(t.dir, name, MARKER);
22230
- if (existsSync45(marker)) {
22231
- installed++;
22232
- try {
22233
- ver = readFileSync41(marker, "utf-8").trim();
22234
- } catch {
22235
- }
22236
- }
22237
- }
22238
- return { id: t.id, label: t.label, available: t.available, installed, version: installed === names.length ? ver : installed ? `${ver} (partial)` : null };
22239
- }).filter((s) => s.available || s.installed > 0);
22240
- }
22241
- var MARKER;
22242
- var init_install_skills = __esm({
22243
- "src/install-skills.ts"() {
22244
- "use strict";
22245
- init_tool_paths();
22246
- init_claude2();
22247
- init_opencode();
22248
- init_codex2();
22249
- init_agy();
22250
- init_cursor();
22251
- init_tool_paths();
22252
- MARKER = ".chat-recall-managed";
22253
- }
22254
- });
22255
-
22256
- // src/service-installer.ts
22257
- var service_installer_exports = {};
22258
- __export(service_installer_exports, {
22259
- ServiceInstallError: () => ServiceInstallError,
22260
- installService: () => installService,
22261
- isServiceRunning: () => isServiceRunning,
22262
- platformName: () => platformName,
22263
- refreshServiceDefinition: () => refreshServiceDefinition,
22264
- renderLaunchdPlist: () => renderLaunchdPlist,
22265
- renderSystemdUnit: () => renderSystemdUnit,
22266
- renderWindowsTaskCommand: () => renderWindowsTaskCommand,
22267
- uninstallService: () => uninstallService
22268
- });
22269
- import { execSync as execSync5 } from "child_process";
22270
- import { existsSync as existsSync46, mkdirSync as mkdirSync15, readFileSync as readFileSync42, rmSync as rmSync8, writeFileSync as writeFileSync13 } from "fs";
22271
- import { homedir as homedir18 } from "os";
22272
- import { join as join52 } from "path";
22273
- import { fileURLToPath as fileURLToPath4 } from "url";
22274
- function resolveDaemonPaths() {
22275
- const watchJs = fileURLToPath4(new URL("./watch.js", import.meta.url));
22276
- const node = process.execPath;
22277
- const dataDir = join52(homedir18(), ".chat-recall");
22278
- const logFile = join52(dataDir, "watch.log");
22279
- return { watchJs, node, logFile, dataDir };
22280
- }
22281
- function tryCmd(cmd) {
22282
- try {
22283
- return execSync5(cmd, { stdio: ["ignore", "pipe", "ignore"] }).toString();
22284
- } catch {
22285
- return "";
22286
- }
22287
- }
22288
- function isServiceRunning() {
22289
- if (process.platform === "win32") {
22290
- if (/Running/i.test(tryCmd("schtasks /query /tn chat-recall-watch /fo LIST /v 2>nul"))) return true;
22291
- return /watch\.js/i.test(tryCmd(`wmic process where "name='node.exe'" get commandline 2>nul`));
22292
- }
22293
- if (process.platform === "darwin") {
22294
- return /com\.chat-recall\.watch/.test(tryCmd("launchctl list 2>/dev/null"));
22295
- }
22296
- if (process.platform === "linux") {
22297
- return /active/.test(tryCmd("systemctl --user is-active chat-recall-watch.service 2>/dev/null"));
22298
- }
22299
- return false;
22300
- }
22301
- function refreshServiceDefinition() {
22302
- if (process.platform !== "linux") return false;
22303
- try {
22304
- const { watchJs, node, logFile } = resolveDaemonPaths();
22305
- const unitPath = join52(homedir18(), ".config", "systemd", "user", "chat-recall-watch.service");
22306
- if (!existsSync46(unitPath)) return false;
22307
- const current = readFileSync42(unitPath, "utf8");
22308
- if (!current.includes(watchJs)) return false;
22309
- const wanted = renderSystemdUnit(watchJs, node, logFile);
22310
- if (readFileSync42(unitPath, "utf8") === wanted) return false;
22311
- writeFileSync13(unitPath, wanted);
22312
- execSync5("systemctl --user daemon-reload", { stdio: "ignore" });
22313
- return true;
22314
- } catch {
22315
- return false;
22316
- }
22317
- }
22318
- function installService() {
22319
- const { watchJs, node, logFile, dataDir } = resolveDaemonPaths();
22320
- mkdirSync15(dataDir, { recursive: true });
22321
- if (process.platform === "linux") {
22322
- return installSystemd(watchJs, node, logFile);
22323
- }
22324
- if (process.platform === "darwin") {
22325
- return installLaunchd(watchJs, node, logFile);
22326
- }
22327
- if (process.platform === "win32") {
22328
- return installWindowsTask(watchJs, node, logFile);
22329
- }
22330
- throw new ServiceInstallError(
22331
- `--install-service: unsupported platform '${process.platform}'.`,
22332
- "Run `chat-recall watch` in the foreground, or point your OS scheduler at `chat-recall-watch`."
22333
- );
22334
- }
22335
- function installSystemd(watchJs, node, logFile) {
22336
- const unitDir = join52(homedir18(), ".config", "systemd", "user");
22337
- const unitPath = join52(unitDir, "chat-recall-watch.service");
22338
- const unit = renderSystemdUnit(watchJs, node, logFile);
22339
- mkdirSync15(unitDir, { recursive: true });
22340
- writeFileSync13(unitPath, unit);
22341
- try {
22342
- execSync5("systemctl --user daemon-reload && systemctl --user enable chat-recall-watch.service && systemctl --user restart chat-recall-watch.service", { stdio: "inherit" });
22343
- try {
22344
- execSync5('loginctl enable-linger "$USER" 2>/dev/null', { stdio: "ignore" });
22345
- } catch {
22346
- }
22347
- } catch (err) {
22348
- throw new ServiceInstallError("Unit written but systemctl failed.", `Unit: ${unitPath}`);
22349
- }
22350
- return { definitionPath: unitPath, logFile };
22328
+ function installSystemd(watchJs, node, logFile) {
22329
+ const unitDir = join51(homedir18(), ".config", "systemd", "user");
22330
+ const unitPath = join51(unitDir, "chat-recall-watch.service");
22331
+ const unit = renderSystemdUnit(watchJs, node, logFile);
22332
+ mkdirSync14(unitDir, { recursive: true });
22333
+ writeFileSync12(unitPath, unit);
22334
+ try {
22335
+ execSync5("systemctl --user daemon-reload && systemctl --user enable chat-recall-watch.service && systemctl --user restart chat-recall-watch.service", { stdio: "inherit" });
22336
+ try {
22337
+ execSync5('loginctl enable-linger "$USER" 2>/dev/null', { stdio: "ignore" });
22338
+ } catch {
22339
+ }
22340
+ } catch (err) {
22341
+ throw new ServiceInstallError("Unit written but systemctl failed.", `Unit: ${unitPath}`);
22342
+ }
22343
+ return { definitionPath: unitPath, logFile };
22351
22344
  }
22352
22345
  function renderSystemdUnit(watchJs, node, logFile) {
22353
22346
  return [
@@ -22370,7 +22363,12 @@ function renderSystemdUnit(watchJs, node, logFile) {
22370
22363
  // sawtooth. 1536MB keeps GC honest (768 OOMed on the largest subagent-fanout session) with plenty of headroom for the
22371
22364
  // largest transcripts.
22372
22365
  `ExecStart=${node} --max-old-space-size=1536 ${watchJs}`,
22373
- "Restart=on-failure",
22366
+ // ALWAYS: the collector never ends its own work, so any exit systemd did
22367
+ // not ask for is a fault. Under on-failure a clean exit left it dead: on
22368
+ // 2026-09-16 the unit went "inactive (dead) … status=0/SUCCESS" and stayed
22369
+ // down for 8 days, so nothing synced and the auto-update tick, which runs
22370
+ // inside the daemon, never ran. `systemctl stop` still stops it.
22371
+ "Restart=always",
22374
22372
  "RestartSec=10",
22375
22373
  "Nice=10",
22376
22374
  // NEVER STOP RETRYING. systemd's default start limiter gives up after a
@@ -22408,11 +22406,11 @@ function renderSystemdUnit(watchJs, node, logFile) {
22408
22406
  }
22409
22407
  function installLaunchd(watchJs, node, logFile) {
22410
22408
  const label = "com.chat-recall.watch";
22411
- const plistDir = join52(homedir18(), "Library", "LaunchAgents");
22412
- const plistPath = join52(plistDir, `${label}.plist`);
22409
+ const plistDir = join51(homedir18(), "Library", "LaunchAgents");
22410
+ const plistPath = join51(plistDir, `${label}.plist`);
22413
22411
  const plist = renderLaunchdPlist(watchJs, node, logFile, label);
22414
- mkdirSync15(plistDir, { recursive: true });
22415
- writeFileSync13(plistPath, plist);
22412
+ mkdirSync14(plistDir, { recursive: true });
22413
+ writeFileSync12(plistPath, plist);
22416
22414
  try {
22417
22415
  try {
22418
22416
  execSync5(`launchctl unload "${plistPath}"`, { stdio: "ignore" });
@@ -22465,9 +22463,9 @@ function uninstallService() {
22465
22463
  removed = true;
22466
22464
  } catch {
22467
22465
  }
22468
- const unitPath = join52(homedir18(), ".config", "systemd", "user", "chat-recall-watch.service");
22466
+ const unitPath = join51(homedir18(), ".config", "systemd", "user", "chat-recall-watch.service");
22469
22467
  if (existsSync46(unitPath)) {
22470
- rmSync8(unitPath, { force: true });
22468
+ rmSync7(unitPath, { force: true });
22471
22469
  removed = true;
22472
22470
  }
22473
22471
  try {
@@ -22477,14 +22475,14 @@ function uninstallService() {
22477
22475
  return removed;
22478
22476
  }
22479
22477
  if (process.platform === "darwin") {
22480
- const plistPath = join52(homedir18(), "Library", "LaunchAgents", "com.chat-recall.watch.plist");
22478
+ const plistPath = join51(homedir18(), "Library", "LaunchAgents", "com.chat-recall.watch.plist");
22481
22479
  try {
22482
22480
  execSync5(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
22483
22481
  } catch {
22484
22482
  }
22485
22483
  let removed = false;
22486
22484
  if (existsSync46(plistPath)) {
22487
- rmSync8(plistPath, { force: true });
22485
+ rmSync7(plistPath, { force: true });
22488
22486
  removed = true;
22489
22487
  }
22490
22488
  return removed;
@@ -22519,6 +22517,211 @@ var init_service_installer = __esm({
22519
22517
  }
22520
22518
  });
22521
22519
 
22520
+ // src/install-skills.ts
22521
+ var install_skills_exports = {};
22522
+ __export(install_skills_exports, {
22523
+ bundledSkillNames: () => bundledSkillNames,
22524
+ installSkills: () => installSkills,
22525
+ skillStatus: () => skillStatus,
22526
+ skillTargets: () => skillTargets,
22527
+ skillsNeedRefresh: () => skillsNeedRefresh,
22528
+ skillsSourceDir: () => skillsSourceDir,
22529
+ uninstallSkills: () => uninstallSkills
22530
+ });
22531
+ import { readdirSync as readdirSync34, existsSync as existsSync47, mkdirSync as mkdirSync15, rmSync as rmSync8, cpSync, writeFileSync as writeFileSync13, readFileSync as readFileSync42, statSync as statSync43, realpathSync as realpathSync4 } from "fs";
22532
+ import { createHash as createHash12 } from "crypto";
22533
+ import { join as join52, basename as basename23 } from "path";
22534
+ function skillsSourceDir() {
22535
+ return join52(import.meta.dirname, "..", "skills");
22536
+ }
22537
+ function version() {
22538
+ try {
22539
+ return JSON.parse(readFileSync42(new URL("../package.json", import.meta.url), "utf-8")).version || "0.0.0";
22540
+ } catch {
22541
+ return "0.0.0";
22542
+ }
22543
+ }
22544
+ function skillsFingerprint() {
22545
+ const src = skillsSourceDir();
22546
+ const h = createHash12("sha256");
22547
+ for (const name of bundledSkillNames()) {
22548
+ for (const rel of filesUnder(join52(src, name)).sort()) {
22549
+ h.update(name + "/" + rel);
22550
+ h.update(readFileSync42(join52(src, name, rel)));
22551
+ }
22552
+ }
22553
+ return h.digest("hex").slice(0, 12);
22554
+ }
22555
+ function filesUnder(dir, prefix = "") {
22556
+ const out = [];
22557
+ for (const e of readdirSync34(dir, { withFileTypes: true })) {
22558
+ if (e.name === MARKER) continue;
22559
+ const rel = prefix ? `${prefix}/${e.name}` : e.name;
22560
+ if (e.isDirectory()) out.push(...filesUnder(join52(dir, e.name), rel));
22561
+ else out.push(rel);
22562
+ }
22563
+ return out;
22564
+ }
22565
+ function skillStamp() {
22566
+ return `${version()} ${skillsFingerprint()}`;
22567
+ }
22568
+ function bundledSkillNames() {
22569
+ const src = skillsSourceDir();
22570
+ if (!existsSync47(src)) return [];
22571
+ return readdirSync34(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync47(join52(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
22572
+ }
22573
+ function realPathOrSelf(p) {
22574
+ try {
22575
+ return realpathSync4(p);
22576
+ } catch {
22577
+ return p;
22578
+ }
22579
+ }
22580
+ function claudeSkillTargets() {
22581
+ const targets = [];
22582
+ const seen = /* @__PURE__ */ new Set();
22583
+ for (const home of claudeHomeDirs()) {
22584
+ const dir = join52(home, "skills");
22585
+ const key2 = realPathOrSelf(dir);
22586
+ if (seen.has(key2)) continue;
22587
+ seen.add(key2);
22588
+ const primary = targets.length === 0;
22589
+ targets.push({
22590
+ id: primary ? "claude" : `claude:${basename23(home)}`,
22591
+ label: primary ? "Claude Code" : `Claude Code (${basename23(home)})`,
22592
+ dir,
22593
+ // Judge each profile on ITS OWN transcripts, not the primary's: a home
22594
+ // with no projects/ is a directory someone made, not a Claude install.
22595
+ available: existsSync47(join52(home, "projects"))
22596
+ });
22597
+ }
22598
+ if (targets.length === 0) {
22599
+ targets.push({ id: "claude", label: "Claude Code", dir: claudeBackend.skillsDir(), available: claudeBackend.isAvailable() });
22600
+ }
22601
+ return targets;
22602
+ }
22603
+ function skillTargets() {
22604
+ return [
22605
+ // Per-tool dirs ONLY. We deliberately do NOT also write ~/.agents/skills:
22606
+ // some tools read BOTH ~/.agents/skills and their own dir, so installing to
22607
+ // both produces "skill conflict" duplicate warnings. Every tool we support
22608
+ // has its own dir, so per-tool coverage is complete.
22609
+ ...claudeSkillTargets(),
22610
+ { id: "agy", label: "Antigravity", dir: join52(agyBackend.homeDir(), "skills"), available: agyBackend.isAvailable() },
22611
+ { id: "codex", label: "Codex", dir: codexBackend.skillsDir(), available: codexBackend.isAvailable() },
22612
+ { id: "opencode", label: "OpenCode", dir: opencodeBackend.skillsDir(), available: opencodeBackend.isAvailable() },
22613
+ // Cursor DOES have its own dir (~/.cursor/skills). `skills-cursor` next to
22614
+ // it is Cursor's bundled set — read-only, never an install target.
22615
+ { id: "cursor", label: "Cursor", dir: join52(cursorHomeDir(), "skills"), available: cursorBackend.isAvailable() }
22616
+ ];
22617
+ }
22618
+ function isManaged(dir) {
22619
+ return existsSync47(join52(dir, MARKER));
22620
+ }
22621
+ function installSkills(opts = {}) {
22622
+ const onlyAvailable = opts.onlyAvailable ?? true;
22623
+ const src = skillsSourceDir();
22624
+ const names = bundledSkillNames();
22625
+ const v = version();
22626
+ const stamp = skillStamp();
22627
+ const perTarget = [];
22628
+ for (const t of skillTargets()) {
22629
+ const installed = [];
22630
+ const skippedUserOwned = [];
22631
+ if (onlyAvailable && !t.available) {
22632
+ perTarget.push({ ...t, installed, skippedUserOwned });
22633
+ continue;
22634
+ }
22635
+ for (const name of names) {
22636
+ const dest = join52(t.dir, name);
22637
+ if (existsSync47(dest) && !isManaged(dest)) {
22638
+ skippedUserOwned.push(name);
22639
+ continue;
22640
+ }
22641
+ mkdirSync15(t.dir, { recursive: true });
22642
+ rmSync8(dest, { recursive: true, force: true });
22643
+ cpSync(join52(src, name), dest, { recursive: true });
22644
+ writeFileSync13(join52(dest, MARKER), `${stamp}
22645
+ `);
22646
+ installed.push(name);
22647
+ }
22648
+ perTarget.push({ ...t, installed, skippedUserOwned });
22649
+ }
22650
+ return { version: v, perTarget };
22651
+ }
22652
+ function uninstallSkills() {
22653
+ const names = new Set(bundledSkillNames());
22654
+ const perTarget = [];
22655
+ for (const t of skillTargets()) {
22656
+ const removed = [];
22657
+ if (!existsSync47(t.dir)) {
22658
+ perTarget.push({ id: t.id, label: t.label, removed });
22659
+ continue;
22660
+ }
22661
+ for (const entry of readdirSync34(t.dir, { withFileTypes: true })) {
22662
+ if (!entry.isDirectory()) continue;
22663
+ const dir = join52(t.dir, entry.name);
22664
+ if (isManaged(dir) && (names.has(entry.name) || entry.name.startsWith("chat-recall"))) {
22665
+ rmSync8(dir, { recursive: true, force: true });
22666
+ removed.push(entry.name);
22667
+ }
22668
+ }
22669
+ perTarget.push({ id: t.id, label: t.label, removed });
22670
+ }
22671
+ return { perTarget };
22672
+ }
22673
+ function skillsNeedRefresh() {
22674
+ const cur = skillStamp();
22675
+ const names = bundledSkillNames();
22676
+ if (names.length === 0) return false;
22677
+ for (const t of skillTargets()) {
22678
+ if (!t.available) continue;
22679
+ for (const name of names) {
22680
+ const marker = join52(t.dir, name, MARKER);
22681
+ if (!existsSync47(marker)) return true;
22682
+ try {
22683
+ if (readFileSync42(marker, "utf-8").trim() !== cur) return true;
22684
+ } catch {
22685
+ return true;
22686
+ }
22687
+ }
22688
+ }
22689
+ return false;
22690
+ }
22691
+ function skillStatus() {
22692
+ const names = bundledSkillNames();
22693
+ const cur = skillStamp();
22694
+ return skillTargets().map((t) => {
22695
+ let installed = 0;
22696
+ let ver = null;
22697
+ for (const name of names) {
22698
+ const marker = join52(t.dir, name, MARKER);
22699
+ if (existsSync47(marker)) {
22700
+ installed++;
22701
+ try {
22702
+ ver = readFileSync42(marker, "utf-8").trim();
22703
+ } catch {
22704
+ }
22705
+ }
22706
+ }
22707
+ return { id: t.id, label: t.label, available: t.available, installed, version: installed === names.length ? ver : installed ? `${ver} (partial)` : null };
22708
+ }).filter((s) => s.available || s.installed > 0);
22709
+ }
22710
+ var MARKER;
22711
+ var init_install_skills = __esm({
22712
+ "src/install-skills.ts"() {
22713
+ "use strict";
22714
+ init_tool_paths();
22715
+ init_claude2();
22716
+ init_opencode();
22717
+ init_codex2();
22718
+ init_agy();
22719
+ init_cursor();
22720
+ init_tool_paths();
22721
+ MARKER = ".chat-recall-managed";
22722
+ }
22723
+ });
22724
+
22522
22725
  // src/mcp.ts
22523
22726
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22524
22727
  import { spawn as spawn3 } from "child_process";
@@ -22639,6 +22842,107 @@ function socketPathFromArgv(argv) {
22639
22842
  return v && !v.startsWith("--") ? v : void 0;
22640
22843
  }
22641
22844
 
22845
+ // src/relay-lifecycle.ts
22846
+ var PARENT_POLL_MS = 5e3;
22847
+ function pidAlive(pid) {
22848
+ try {
22849
+ process.kill(pid, 0);
22850
+ return true;
22851
+ } catch (err) {
22852
+ return err.code === "EPERM";
22853
+ }
22854
+ }
22855
+ var processProbe = {
22856
+ ppid: () => process.ppid,
22857
+ alive: pidAlive
22858
+ };
22859
+ function parentGone(originalPpid, probe) {
22860
+ if (probe.ppid() !== originalPpid) return true;
22861
+ return !probe.alive(originalPpid);
22862
+ }
22863
+ function watchParent(onGone, opts = {}) {
22864
+ const probe = opts.probe ?? processProbe;
22865
+ const original = probe.ppid();
22866
+ if (original <= 1) return () => {
22867
+ };
22868
+ const timer = setInterval(() => {
22869
+ if (!parentGone(original, probe)) return;
22870
+ clearInterval(timer);
22871
+ onGone();
22872
+ }, opts.intervalMs ?? PARENT_POLL_MS);
22873
+ timer.unref?.();
22874
+ return () => clearInterval(timer);
22875
+ }
22876
+ function onInputFinished(input, onEnd) {
22877
+ let fired = false;
22878
+ const fire = () => {
22879
+ if (fired) return;
22880
+ fired = true;
22881
+ onEnd();
22882
+ };
22883
+ input.once("end", fire);
22884
+ input.once("close", fire);
22885
+ input.once("error", fire);
22886
+ }
22887
+
22888
+ // src/sync-supervisor.ts
22889
+ function createSyncSupervisor(opts) {
22890
+ let owner = null;
22891
+ let inFlight = false;
22892
+ let tookOver = false;
22893
+ const log6 = opts.log ?? (() => {
22894
+ });
22895
+ const serviceRunning = () => {
22896
+ try {
22897
+ return opts.serviceRunning();
22898
+ } catch {
22899
+ return false;
22900
+ }
22901
+ };
22902
+ const check = async () => {
22903
+ if (serviceRunning()) {
22904
+ if (owner !== "service") {
22905
+ log6(owner === "mcp" ? "watch service is running again \u2014 it owns sync; MCP background sync stands by" : "watch service active \u2014 MCP will not run background sync (the service syncs)");
22906
+ }
22907
+ owner = "service";
22908
+ return;
22909
+ }
22910
+ const scope = owner === "mcp" ? "changed" : "full";
22911
+ if (owner !== "mcp") {
22912
+ log6(owner === "service" ? "watch service is no longer running \u2014 MCP takes over background sync" : "no watch service running \u2014 MCP runs background sync");
22913
+ if (!tookOver) {
22914
+ tookOver = true;
22915
+ opts.onFirstTakeover?.();
22916
+ }
22917
+ }
22918
+ owner = "mcp";
22919
+ if (inFlight) return;
22920
+ inFlight = true;
22921
+ try {
22922
+ await opts.tick(scope);
22923
+ } catch {
22924
+ } finally {
22925
+ inFlight = false;
22926
+ }
22927
+ };
22928
+ const first = setTimeout(() => {
22929
+ void check();
22930
+ }, opts.firstCheckMs);
22931
+ first.unref?.();
22932
+ const every = setInterval(() => {
22933
+ void check();
22934
+ }, opts.intervalMs);
22935
+ every.unref?.();
22936
+ return {
22937
+ check,
22938
+ owner: () => owner,
22939
+ stop: () => {
22940
+ clearTimeout(first);
22941
+ clearInterval(every);
22942
+ }
22943
+ };
22944
+ }
22945
+
22642
22946
  // src/mcp.ts
22643
22947
  import { fileURLToPath as fileURLToPath5 } from "node:url";
22644
22948
 
@@ -23553,7 +23857,7 @@ function sanitizeQuery(query) {
23553
23857
 
23554
23858
  // ../engine/dist/core/write-ahead-log.js
23555
23859
  init_paths();
23556
- import { existsSync as existsSync17, mkdirSync as mkdirSync5, appendFileSync, openSync as openSync7, closeSync as closeSync7, chmodSync as chmodSync3 } from "fs";
23860
+ import { existsSync as existsSync17, mkdirSync as mkdirSync5, appendFileSync, openSync as openSync8, closeSync as closeSync8, chmodSync as chmodSync3 } from "fs";
23557
23861
  import { join as join20 } from "path";
23558
23862
  var REDACT_KEYS = /* @__PURE__ */ new Set([
23559
23863
  "content",
@@ -23579,8 +23883,8 @@ var WriteAheadLog = class {
23579
23883
  }
23580
23884
  this.walPath = join20(dir, "write_log.jsonl");
23581
23885
  if (!existsSync17(this.walPath)) {
23582
- const fd = openSync7(this.walPath, "w", 384);
23583
- closeSync7(fd);
23886
+ const fd = openSync8(this.walPath, "w", 384);
23887
+ closeSync8(fd);
23584
23888
  }
23585
23889
  }
23586
23890
  /**
@@ -28371,12 +28675,6 @@ function startBackgroundSync() {
28371
28675
  console.error("[mcp] background sync tick failed:", err instanceof Error ? err.message : err);
28372
28676
  }
28373
28677
  };
28374
- setInterval(() => {
28375
- void tick("changed");
28376
- }, SYNC_TICK_MS).unref();
28377
- setTimeout(() => {
28378
- void tick("full");
28379
- }, 15e3).unref();
28380
28678
  const updateTick = async () => {
28381
28679
  try {
28382
28680
  const { refreshUpdateCheck: refreshUpdateCheck2, updateNotice: updateNotice3 } = await Promise.resolve().then(() => (init_update_notice(), update_notice_exports));
@@ -28386,12 +28684,27 @@ function startBackgroundSync() {
28386
28684
  } catch {
28387
28685
  }
28388
28686
  };
28389
- setTimeout(() => {
28390
- void updateTick();
28391
- }, 2e4).unref();
28392
- setInterval(() => {
28393
- void updateTick();
28394
- }, 6 * 60 * 60 * 1e3).unref();
28687
+ const startUpdateProbe = () => {
28688
+ setTimeout(() => {
28689
+ void updateTick();
28690
+ }, 5e3).unref();
28691
+ setInterval(() => {
28692
+ void updateTick();
28693
+ }, 6 * 60 * 60 * 1e3).unref();
28694
+ };
28695
+ let isServiceRunning2 = null;
28696
+ createSyncSupervisor({
28697
+ serviceRunning: () => isServiceRunning2?.() ?? false,
28698
+ tick,
28699
+ intervalMs: SYNC_TICK_MS,
28700
+ firstCheckMs: 15e3,
28701
+ onFirstTakeover: startUpdateProbe,
28702
+ log: (msg) => console.error(`[mcp] ${msg}`)
28703
+ });
28704
+ void Promise.resolve().then(() => (init_service_installer(), service_installer_exports)).then((m) => {
28705
+ isServiceRunning2 = m.isServiceRunning;
28706
+ }).catch(() => {
28707
+ });
28395
28708
  }
28396
28709
  function installCrashGuards() {
28397
28710
  process.on("unhandledRejection", (reason) => {
@@ -28404,7 +28717,7 @@ function installCrashGuards() {
28404
28717
  reportClientEvent2("mcp_crash", { message: err.stack || err.message });
28405
28718
  });
28406
28719
  }
28407
- var RELAY_VERSION = true ? "0.7.0" : "0.0.0";
28720
+ var RELAY_VERSION = true ? "0.7.2" : "0.0.0";
28408
28721
  function daemonIdleSecs() {
28409
28722
  const raw = process.env.CHAT_RECALL_DAEMON_IDLE_SECS;
28410
28723
  if (raw === void 0) return 900;
@@ -28509,6 +28822,9 @@ async function main() {
28509
28822
  } else {
28510
28823
  const transport2 = new StdioServerTransport();
28511
28824
  await createMcpServer().connect(transport2);
28825
+ const leave = () => process.exit(0);
28826
+ onInputFinished(process.stdin, leave);
28827
+ watchParent(leave);
28512
28828
  }
28513
28829
  void (async () => {
28514
28830
  try {
@@ -28529,16 +28845,6 @@ async function main() {
28529
28845
  }
28530
28846
  } catch {
28531
28847
  }
28532
- let daemonRunning = false;
28533
- try {
28534
- const { isServiceRunning: isServiceRunning2 } = await Promise.resolve().then(() => (init_service_installer(), service_installer_exports));
28535
- daemonRunning = isServiceRunning2();
28536
- } catch {
28537
- }
28538
- if (daemonRunning) {
28539
- console.error("[mcp] watch daemon active \u2014 MCP will not run background sync (keeps the tool server lightweight; the daemon syncs).");
28540
- } else {
28541
- startBackgroundSync();
28542
- }
28848
+ startBackgroundSync();
28543
28849
  }
28544
28850
  main().catch(console.error);