chat-recall 0.7.0 → 0.7.1

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
@@ -7156,7 +7156,7 @@ var init_raw = __esm({
7156
7156
  // ../engine/dist/transcript/shadow.js
7157
7157
  import { gzipSync as gzipSync2, gunzipSync as gunzipSync2 } from "zlib";
7158
7158
  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";
7159
+ 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
7160
  import { dirname as dirname9, join as join11 } from "path";
7161
7161
  function shadowRoot() {
7162
7162
  return join11(getDataDir(), "shadow");
@@ -7332,6 +7332,29 @@ function updateShadow(sessionId, exp, prebuilt) {
7332
7332
  path: path2
7333
7333
  };
7334
7334
  }
7335
+ function shadowUncompressedBytes(tool, rawId) {
7336
+ const path2 = shadowFileFor(tool, rawId);
7337
+ let fd = null;
7338
+ try {
7339
+ const size = statSync7(path2).size;
7340
+ if (size < 18)
7341
+ return 0;
7342
+ fd = openSync2(path2, "r");
7343
+ const tail = Buffer.alloc(4);
7344
+ if (readSync2(fd, tail, 0, 4, size - 4) !== 4)
7345
+ return 0;
7346
+ return tail.readUInt32LE(0);
7347
+ } catch {
7348
+ return 0;
7349
+ } finally {
7350
+ if (fd !== null) {
7351
+ try {
7352
+ closeSync2(fd);
7353
+ } catch {
7354
+ }
7355
+ }
7356
+ }
7357
+ }
7335
7358
  var WHOLE_FILE_TOOLS, SINGLETON_META;
7336
7359
  var init_shadow = __esm({
7337
7360
  "../engine/dist/transcript/shadow.js"() {
@@ -7344,7 +7367,7 @@ var init_shadow = __esm({
7344
7367
  });
7345
7368
 
7346
7369
  // ../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";
7370
+ 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
7371
  import { join as join12, basename as basename7 } from "path";
7349
7372
  function detectTool(sessionId) {
7350
7373
  return getBackendForId(sessionId)?.id ?? "claude";
@@ -7371,6 +7394,15 @@ function sessionFileIndex() {
7371
7394
  return indexCache;
7372
7395
  const byId = /* @__PURE__ */ new Map();
7373
7396
  const seenRoots = /* @__PURE__ */ new Set();
7397
+ const projectPaths = /* @__PURE__ */ new Map();
7398
+ const projectPathOf = (dirName) => {
7399
+ let p = projectPaths.get(dirName);
7400
+ if (p === void 0) {
7401
+ p = resolveProjectDirName(dirName);
7402
+ projectPaths.set(dirName, p);
7403
+ }
7404
+ return p;
7405
+ };
7374
7406
  for (const root of claudeProjectDirs()) {
7375
7407
  if (seenRoots.has(root) || !existsSync8(root))
7376
7408
  continue;
@@ -7398,7 +7430,7 @@ function sessionFileIndex() {
7398
7430
  const located = {
7399
7431
  path: join12(dir, name),
7400
7432
  projectDir: entry.name,
7401
- projectPath: resolveProjectDirName(entry.name)
7433
+ projectPath: projectPathOf(entry.name)
7402
7434
  };
7403
7435
  const prior = byId.get(id);
7404
7436
  if (prior)
@@ -8080,7 +8112,7 @@ var init_flat_string = __esm({
8080
8112
  });
8081
8113
 
8082
8114
  // ../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";
8115
+ import { createReadStream as createReadStream2, existsSync as existsSync10, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
8084
8116
  import { StringDecoder } from "string_decoder";
8085
8117
  function stripBanners2(s) {
8086
8118
  let out = s;
@@ -8134,7 +8166,7 @@ function extractFirstUserPromptSync(filePath, options = {}) {
8134
8166
  const maxBytes = options.maxBytes ?? 4 * 1024 * 1024;
8135
8167
  let fd;
8136
8168
  try {
8137
- fd = openSync3(filePath, "r");
8169
+ fd = openSync4(filePath, "r");
8138
8170
  } catch {
8139
8171
  return "";
8140
8172
  }
@@ -8146,7 +8178,7 @@ function extractFirstUserPromptSync(filePath, options = {}) {
8146
8178
  let pos = 0;
8147
8179
  let lineNo = 0;
8148
8180
  for (; ; ) {
8149
- const n = readSync3(fd, buf, 0, CHUNK, pos);
8181
+ const n = readSync4(fd, buf, 0, CHUNK, pos);
8150
8182
  if (n <= 0)
8151
8183
  break;
8152
8184
  pos += n;
@@ -8172,7 +8204,7 @@ function extractFirstUserPromptSync(filePath, options = {}) {
8172
8204
  return "";
8173
8205
  } finally {
8174
8206
  try {
8175
- closeSync3(fd);
8207
+ closeSync4(fd);
8176
8208
  } catch {
8177
8209
  }
8178
8210
  }
@@ -8191,8 +8223,8 @@ var init_first_prompt = __esm({
8191
8223
  });
8192
8224
 
8193
8225
  // ../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) {
8226
+ import { openSync as openSync5, readSync as readSync5, closeSync as closeSync5, statSync as statSync9 } from "node:fs";
8227
+ function readTailFromOffset(path2, offset, maxBytes = TAIL_READ_MAX_BYTES) {
8196
8228
  let size = 0;
8197
8229
  try {
8198
8230
  size = statSync9(path2).size;
@@ -8201,14 +8233,14 @@ function readTailFromOffset(path2, offset) {
8201
8233
  }
8202
8234
  if (size <= offset)
8203
8235
  return { text: "", newOffset: offset };
8204
- const len = Math.min(size - offset, 16 * 1024 * 1024);
8236
+ const len = Math.min(size - offset, Math.max(1, maxBytes));
8205
8237
  const buf = Buffer.allocUnsafe(len);
8206
8238
  let fd = null;
8207
8239
  try {
8208
- fd = openSync4(path2, "r");
8240
+ fd = openSync5(path2, "r");
8209
8241
  let got = 0;
8210
8242
  while (got < len) {
8211
- const n = readSync4(fd, buf, got, len - got, offset + got);
8243
+ const n = readSync5(fd, buf, got, len - got, offset + got);
8212
8244
  if (n <= 0)
8213
8245
  break;
8214
8246
  got += n;
@@ -8217,25 +8249,47 @@ function readTailFromOffset(path2, offset) {
8217
8249
  return { text: "", newOffset: offset };
8218
8250
  const slice = got < len ? buf.subarray(0, got) : buf;
8219
8251
  const lastNl = slice.lastIndexOf(10);
8220
- if (lastNl < 0) {
8221
- return { text: "", newOffset: offset };
8252
+ if (lastNl >= 0) {
8253
+ const text = slice.subarray(0, lastNl + 1).toString("utf-8");
8254
+ return { text, newOffset: offset + lastNl + 1 };
8222
8255
  }
8223
- const text = slice.subarray(0, lastNl + 1).toString("utf-8");
8224
- return { text, newOffset: offset + lastNl + 1 };
8256
+ if (offset + got >= size)
8257
+ return { text: "", newOffset: offset };
8258
+ const end = findNewline(fd, offset + got, size);
8259
+ if (end < 0)
8260
+ return { text: "", newOffset: offset };
8261
+ return { text: "", newOffset: end + 1, skippedBytes: end + 1 - offset };
8225
8262
  } catch {
8226
8263
  return { text: "", newOffset: offset };
8227
8264
  } finally {
8228
8265
  if (fd !== null) {
8229
8266
  try {
8230
- closeSync4(fd);
8267
+ closeSync5(fd);
8231
8268
  } catch {
8232
8269
  }
8233
8270
  }
8234
8271
  }
8235
8272
  }
8273
+ function findNewline(fd, from, size) {
8274
+ const block = Buffer.allocUnsafe(SKIP_SCAN_BYTES);
8275
+ let pos = from;
8276
+ while (pos < size) {
8277
+ const n = readSync5(fd, block, 0, Math.min(SKIP_SCAN_BYTES, size - pos), pos);
8278
+ if (n <= 0)
8279
+ return -1;
8280
+ const i = block.subarray(0, n).indexOf(10);
8281
+ if (i >= 0)
8282
+ return pos + i;
8283
+ pos += n;
8284
+ }
8285
+ return -1;
8286
+ }
8287
+ var TAIL_READ_MAX_BYTES, SKIP_SCAN_BYTES;
8236
8288
  var init_tail_read = __esm({
8237
8289
  "../engine/dist/core/backends/tail-read.js"() {
8238
8290
  "use strict";
8291
+ TAIL_READ_MAX_BYTES = 16 * 1024 * 1024;
8292
+ SKIP_SCAN_BYTES = 1024 * 1024;
8239
8293
  }
8240
8294
  });
8241
8295
 
@@ -8512,7 +8566,7 @@ var init_generic_engine = __esm({
8512
8566
  });
8513
8567
 
8514
8568
  // ../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";
8569
+ 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
8570
  import { join as join13, basename as basename10 } from "path";
8517
8571
  function stripReminders(text) {
8518
8572
  return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
@@ -8609,13 +8663,13 @@ var init_claude2 = __esm({
8609
8663
  return null;
8610
8664
  let head;
8611
8665
  try {
8612
- const fd = openSync5(located.path, "r");
8666
+ const fd = openSync6(located.path, "r");
8613
8667
  try {
8614
8668
  const buf = Buffer.alloc(65536);
8615
- const n = readSync5(fd, buf, 0, buf.length, 0);
8669
+ const n = readSync6(fd, buf, 0, buf.length, 0);
8616
8670
  head = buf.toString("utf-8", 0, n);
8617
8671
  } finally {
8618
- closeSync5(fd);
8672
+ closeSync6(fd);
8619
8673
  }
8620
8674
  } catch {
8621
8675
  return null;
@@ -9087,14 +9141,14 @@ var init_claude2 = __esm({
9087
9141
  spansMultipleSources(prefixedId) {
9088
9142
  return findSessionFiles(this.toRawId(prefixedId)).length > 1;
9089
9143
  }
9090
- async readFromOffset(prefixedId, offset) {
9144
+ async readFromOffset(prefixedId, offset, maxBytes) {
9091
9145
  const rawId = this.toRawId(prefixedId);
9092
9146
  const copies = findSessionFiles(rawId);
9093
9147
  if (copies.length === 0)
9094
9148
  return { text: "", newOffset: offset };
9095
9149
  if (copies.length > 1)
9096
9150
  return { text: "", newOffset: offset };
9097
- return readTailFromOffset(copies[0].path, offset);
9151
+ return readTailFromOffset(copies[0].path, offset, maxBytes);
9098
9152
  }
9099
9153
  };
9100
9154
  claudeBackend = new ClaudeBackend();
@@ -9678,7 +9732,7 @@ var init_opencode = __esm({
9678
9732
  });
9679
9733
 
9680
9734
  // ../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";
9735
+ import { readdirSync as readdirSync10, readFileSync as readFileSync8, statSync as statSync11, openSync as openSync7, readSync as readSync7, closeSync as closeSync7 } from "fs";
9682
9736
  import { basename as basename11, dirname as dirname12, join as join15 } from "path";
9683
9737
  function parseShellWrites(cmd) {
9684
9738
  const out = [];
@@ -10352,10 +10406,10 @@ var init_codex2 = __esm({
10352
10406
  if (p === loc.path)
10353
10407
  continue;
10354
10408
  try {
10355
- const fd = openSync6(p, "r");
10409
+ const fd = openSync7(p, "r");
10356
10410
  const buf = Buffer.alloc(4096);
10357
- const n = readSync6(fd, buf, 0, 4096, 0);
10358
- closeSync6(fd);
10411
+ const n = readSync7(fd, buf, 0, 4096, 0);
10412
+ closeSync7(fd);
10359
10413
  const head = buf.toString("utf-8", 0, n);
10360
10414
  if (head.includes(parentId) && head.includes("parent_thread_id"))
10361
10415
  push(p, `subagents/${f}`);
@@ -10380,11 +10434,11 @@ var init_codex2 = __esm({
10380
10434
  return 0;
10381
10435
  }
10382
10436
  }
10383
- async readFromOffset(prefixedId, offset) {
10437
+ async readFromOffset(prefixedId, offset, maxBytes) {
10384
10438
  const loc = this.findSession(prefixedId);
10385
10439
  if (!loc)
10386
10440
  return { text: "", newOffset: offset };
10387
- return readTailFromOffset(loc.path, offset);
10441
+ return readTailFromOffset(loc.path, offset, maxBytes);
10388
10442
  }
10389
10443
  };
10390
10444
  codexBackend = new CodexBackend();
@@ -10945,11 +10999,11 @@ var init_agy = __esm({
10945
10999
  return 0;
10946
11000
  }
10947
11001
  }
10948
- async readFromOffset(prefixedId, offset) {
11002
+ async readFromOffset(prefixedId, offset, maxBytes) {
10949
11003
  const loc = this.findSession(prefixedId);
10950
11004
  if (!loc)
10951
11005
  return { text: "", newOffset: offset };
10952
- return readTailFromOffset(loc.path, offset);
11006
+ return readTailFromOffset(loc.path, offset, maxBytes);
10953
11007
  }
10954
11008
  };
10955
11009
  agyBackend = new AgyBackend();
@@ -14715,10 +14769,10 @@ var init_types = __esm({
14715
14769
 
14716
14770
  // ../engine/dist/transcript/opencode.js
14717
14771
  async function parseOpenCodeSubagents(parentSessionId) {
14718
- const { existsSync: existsSync47 } = await import("fs");
14772
+ const { existsSync: existsSync48 } = await import("fs");
14719
14773
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
14720
14774
  const dbPath = opencodeBackend2.dbPath();
14721
- if (!existsSync47(dbPath))
14775
+ if (!existsSync48(dbPath))
14722
14776
  return [];
14723
14777
  const realParent = parentSessionId.replace("opencode_", "");
14724
14778
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -14759,10 +14813,10 @@ async function parseOpenCodeSubagents(parentSessionId) {
14759
14813
  }
14760
14814
  }
14761
14815
  async function parseOpenCodeTranscript(sessionId) {
14762
- const { existsSync: existsSync47 } = await import("fs");
14816
+ const { existsSync: existsSync48 } = await import("fs");
14763
14817
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
14764
14818
  const dbPath = opencodeBackend2.dbPath();
14765
- if (!existsSync47(dbPath))
14819
+ if (!existsSync48(dbPath))
14766
14820
  return [];
14767
14821
  const realId = sessionId.replace("opencode_", "");
14768
14822
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -19248,7 +19302,7 @@ var init_sync_fields = __esm({
19248
19302
  });
19249
19303
 
19250
19304
  // ../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";
19305
+ 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
19306
  import { dirname as dirname18, join as join47 } from "path";
19253
19307
  function acquireIndexLock(opts) {
19254
19308
  const lockPath = lockFilePath();
@@ -19260,7 +19314,7 @@ function acquireIndexLock(opts) {
19260
19314
  }
19261
19315
  let fd;
19262
19316
  try {
19263
- fd = openSync8(lockPath, "wx", 384);
19317
+ fd = openSync9(lockPath, "wx", 384);
19264
19318
  } catch (err) {
19265
19319
  if (err.code === "EEXIST")
19266
19320
  return null;
@@ -19270,7 +19324,7 @@ function acquireIndexLock(opts) {
19270
19324
  try {
19271
19325
  writeFileSync8(fd, JSON.stringify(holder));
19272
19326
  } finally {
19273
- closeSync8(fd);
19327
+ closeSync9(fd);
19274
19328
  }
19275
19329
  let released = false;
19276
19330
  return {
@@ -19364,7 +19418,7 @@ var init_http = __esm({
19364
19418
  CLI_VERSION = JSON.parse(readFileSync36(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
19365
19419
  } catch {
19366
19420
  try {
19367
- if (true) CLI_VERSION = "0.7.0";
19421
+ if (true) CLI_VERSION = "0.7.1";
19368
19422
  } catch {
19369
19423
  }
19370
19424
  }
@@ -19863,7 +19917,7 @@ var init_telemetry = __esm({
19863
19917
  init_sync_client();
19864
19918
  cliVersion2 = () => {
19865
19919
  try {
19866
- return true ? "0.7.0" : "dev";
19920
+ return true ? "0.7.1" : "dev";
19867
19921
  } catch {
19868
19922
  return "dev";
19869
19923
  }
@@ -20023,16 +20077,17 @@ __export(auto_update_exports, {
20023
20077
  noteUpdateFailure: () => noteUpdateFailure,
20024
20078
  planAutoUpdate: () => planAutoUpdate,
20025
20079
  readUpdateState: () => readUpdateState,
20080
+ resolveNpm: () => resolveNpm,
20026
20081
  runAutoUpdate: () => runAutoUpdate,
20027
20082
  runningPrefix: () => runningPrefix,
20028
20083
  sweepStaleStaging: () => sweepStaleStaging,
20029
20084
  tarballUrl: () => tarballUrl
20030
20085
  });
20031
- import { execSync as execSync4 } from "node:child_process";
20032
- import { readFileSync as readFileSync37 } from "node:fs";
20086
+ import { execSync as execSync4, execFileSync as execFileSync3 } from "node:child_process";
20087
+ import { readFileSync as readFileSync37, existsSync as existsSync45 } from "node:fs";
20033
20088
  import { fileURLToPath as fileURLToPath2 } from "node:url";
20034
20089
  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";
20090
+ import { join as join48, dirname as dirname19, sep as sep3, posix as pathPosix, win32 as pathWin32 } from "node:path";
20036
20091
  import { tmpdir as tmpdir3, homedir as homedir17 } from "node:os";
20037
20092
  import { createHash as createHash10 } from "node:crypto";
20038
20093
  function compareVersions(a, b) {
@@ -20147,25 +20202,46 @@ function runningPrefix(moduleUrl = import.meta.url) {
20147
20202
  const beforeNodeModules = here.slice(0, at);
20148
20203
  return beforeNodeModules.endsWith(`${sep3}lib`) ? beforeNodeModules.slice(0, -4) : beforeNodeModules;
20149
20204
  }
20150
- function npmInstall(cmd) {
20205
+ function npmInstall(npm, args) {
20151
20206
  try {
20152
- execSync4(cmd, { stdio: ["ignore", "ignore", "pipe"] });
20207
+ const opts = { stdio: ["ignore", "ignore", "pipe"], env: npm.env };
20208
+ if (npm.shell) execSync4([npm.file, ...npm.args, ...args].map((a) => `"${a}"`).join(" "), opts);
20209
+ else execFileSync3(npm.file, [...npm.args, ...args], opts);
20153
20210
  } catch (e) {
20154
20211
  const err = e;
20155
20212
  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
20213
  throw new Error(detail ? `${err.message ?? "install failed"} \u2014 ${detail}` : err.message ?? "install failed");
20157
20214
  }
20158
20215
  }
20216
+ function resolveNpm(loc) {
20217
+ const win = loc.platform === "win32";
20218
+ const p = win ? pathWin32 : pathPosix;
20219
+ const nodeDir = p.dirname(loc.execPath);
20220
+ 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");
20221
+ const candidates2 = [
20222
+ cliUnder(win ? nodeDir : p.join(nodeDir, "..")),
20223
+ ...loc.prefix ? [cliUnder(loc.prefix)] : []
20224
+ ];
20225
+ const pathKey = Object.keys(loc.env).find((k) => k.toUpperCase() === "PATH") ?? "PATH";
20226
+ const binDirs = [nodeDir, ...loc.prefix ? [win ? loc.prefix : p.join(loc.prefix, "bin")] : []];
20227
+ const delimiter3 = win ? ";" : ":";
20228
+ const current = loc.env[pathKey] ?? "";
20229
+ const env = { ...loc.env, [pathKey]: [...binDirs, ...current ? [current] : []].join(delimiter3) };
20230
+ const cli = candidates2.find((c) => loc.exists(c));
20231
+ if (cli) return { file: loc.execPath, args: [cli], env, shell: false };
20232
+ return { file: "npm", args: [], env, shell: true };
20233
+ }
20159
20234
  function realInstall(tgz) {
20160
20235
  const prefix = runningPrefix();
20236
+ const npm = resolveNpm({ execPath: process.execPath, prefix, platform: process.platform, env: process.env, exists: existsSync45 });
20161
20237
  if (prefix) {
20162
- npmInstall(`npm install -g --prefix "${prefix}" "${tgz}"`);
20238
+ npmInstall(npm, ["install", "-g", "--prefix", prefix, tgz]);
20163
20239
  return;
20164
20240
  }
20165
20241
  try {
20166
- npmInstall(`npm install -g "${tgz}"`);
20242
+ npmInstall(npm, ["install", "-g", tgz]);
20167
20243
  } catch {
20168
- npmInstall(`npm install -g --prefix "${process.env.HOME}/.local" "${tgz}"`);
20244
+ npmInstall(npm, ["install", "-g", "--prefix", join48(homedir17(), ".local"), tgz]);
20169
20245
  }
20170
20246
  }
20171
20247
  function realRestart(platform2) {
@@ -20266,7 +20342,9 @@ var init_auto_update = __esm({
20266
20342
  var sync_client_exports = {};
20267
20343
  __export(sync_client_exports, {
20268
20344
  BATCH_SCAN_MAX_BYTES: () => BATCH_SCAN_MAX_BYTES,
20345
+ DERIVED_MAX_BYTES: () => DERIVED_MAX_BYTES,
20269
20346
  FULL_BUILD_MAX_BYTES: () => FULL_BUILD_MAX_BYTES,
20347
+ SYNC_CHUNK_BYTES: () => SYNC_CHUNK_BYTES,
20270
20348
  _acceptedUploads: () => _acceptedUploads,
20271
20349
  _fetchTenantSyncConfigForTests: () => _fetchTenantSyncConfigForTests,
20272
20350
  _resetCapsCacheForTests: () => _resetCapsCacheForTests,
@@ -20285,8 +20363,10 @@ __export(sync_client_exports, {
20285
20363
  reconcileFields: () => reconcileFields,
20286
20364
  removeCredentials: () => removeCredentials,
20287
20365
  saveCredentials: () => saveCredentials,
20366
+ scanTranscriptFindingsChunked: () => scanTranscriptFindingsChunked,
20288
20367
  serverAcceptsGzipBody: () => serverAcceptsGzipBody,
20289
20368
  serverIngestConcurrency: () => serverIngestConcurrency,
20369
+ sessionBuildBytes: () => sessionBuildBytes,
20290
20370
  sessionFileBytes: () => sessionFileBytes,
20291
20371
  setServerIngestConcurrency: () => setServerIngestConcurrency,
20292
20372
  syncIncremental: () => syncIncremental,
@@ -21058,16 +21138,53 @@ async function syncToTargetInScope(cred, opts = {}) {
21058
21138
  clearTimeout(timer);
21059
21139
  }
21060
21140
  };
21141
+ const postChunkedHead = async (conv, fileSize) => {
21142
+ const headers = { "content-type": "application/json" };
21143
+ if (cred.token) headers.authorization = `Bearer ${cred.token}`;
21144
+ const ac = new AbortController();
21145
+ const timer = setTimeout(() => ac.abort(new Error(`upload timed out after ${UPLOAD_TIMEOUT_MS}ms`)), UPLOAD_TIMEOUT_MS);
21146
+ try {
21147
+ const res = await fetch(`${base}/api/sync`, {
21148
+ method: "POST",
21149
+ headers,
21150
+ body: JSON.stringify({ conversations: [conv] }),
21151
+ signal: ac.signal
21152
+ });
21153
+ if (!res.ok) throw new Error(`chunked head sync failed: HTTP ${res.status} ${await res.text().catch(() => "")}`);
21154
+ const body = await res.json().catch(() => ({}));
21155
+ const id = conv.session_id;
21156
+ const guarded = (body.shrink_guarded ?? []).find((g) => g.session_id === id);
21157
+ const mtime = conv.mtime;
21158
+ try {
21159
+ if (!guarded) {
21160
+ markSynced(base, [{ id, mtime, offset: conv.from_offset, size: conv.from_offset, acked: true }]);
21161
+ } else if (typeof guarded.o === "number" && guarded.o > 0 && guarded.o <= fileSize) {
21162
+ markSynced(base, [{ id, mtime, offset: guarded.o, size: guarded.o, acked: true }]);
21163
+ } else {
21164
+ markSynced(base, [{ id, mtime }]);
21165
+ }
21166
+ } catch {
21167
+ }
21168
+ appendResults.uploaded++;
21169
+ } finally {
21170
+ clearTimeout(timer);
21171
+ }
21172
+ };
21061
21173
  let appendBatch = [];
21174
+ let appendBatchBytes = 0;
21062
21175
  const APPEND_BATCH_SIZE = 50;
21176
+ const APPEND_BATCH_MAX_BYTES = SYNC_CHUNK_BYTES;
21063
21177
  const appendFlush = async () => {
21064
21178
  if (appendBatch.length === 0) return;
21065
21179
  const batch = appendBatch;
21066
21180
  appendBatch = [];
21181
+ appendBatchBytes = 0;
21067
21182
  await postAppend(batch);
21068
21183
  };
21069
- const appendAdd = async (conv) => {
21184
+ const appendAdd = async (conv, bytes = 0) => {
21185
+ if (appendBatch.length > 0 && appendBatchBytes + bytes > APPEND_BATCH_MAX_BYTES) await appendFlush();
21070
21186
  appendBatch.push(conv);
21187
+ appendBatchBytes += bytes;
21071
21188
  if (appendBatch.length >= APPEND_BATCH_SIZE) await appendFlush();
21072
21189
  };
21073
21190
  const itemsBatch = makeBatcher("items", 100);
@@ -21146,7 +21263,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21146
21263
  let externalFindings = null;
21147
21264
  if (scanSecrets) {
21148
21265
  externalFindings = /* @__PURE__ */ new Map();
21149
- const toScan = slice.filter((ref) => modeOf(ref) === "full" && sessionFileBytes(ref) <= FULL_BUILD_MAX_BYTES);
21266
+ const toScan = slice.filter((ref) => modeOf(ref) === "full" && sessionBuildBytes(ref) <= FULL_BUILD_MAX_BYTES);
21150
21267
  trace?.(`batch secret scan start: ${toScan.length} session(s), \u2264${(BATCH_SCAN_MAX_BYTES / 1048576).toFixed(0)}MB per slice`);
21151
21268
  const batch = batchScanExternal(
21152
21269
  toScan.map((ref) => ({
@@ -21190,11 +21307,17 @@ async function syncToTargetInScope(cred, opts = {}) {
21190
21307
  const ack = ledger.get(ref.prefixedId);
21191
21308
  try {
21192
21309
  const tail = await buildConversationTail(ref, ack.o ?? 0, { mapPath, tenantRules });
21193
- if (tail && tail.findings.length > 0) {
21310
+ const chunked = sessionBuildBytes(ref) > FULL_BUILD_MAX_BYTES;
21311
+ if (tail && tail.findings.length > 0 && !chunked) {
21194
21312
  promotedForFindings = true;
21195
21313
  } else if (tail) {
21196
21314
  redactions += tail.redactions;
21197
- await appendAdd(tail.conv);
21315
+ await appendAdd(tail.conv, tail.bytes);
21316
+ if (tail.findings.length > 0) {
21317
+ await appendFlush();
21318
+ const all = await scanTranscriptFindingsChunked(ref, tenantRules);
21319
+ if (all.length > 0) await findingsBatch.add(all);
21320
+ }
21198
21321
  walked++;
21199
21322
  if (walked % 250 === 0) console.error(`[sync] ${walked}/${slice.length} sessions\u2026`);
21200
21323
  continue;
@@ -21242,12 +21365,13 @@ async function syncToTargetInScope(cred, opts = {}) {
21242
21365
  }
21243
21366
  redactions += built.redactions;
21244
21367
  if (built.srcHash) shippedHash.set(ref.prefixedId, built.srcHash);
21245
- await convBatch.add(built.conv);
21368
+ if (built.conv.chunked) await postChunkedHead(built.conv, sessionFileBytes(ref));
21369
+ else await convBatch.add(built.conv);
21246
21370
  if (built.findings.length > 0) await findingsBatch.add(built.findings);
21247
21371
  if (upload.sessionMeta) {
21248
21372
  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
21373
  }
21250
- if (upload.sessionMeta && sessionFileBytes(ref) <= FULL_BUILD_MAX_BYTES) {
21374
+ if (upload.sessionMeta && sessionFileBytes(ref) <= DERIVED_MAX_BYTES) {
21251
21375
  try {
21252
21376
  const count = { redactions: 0 };
21253
21377
  const offloaded = await scanPool().runDerived({
@@ -21427,35 +21551,70 @@ async function syncToTargetInScope(cred, opts = {}) {
21427
21551
  scanMs: Math.round(scanMs)
21428
21552
  };
21429
21553
  }
21554
+ async function scanTranscriptFindingsChunked(ref, tenantRules) {
21555
+ const backend = getBackendForId(ref.prefixedId);
21556
+ const findings = [];
21557
+ if (!backend?.readFromOffset) return findings;
21558
+ let offset = 0;
21559
+ let lineBase = 0;
21560
+ for (; ; ) {
21561
+ const chunk = await backend.readFromOffset(ref.prefixedId, offset, SYNC_CHUNK_BYTES);
21562
+ if (chunk.newOffset <= offset) break;
21563
+ offset = chunk.newOffset;
21564
+ if (chunk.skippedBytes) {
21565
+ lineBase += 1;
21566
+ continue;
21567
+ }
21568
+ try {
21569
+ for (const f of scanTextForFindings(chunk.text)) {
21570
+ findings.push({ session_id: ref.prefixedId, detector: "builtin", rule: f.rule, line: lineBase + f.line, preview: f.preview });
21571
+ }
21572
+ if (tenantRules && tenantRules.length > 0) {
21573
+ for (const f of scanTenantRules(chunk.text, tenantRules)) {
21574
+ findings.push({ session_id: ref.prefixedId, detector: f.detector, rule: f.rule, line: lineBase + f.line, preview: f.preview });
21575
+ }
21576
+ }
21577
+ } catch {
21578
+ }
21579
+ for (let i = chunk.text.indexOf("\n"); i >= 0; i = chunk.text.indexOf("\n", i + 1)) lineBase++;
21580
+ }
21581
+ return dropFuzzyFindings(findings, (f) => ({ detector: f.detector, rule: f.rule }));
21582
+ }
21430
21583
  function sessionFileBytes(ref) {
21431
21584
  const backend = getBackendForId(ref.prefixedId);
21432
21585
  if (!backend?.isAppendOnly?.()) return 0;
21433
21586
  return backend.fileSize?.(ref.prefixedId) ?? 0;
21434
21587
  }
21588
+ function sessionBuildBytes(ref) {
21589
+ const main2 = sessionFileBytes(ref);
21590
+ if (main2 === 0) return 0;
21591
+ return Math.max(main2, shadowUncompressedBytes(ref.toolId, ref.rawId));
21592
+ }
21435
21593
  async function buildConversationSync(ref, mtime, opts = {}) {
21436
21594
  const mapPath = opts.mapPath ?? ((p) => p);
21437
21595
  let includeRaw = opts.includeRaw !== false;
21438
21596
  let includeMeta = opts.includeMeta !== false;
21439
21597
  let srcHash;
21440
- const fileBytes = sessionFileBytes(ref);
21441
- const oversized = fileBytes > FULL_BUILD_MAX_BYTES;
21442
- let tailText = "";
21443
- let tailOffsetEnd = 0;
21598
+ const oversized = sessionBuildBytes(ref) > FULL_BUILD_MAX_BYTES;
21599
+ let headOffsetEnd = 0;
21600
+ let oversizedFindings = null;
21444
21601
  let transcript = null;
21445
21602
  let fullestContainer = null;
21446
21603
  if (oversized) {
21447
21604
  const backend = getBackendForId(ref.prefixedId);
21448
21605
  if (!backend?.readFromOffset) return null;
21449
21606
  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);
21607
+ let head = await backend.readFromOffset(ref.prefixedId, 0, SYNC_CHUNK_BYTES);
21608
+ for (let hops = 0; head.skippedBytes && hops < 8; hops++) {
21609
+ head = await backend.readFromOffset(ref.prefixedId, head.newOffset, SYNC_CHUNK_BYTES);
21610
+ }
21611
+ headOffsetEnd = head.newOffset;
21612
+ const messages = parseTailMessages(ref.toolId, head.text);
21454
21613
  if (messages.length > 0) transcript = { messages, subagents: [] };
21455
21614
  } catch {
21456
21615
  }
21457
21616
  includeRaw = false;
21458
- includeMeta = false;
21617
+ oversizedFindings = await scanTranscriptFindingsChunked(ref, opts.tenantRules);
21459
21618
  } else {
21460
21619
  try {
21461
21620
  const backend = getBackendForId(ref.prefixedId) ?? getBackend("claude");
@@ -21516,7 +21675,6 @@ async function buildConversationSync(ref, mtime, opts = {}) {
21516
21675
  );
21517
21676
  let projectPath = ref.projectPath;
21518
21677
  const meta = { messageCount: textMessages.length };
21519
- if (oversized) meta.truncated = true;
21520
21678
  if (textMessages.filter((m) => m.role === "user").length <= 1) meta.oneShot = true;
21521
21679
  try {
21522
21680
  const cwd = ref.fullPath ? readCwdFromJsonl(ref.fullPath) : "";
@@ -21630,21 +21788,7 @@ async function buildConversationSync(ref, mtime, opts = {}) {
21630
21788
  }
21631
21789
  scanMs = performance.now() - t0;
21632
21790
  }
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
- }
21791
+ if (oversizedFindings) findings.push(...oversizedFindings);
21648
21792
  const keptFindings = dropFuzzyFindings(findings, (f) => ({ detector: f.detector, rule: f.rule }));
21649
21793
  findings.length = 0;
21650
21794
  findings.push(...keptFindings);
@@ -21690,7 +21834,10 @@ async function buildConversationSync(ref, mtime, opts = {}) {
21690
21834
  // (convBatch onFlush uses this, not a recomputed size) so the next
21691
21835
  // append's base_offset matches exactly — no spurious continuity misses on
21692
21836
  // an actively-growing session. undefined for non-append-only backends.
21693
- from_offset: oversized ? tailOffsetEnd : getBackendForId(ref.prefixedId)?.fileSize?.(ref.rawId),
21837
+ from_offset: oversized ? headOffsetEnd : getBackendForId(ref.prefixedId)?.fileSize?.(ref.rawId),
21838
+ // The head of a chunked session. The server reports back when its shrink
21839
+ // guard keeps a fuller stored copy, so the client appends from there.
21840
+ ...oversized ? { chunked: true } : {},
21694
21841
  meta,
21695
21842
  mtime
21696
21843
  },
@@ -21712,12 +21859,30 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
21712
21859
  const backend = getBackendForId(ref.prefixedId);
21713
21860
  if (!backend?.readFromOffset) return null;
21714
21861
  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;
21862
+ const { text: tailText, newOffset, skippedBytes } = await backend.readFromOffset(ref.prefixedId, fromOffset, SYNC_CHUNK_BYTES);
21863
+ if (newOffset <= fromOffset) return null;
21864
+ const messages = skippedBytes ? [] : parseTailMessages(ref.toolId, tailText);
21719
21865
  const textMessages = messages.filter((m) => m.role !== "summary" && m.content?.trim());
21720
- if (textMessages.length === 0 && !messages.some((m) => m.toolCalls?.length)) return null;
21866
+ const hasContent = textMessages.length > 0 || messages.some((m) => m.toolCalls?.length);
21867
+ if (!hasContent) {
21868
+ return {
21869
+ conv: {
21870
+ session_id: ref.prefixedId,
21871
+ tool: ref.toolId,
21872
+ project_path: mapPath(ref.projectPath),
21873
+ append: true,
21874
+ base_offset: fromOffset,
21875
+ from_offset: newOffset,
21876
+ redacted_text: "",
21877
+ envelope: { v: TRANSCRIPT_VERSION, messages: [], subagents: [] },
21878
+ mtime: Math.floor(ref.mtime) || 0
21879
+ },
21880
+ redactions: 0,
21881
+ newOffset,
21882
+ findings: [],
21883
+ bytes: 0
21884
+ };
21885
+ }
21721
21886
  const firstUser = textMessages.find((m) => m.role === "user");
21722
21887
  if (isInternalToolPrompt(firstUser?.content)) return null;
21723
21888
  const count = { redactions: 0 };
@@ -21760,7 +21925,8 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
21760
21925
  },
21761
21926
  redactions: count.redactions,
21762
21927
  newOffset,
21763
- findings
21928
+ findings,
21929
+ bytes: Buffer.byteLength(tailText)
21764
21930
  };
21765
21931
  }
21766
21932
  function isSyncSkip(r) {
@@ -21790,7 +21956,7 @@ async function syncIncremental(opts = {}) {
21790
21956
  lock.release();
21791
21957
  }
21792
21958
  }
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;
21959
+ 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
21960
  var init_sync_client = __esm({
21795
21961
  "src/sync-client.ts"() {
21796
21962
  "use strict";
@@ -21835,7 +22001,7 @@ var init_sync_client = __esm({
21835
22001
  ownVersion = JSON.parse(readFileSync38(join49(dir, "../package.json"), "utf-8")).version || ownVersion;
21836
22002
  } catch {
21837
22003
  try {
21838
- if (true) ownVersion = "0.7.0";
22004
+ if (true) ownVersion = "0.7.1";
21839
22005
  } catch {
21840
22006
  }
21841
22007
  }
@@ -21862,8 +22028,9 @@ var init_sync_client = __esm({
21862
22028
  capsOk = /* @__PURE__ */ new Map();
21863
22029
  advertisedIngestConc = /* @__PURE__ */ new Map();
21864
22030
  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;
22031
+ FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "24", 10)) * 1024 * 1024;
22032
+ DERIVED_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_DERIVED_MAX_MB || "64", 10)) * 1024 * 1024;
22033
+ SYNC_CHUNK_BYTES = 8 * 1024 * 1024;
21867
22034
  BATCH_SCAN_MAX_BYTES = Math.max(1, parseInt(process.env.CHAT_RECALL_BATCHSCAN_MAX_MB || "64", 10)) * 1024 * 1024;
21868
22035
  }
21869
22036
  });
@@ -22048,211 +22215,6 @@ var init_update_notice = __esm({
22048
22215
  }
22049
22216
  });
22050
22217
 
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
22061
- });
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");
22067
- }
22068
- function version() {
22069
- try {
22070
- return JSON.parse(readFileSync41(new URL("../package.json", import.meta.url), "utf-8")).version || "0.0.0";
22071
- } catch {
22072
- return "0.0.0";
22073
- }
22074
- }
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
- }
22083
- }
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);
22093
- }
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();
22103
- }
22104
- function realPathOrSelf(p) {
22105
- try {
22106
- return realpathSync4(p);
22107
- } catch {
22108
- return p;
22109
- }
22110
- }
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
- });
22128
- }
22129
- if (targets.length === 0) {
22130
- targets.push({ id: "claude", label: "Claude Code", dir: claudeBackend.skillsDir(), available: claudeBackend.isAvailable() });
22131
- }
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
- ];
22148
- }
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
22218
  // src/service-installer.ts
22257
22219
  var service_installer_exports = {};
22258
22220
  __export(service_installer_exports, {
@@ -22267,15 +22229,15 @@ __export(service_installer_exports, {
22267
22229
  uninstallService: () => uninstallService
22268
22230
  });
22269
22231
  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";
22232
+ import { existsSync as existsSync46, mkdirSync as mkdirSync14, readFileSync as readFileSync41, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
22271
22233
  import { homedir as homedir18 } from "os";
22272
- import { join as join52 } from "path";
22234
+ import { join as join51 } from "path";
22273
22235
  import { fileURLToPath as fileURLToPath4 } from "url";
22274
22236
  function resolveDaemonPaths() {
22275
22237
  const watchJs = fileURLToPath4(new URL("./watch.js", import.meta.url));
22276
22238
  const node = process.execPath;
22277
- const dataDir = join52(homedir18(), ".chat-recall");
22278
- const logFile = join52(dataDir, "watch.log");
22239
+ const dataDir = join51(homedir18(), ".chat-recall");
22240
+ const logFile = join51(dataDir, "watch.log");
22279
22241
  return { watchJs, node, logFile, dataDir };
22280
22242
  }
22281
22243
  function tryCmd(cmd) {
@@ -22294,7 +22256,7 @@ function isServiceRunning() {
22294
22256
  return /com\.chat-recall\.watch/.test(tryCmd("launchctl list 2>/dev/null"));
22295
22257
  }
22296
22258
  if (process.platform === "linux") {
22297
- return /active/.test(tryCmd("systemctl --user is-active chat-recall-watch.service 2>/dev/null"));
22259
+ return /^active$/m.test(tryCmd("systemctl --user is-active chat-recall-watch.service 2>/dev/null"));
22298
22260
  }
22299
22261
  return false;
22300
22262
  }
@@ -22302,13 +22264,13 @@ function refreshServiceDefinition() {
22302
22264
  if (process.platform !== "linux") return false;
22303
22265
  try {
22304
22266
  const { watchJs, node, logFile } = resolveDaemonPaths();
22305
- const unitPath = join52(homedir18(), ".config", "systemd", "user", "chat-recall-watch.service");
22267
+ const unitPath = join51(homedir18(), ".config", "systemd", "user", "chat-recall-watch.service");
22306
22268
  if (!existsSync46(unitPath)) return false;
22307
- const current = readFileSync42(unitPath, "utf8");
22269
+ const current = readFileSync41(unitPath, "utf8");
22308
22270
  if (!current.includes(watchJs)) return false;
22309
22271
  const wanted = renderSystemdUnit(watchJs, node, logFile);
22310
- if (readFileSync42(unitPath, "utf8") === wanted) return false;
22311
- writeFileSync13(unitPath, wanted);
22272
+ if (readFileSync41(unitPath, "utf8") === wanted) return false;
22273
+ writeFileSync12(unitPath, wanted);
22312
22274
  execSync5("systemctl --user daemon-reload", { stdio: "ignore" });
22313
22275
  return true;
22314
22276
  } catch {
@@ -22317,7 +22279,7 @@ function refreshServiceDefinition() {
22317
22279
  }
22318
22280
  function installService() {
22319
22281
  const { watchJs, node, logFile, dataDir } = resolveDaemonPaths();
22320
- mkdirSync15(dataDir, { recursive: true });
22282
+ mkdirSync14(dataDir, { recursive: true });
22321
22283
  if (process.platform === "linux") {
22322
22284
  return installSystemd(watchJs, node, logFile);
22323
22285
  }
@@ -22333,11 +22295,11 @@ function installService() {
22333
22295
  );
22334
22296
  }
22335
22297
  function installSystemd(watchJs, node, logFile) {
22336
- const unitDir = join52(homedir18(), ".config", "systemd", "user");
22337
- const unitPath = join52(unitDir, "chat-recall-watch.service");
22298
+ const unitDir = join51(homedir18(), ".config", "systemd", "user");
22299
+ const unitPath = join51(unitDir, "chat-recall-watch.service");
22338
22300
  const unit = renderSystemdUnit(watchJs, node, logFile);
22339
- mkdirSync15(unitDir, { recursive: true });
22340
- writeFileSync13(unitPath, unit);
22301
+ mkdirSync14(unitDir, { recursive: true });
22302
+ writeFileSync12(unitPath, unit);
22341
22303
  try {
22342
22304
  execSync5("systemctl --user daemon-reload && systemctl --user enable chat-recall-watch.service && systemctl --user restart chat-recall-watch.service", { stdio: "inherit" });
22343
22305
  try {
@@ -22370,7 +22332,12 @@ function renderSystemdUnit(watchJs, node, logFile) {
22370
22332
  // sawtooth. 1536MB keeps GC honest (768 OOMed on the largest subagent-fanout session) with plenty of headroom for the
22371
22333
  // largest transcripts.
22372
22334
  `ExecStart=${node} --max-old-space-size=1536 ${watchJs}`,
22373
- "Restart=on-failure",
22335
+ // ALWAYS: the collector never ends its own work, so any exit systemd did
22336
+ // not ask for is a fault. Under on-failure a clean exit left it dead: on
22337
+ // 2026-09-16 the unit went "inactive (dead) … status=0/SUCCESS" and stayed
22338
+ // down for 8 days, so nothing synced and the auto-update tick, which runs
22339
+ // inside the daemon, never ran. `systemctl stop` still stops it.
22340
+ "Restart=always",
22374
22341
  "RestartSec=10",
22375
22342
  "Nice=10",
22376
22343
  // NEVER STOP RETRYING. systemd's default start limiter gives up after a
@@ -22408,11 +22375,11 @@ function renderSystemdUnit(watchJs, node, logFile) {
22408
22375
  }
22409
22376
  function installLaunchd(watchJs, node, logFile) {
22410
22377
  const label = "com.chat-recall.watch";
22411
- const plistDir = join52(homedir18(), "Library", "LaunchAgents");
22412
- const plistPath = join52(plistDir, `${label}.plist`);
22378
+ const plistDir = join51(homedir18(), "Library", "LaunchAgents");
22379
+ const plistPath = join51(plistDir, `${label}.plist`);
22413
22380
  const plist = renderLaunchdPlist(watchJs, node, logFile, label);
22414
- mkdirSync15(plistDir, { recursive: true });
22415
- writeFileSync13(plistPath, plist);
22381
+ mkdirSync14(plistDir, { recursive: true });
22382
+ writeFileSync12(plistPath, plist);
22416
22383
  try {
22417
22384
  try {
22418
22385
  execSync5(`launchctl unload "${plistPath}"`, { stdio: "ignore" });
@@ -22465,9 +22432,9 @@ function uninstallService() {
22465
22432
  removed = true;
22466
22433
  } catch {
22467
22434
  }
22468
- const unitPath = join52(homedir18(), ".config", "systemd", "user", "chat-recall-watch.service");
22435
+ const unitPath = join51(homedir18(), ".config", "systemd", "user", "chat-recall-watch.service");
22469
22436
  if (existsSync46(unitPath)) {
22470
- rmSync8(unitPath, { force: true });
22437
+ rmSync7(unitPath, { force: true });
22471
22438
  removed = true;
22472
22439
  }
22473
22440
  try {
@@ -22477,14 +22444,14 @@ function uninstallService() {
22477
22444
  return removed;
22478
22445
  }
22479
22446
  if (process.platform === "darwin") {
22480
- const plistPath = join52(homedir18(), "Library", "LaunchAgents", "com.chat-recall.watch.plist");
22447
+ const plistPath = join51(homedir18(), "Library", "LaunchAgents", "com.chat-recall.watch.plist");
22481
22448
  try {
22482
22449
  execSync5(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
22483
22450
  } catch {
22484
22451
  }
22485
22452
  let removed = false;
22486
22453
  if (existsSync46(plistPath)) {
22487
- rmSync8(plistPath, { force: true });
22454
+ rmSync7(plistPath, { force: true });
22488
22455
  removed = true;
22489
22456
  }
22490
22457
  return removed;
@@ -22519,6 +22486,211 @@ var init_service_installer = __esm({
22519
22486
  }
22520
22487
  });
22521
22488
 
22489
+ // src/install-skills.ts
22490
+ var install_skills_exports = {};
22491
+ __export(install_skills_exports, {
22492
+ bundledSkillNames: () => bundledSkillNames,
22493
+ installSkills: () => installSkills,
22494
+ skillStatus: () => skillStatus,
22495
+ skillTargets: () => skillTargets,
22496
+ skillsNeedRefresh: () => skillsNeedRefresh,
22497
+ skillsSourceDir: () => skillsSourceDir,
22498
+ uninstallSkills: () => uninstallSkills
22499
+ });
22500
+ 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";
22501
+ import { createHash as createHash12 } from "crypto";
22502
+ import { join as join52, basename as basename23 } from "path";
22503
+ function skillsSourceDir() {
22504
+ return join52(import.meta.dirname, "..", "skills");
22505
+ }
22506
+ function version() {
22507
+ try {
22508
+ return JSON.parse(readFileSync42(new URL("../package.json", import.meta.url), "utf-8")).version || "0.0.0";
22509
+ } catch {
22510
+ return "0.0.0";
22511
+ }
22512
+ }
22513
+ function skillsFingerprint() {
22514
+ const src = skillsSourceDir();
22515
+ const h = createHash12("sha256");
22516
+ for (const name of bundledSkillNames()) {
22517
+ for (const rel of filesUnder(join52(src, name)).sort()) {
22518
+ h.update(name + "/" + rel);
22519
+ h.update(readFileSync42(join52(src, name, rel)));
22520
+ }
22521
+ }
22522
+ return h.digest("hex").slice(0, 12);
22523
+ }
22524
+ function filesUnder(dir, prefix = "") {
22525
+ const out = [];
22526
+ for (const e of readdirSync34(dir, { withFileTypes: true })) {
22527
+ if (e.name === MARKER) continue;
22528
+ const rel = prefix ? `${prefix}/${e.name}` : e.name;
22529
+ if (e.isDirectory()) out.push(...filesUnder(join52(dir, e.name), rel));
22530
+ else out.push(rel);
22531
+ }
22532
+ return out;
22533
+ }
22534
+ function skillStamp() {
22535
+ return `${version()} ${skillsFingerprint()}`;
22536
+ }
22537
+ function bundledSkillNames() {
22538
+ const src = skillsSourceDir();
22539
+ if (!existsSync47(src)) return [];
22540
+ return readdirSync34(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync47(join52(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
22541
+ }
22542
+ function realPathOrSelf(p) {
22543
+ try {
22544
+ return realpathSync4(p);
22545
+ } catch {
22546
+ return p;
22547
+ }
22548
+ }
22549
+ function claudeSkillTargets() {
22550
+ const targets = [];
22551
+ const seen = /* @__PURE__ */ new Set();
22552
+ for (const home of claudeHomeDirs()) {
22553
+ const dir = join52(home, "skills");
22554
+ const key2 = realPathOrSelf(dir);
22555
+ if (seen.has(key2)) continue;
22556
+ seen.add(key2);
22557
+ const primary = targets.length === 0;
22558
+ targets.push({
22559
+ id: primary ? "claude" : `claude:${basename23(home)}`,
22560
+ label: primary ? "Claude Code" : `Claude Code (${basename23(home)})`,
22561
+ dir,
22562
+ // Judge each profile on ITS OWN transcripts, not the primary's: a home
22563
+ // with no projects/ is a directory someone made, not a Claude install.
22564
+ available: existsSync47(join52(home, "projects"))
22565
+ });
22566
+ }
22567
+ if (targets.length === 0) {
22568
+ targets.push({ id: "claude", label: "Claude Code", dir: claudeBackend.skillsDir(), available: claudeBackend.isAvailable() });
22569
+ }
22570
+ return targets;
22571
+ }
22572
+ function skillTargets() {
22573
+ return [
22574
+ // Per-tool dirs ONLY. We deliberately do NOT also write ~/.agents/skills:
22575
+ // some tools read BOTH ~/.agents/skills and their own dir, so installing to
22576
+ // both produces "skill conflict" duplicate warnings. Every tool we support
22577
+ // has its own dir, so per-tool coverage is complete.
22578
+ ...claudeSkillTargets(),
22579
+ { id: "agy", label: "Antigravity", dir: join52(agyBackend.homeDir(), "skills"), available: agyBackend.isAvailable() },
22580
+ { id: "codex", label: "Codex", dir: codexBackend.skillsDir(), available: codexBackend.isAvailable() },
22581
+ { id: "opencode", label: "OpenCode", dir: opencodeBackend.skillsDir(), available: opencodeBackend.isAvailable() },
22582
+ // Cursor DOES have its own dir (~/.cursor/skills). `skills-cursor` next to
22583
+ // it is Cursor's bundled set — read-only, never an install target.
22584
+ { id: "cursor", label: "Cursor", dir: join52(cursorHomeDir(), "skills"), available: cursorBackend.isAvailable() }
22585
+ ];
22586
+ }
22587
+ function isManaged(dir) {
22588
+ return existsSync47(join52(dir, MARKER));
22589
+ }
22590
+ function installSkills(opts = {}) {
22591
+ const onlyAvailable = opts.onlyAvailable ?? true;
22592
+ const src = skillsSourceDir();
22593
+ const names = bundledSkillNames();
22594
+ const v = version();
22595
+ const stamp = skillStamp();
22596
+ const perTarget = [];
22597
+ for (const t of skillTargets()) {
22598
+ const installed = [];
22599
+ const skippedUserOwned = [];
22600
+ if (onlyAvailable && !t.available) {
22601
+ perTarget.push({ ...t, installed, skippedUserOwned });
22602
+ continue;
22603
+ }
22604
+ for (const name of names) {
22605
+ const dest = join52(t.dir, name);
22606
+ if (existsSync47(dest) && !isManaged(dest)) {
22607
+ skippedUserOwned.push(name);
22608
+ continue;
22609
+ }
22610
+ mkdirSync15(t.dir, { recursive: true });
22611
+ rmSync8(dest, { recursive: true, force: true });
22612
+ cpSync(join52(src, name), dest, { recursive: true });
22613
+ writeFileSync13(join52(dest, MARKER), `${stamp}
22614
+ `);
22615
+ installed.push(name);
22616
+ }
22617
+ perTarget.push({ ...t, installed, skippedUserOwned });
22618
+ }
22619
+ return { version: v, perTarget };
22620
+ }
22621
+ function uninstallSkills() {
22622
+ const names = new Set(bundledSkillNames());
22623
+ const perTarget = [];
22624
+ for (const t of skillTargets()) {
22625
+ const removed = [];
22626
+ if (!existsSync47(t.dir)) {
22627
+ perTarget.push({ id: t.id, label: t.label, removed });
22628
+ continue;
22629
+ }
22630
+ for (const entry of readdirSync34(t.dir, { withFileTypes: true })) {
22631
+ if (!entry.isDirectory()) continue;
22632
+ const dir = join52(t.dir, entry.name);
22633
+ if (isManaged(dir) && (names.has(entry.name) || entry.name.startsWith("chat-recall"))) {
22634
+ rmSync8(dir, { recursive: true, force: true });
22635
+ removed.push(entry.name);
22636
+ }
22637
+ }
22638
+ perTarget.push({ id: t.id, label: t.label, removed });
22639
+ }
22640
+ return { perTarget };
22641
+ }
22642
+ function skillsNeedRefresh() {
22643
+ const cur = skillStamp();
22644
+ const names = bundledSkillNames();
22645
+ if (names.length === 0) return false;
22646
+ for (const t of skillTargets()) {
22647
+ if (!t.available) continue;
22648
+ for (const name of names) {
22649
+ const marker = join52(t.dir, name, MARKER);
22650
+ if (!existsSync47(marker)) return true;
22651
+ try {
22652
+ if (readFileSync42(marker, "utf-8").trim() !== cur) return true;
22653
+ } catch {
22654
+ return true;
22655
+ }
22656
+ }
22657
+ }
22658
+ return false;
22659
+ }
22660
+ function skillStatus() {
22661
+ const names = bundledSkillNames();
22662
+ const cur = skillStamp();
22663
+ return skillTargets().map((t) => {
22664
+ let installed = 0;
22665
+ let ver = null;
22666
+ for (const name of names) {
22667
+ const marker = join52(t.dir, name, MARKER);
22668
+ if (existsSync47(marker)) {
22669
+ installed++;
22670
+ try {
22671
+ ver = readFileSync42(marker, "utf-8").trim();
22672
+ } catch {
22673
+ }
22674
+ }
22675
+ }
22676
+ return { id: t.id, label: t.label, available: t.available, installed, version: installed === names.length ? ver : installed ? `${ver} (partial)` : null };
22677
+ }).filter((s) => s.available || s.installed > 0);
22678
+ }
22679
+ var MARKER;
22680
+ var init_install_skills = __esm({
22681
+ "src/install-skills.ts"() {
22682
+ "use strict";
22683
+ init_tool_paths();
22684
+ init_claude2();
22685
+ init_opencode();
22686
+ init_codex2();
22687
+ init_agy();
22688
+ init_cursor();
22689
+ init_tool_paths();
22690
+ MARKER = ".chat-recall-managed";
22691
+ }
22692
+ });
22693
+
22522
22694
  // src/mcp.ts
22523
22695
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22524
22696
  import { spawn as spawn3 } from "child_process";
@@ -22639,6 +22811,107 @@ function socketPathFromArgv(argv) {
22639
22811
  return v && !v.startsWith("--") ? v : void 0;
22640
22812
  }
22641
22813
 
22814
+ // src/relay-lifecycle.ts
22815
+ var PARENT_POLL_MS = 5e3;
22816
+ function pidAlive(pid) {
22817
+ try {
22818
+ process.kill(pid, 0);
22819
+ return true;
22820
+ } catch (err) {
22821
+ return err.code === "EPERM";
22822
+ }
22823
+ }
22824
+ var processProbe = {
22825
+ ppid: () => process.ppid,
22826
+ alive: pidAlive
22827
+ };
22828
+ function parentGone(originalPpid, probe) {
22829
+ if (probe.ppid() !== originalPpid) return true;
22830
+ return !probe.alive(originalPpid);
22831
+ }
22832
+ function watchParent(onGone, opts = {}) {
22833
+ const probe = opts.probe ?? processProbe;
22834
+ const original = probe.ppid();
22835
+ if (original <= 1) return () => {
22836
+ };
22837
+ const timer = setInterval(() => {
22838
+ if (!parentGone(original, probe)) return;
22839
+ clearInterval(timer);
22840
+ onGone();
22841
+ }, opts.intervalMs ?? PARENT_POLL_MS);
22842
+ timer.unref?.();
22843
+ return () => clearInterval(timer);
22844
+ }
22845
+ function onInputFinished(input, onEnd) {
22846
+ let fired = false;
22847
+ const fire = () => {
22848
+ if (fired) return;
22849
+ fired = true;
22850
+ onEnd();
22851
+ };
22852
+ input.once("end", fire);
22853
+ input.once("close", fire);
22854
+ input.once("error", fire);
22855
+ }
22856
+
22857
+ // src/sync-supervisor.ts
22858
+ function createSyncSupervisor(opts) {
22859
+ let owner = null;
22860
+ let inFlight = false;
22861
+ let tookOver = false;
22862
+ const log6 = opts.log ?? (() => {
22863
+ });
22864
+ const serviceRunning = () => {
22865
+ try {
22866
+ return opts.serviceRunning();
22867
+ } catch {
22868
+ return false;
22869
+ }
22870
+ };
22871
+ const check = async () => {
22872
+ if (serviceRunning()) {
22873
+ if (owner !== "service") {
22874
+ 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)");
22875
+ }
22876
+ owner = "service";
22877
+ return;
22878
+ }
22879
+ const scope = owner === "mcp" ? "changed" : "full";
22880
+ if (owner !== "mcp") {
22881
+ log6(owner === "service" ? "watch service is no longer running \u2014 MCP takes over background sync" : "no watch service running \u2014 MCP runs background sync");
22882
+ if (!tookOver) {
22883
+ tookOver = true;
22884
+ opts.onFirstTakeover?.();
22885
+ }
22886
+ }
22887
+ owner = "mcp";
22888
+ if (inFlight) return;
22889
+ inFlight = true;
22890
+ try {
22891
+ await opts.tick(scope);
22892
+ } catch {
22893
+ } finally {
22894
+ inFlight = false;
22895
+ }
22896
+ };
22897
+ const first = setTimeout(() => {
22898
+ void check();
22899
+ }, opts.firstCheckMs);
22900
+ first.unref?.();
22901
+ const every = setInterval(() => {
22902
+ void check();
22903
+ }, opts.intervalMs);
22904
+ every.unref?.();
22905
+ return {
22906
+ check,
22907
+ owner: () => owner,
22908
+ stop: () => {
22909
+ clearTimeout(first);
22910
+ clearInterval(every);
22911
+ }
22912
+ };
22913
+ }
22914
+
22642
22915
  // src/mcp.ts
22643
22916
  import { fileURLToPath as fileURLToPath5 } from "node:url";
22644
22917
 
@@ -23553,7 +23826,7 @@ function sanitizeQuery(query) {
23553
23826
 
23554
23827
  // ../engine/dist/core/write-ahead-log.js
23555
23828
  init_paths();
23556
- import { existsSync as existsSync17, mkdirSync as mkdirSync5, appendFileSync, openSync as openSync7, closeSync as closeSync7, chmodSync as chmodSync3 } from "fs";
23829
+ import { existsSync as existsSync17, mkdirSync as mkdirSync5, appendFileSync, openSync as openSync8, closeSync as closeSync8, chmodSync as chmodSync3 } from "fs";
23557
23830
  import { join as join20 } from "path";
23558
23831
  var REDACT_KEYS = /* @__PURE__ */ new Set([
23559
23832
  "content",
@@ -23579,8 +23852,8 @@ var WriteAheadLog = class {
23579
23852
  }
23580
23853
  this.walPath = join20(dir, "write_log.jsonl");
23581
23854
  if (!existsSync17(this.walPath)) {
23582
- const fd = openSync7(this.walPath, "w", 384);
23583
- closeSync7(fd);
23855
+ const fd = openSync8(this.walPath, "w", 384);
23856
+ closeSync8(fd);
23584
23857
  }
23585
23858
  }
23586
23859
  /**
@@ -28371,12 +28644,6 @@ function startBackgroundSync() {
28371
28644
  console.error("[mcp] background sync tick failed:", err instanceof Error ? err.message : err);
28372
28645
  }
28373
28646
  };
28374
- setInterval(() => {
28375
- void tick("changed");
28376
- }, SYNC_TICK_MS).unref();
28377
- setTimeout(() => {
28378
- void tick("full");
28379
- }, 15e3).unref();
28380
28647
  const updateTick = async () => {
28381
28648
  try {
28382
28649
  const { refreshUpdateCheck: refreshUpdateCheck2, updateNotice: updateNotice3 } = await Promise.resolve().then(() => (init_update_notice(), update_notice_exports));
@@ -28386,12 +28653,27 @@ function startBackgroundSync() {
28386
28653
  } catch {
28387
28654
  }
28388
28655
  };
28389
- setTimeout(() => {
28390
- void updateTick();
28391
- }, 2e4).unref();
28392
- setInterval(() => {
28393
- void updateTick();
28394
- }, 6 * 60 * 60 * 1e3).unref();
28656
+ const startUpdateProbe = () => {
28657
+ setTimeout(() => {
28658
+ void updateTick();
28659
+ }, 5e3).unref();
28660
+ setInterval(() => {
28661
+ void updateTick();
28662
+ }, 6 * 60 * 60 * 1e3).unref();
28663
+ };
28664
+ let isServiceRunning2 = null;
28665
+ createSyncSupervisor({
28666
+ serviceRunning: () => isServiceRunning2?.() ?? false,
28667
+ tick,
28668
+ intervalMs: SYNC_TICK_MS,
28669
+ firstCheckMs: 15e3,
28670
+ onFirstTakeover: startUpdateProbe,
28671
+ log: (msg) => console.error(`[mcp] ${msg}`)
28672
+ });
28673
+ void Promise.resolve().then(() => (init_service_installer(), service_installer_exports)).then((m) => {
28674
+ isServiceRunning2 = m.isServiceRunning;
28675
+ }).catch(() => {
28676
+ });
28395
28677
  }
28396
28678
  function installCrashGuards() {
28397
28679
  process.on("unhandledRejection", (reason) => {
@@ -28404,7 +28686,7 @@ function installCrashGuards() {
28404
28686
  reportClientEvent2("mcp_crash", { message: err.stack || err.message });
28405
28687
  });
28406
28688
  }
28407
- var RELAY_VERSION = true ? "0.7.0" : "0.0.0";
28689
+ var RELAY_VERSION = true ? "0.7.1" : "0.0.0";
28408
28690
  function daemonIdleSecs() {
28409
28691
  const raw = process.env.CHAT_RECALL_DAEMON_IDLE_SECS;
28410
28692
  if (raw === void 0) return 900;
@@ -28509,6 +28791,9 @@ async function main() {
28509
28791
  } else {
28510
28792
  const transport2 = new StdioServerTransport();
28511
28793
  await createMcpServer().connect(transport2);
28794
+ const leave = () => process.exit(0);
28795
+ onInputFinished(process.stdin, leave);
28796
+ watchParent(leave);
28512
28797
  }
28513
28798
  void (async () => {
28514
28799
  try {
@@ -28529,16 +28814,6 @@ async function main() {
28529
28814
  }
28530
28815
  } catch {
28531
28816
  }
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
- }
28817
+ startBackgroundSync();
28543
28818
  }
28544
28819
  main().catch(console.error);