chat-recall 0.5.5 → 0.5.6

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/watch.js CHANGED
@@ -2110,7 +2110,7 @@ var require_thread_stream = __commonJS({
2110
2110
  var { version } = require_package();
2111
2111
  var { EventEmitter } = __require("events");
2112
2112
  var { Worker } = __require("worker_threads");
2113
- var { join: join48 } = __require("path");
2113
+ var { join: join50 } = __require("path");
2114
2114
  var { pathToFileURL } = __require("url");
2115
2115
  var { wait } = require_wait();
2116
2116
  var {
@@ -2153,7 +2153,7 @@ var require_thread_stream = __commonJS({
2153
2153
  function createWorker(stream, opts) {
2154
2154
  const { filename, workerData } = opts;
2155
2155
  const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
2156
- const toExecute = bundlerOverrides["thread-stream-worker"] || join48(__dirname, "lib", "worker.js");
2156
+ const toExecute = bundlerOverrides["thread-stream-worker"] || join50(__dirname, "lib", "worker.js");
2157
2157
  const worker = new Worker(toExecute, {
2158
2158
  ...opts.workerOpts,
2159
2159
  trackUnmanagedFds: false,
@@ -2556,7 +2556,7 @@ var require_transport = __commonJS({
2556
2556
  "use strict";
2557
2557
  var { createRequire: createRequire3 } = __require("module");
2558
2558
  var getCallers = require_caller();
2559
- var { join: join48, isAbsolute: isAbsolute2, sep: sep2 } = __require("node:path");
2559
+ var { join: join50, isAbsolute: isAbsolute3, sep: sep4 } = __require("node:path");
2560
2560
  var sleep = require_atomic_sleep();
2561
2561
  var onExit = require_on_exit_leak_free();
2562
2562
  var ThreadStream = require_thread_stream();
@@ -2619,7 +2619,7 @@ var require_transport = __commonJS({
2619
2619
  throw new Error("only one of target or targets can be specified");
2620
2620
  }
2621
2621
  if (targets) {
2622
- target = bundlerOverrides["pino-worker"] || join48(__dirname, "worker.js");
2622
+ target = bundlerOverrides["pino-worker"] || join50(__dirname, "worker.js");
2623
2623
  options.targets = targets.filter((dest) => dest.target).map((dest) => {
2624
2624
  return {
2625
2625
  ...dest,
@@ -2637,7 +2637,7 @@ var require_transport = __commonJS({
2637
2637
  });
2638
2638
  });
2639
2639
  } else if (pipeline) {
2640
- target = bundlerOverrides["pino-worker"] || join48(__dirname, "worker.js");
2640
+ target = bundlerOverrides["pino-worker"] || join50(__dirname, "worker.js");
2641
2641
  options.pipelines = [pipeline.map((dest) => {
2642
2642
  return {
2643
2643
  ...dest,
@@ -2655,16 +2655,16 @@ var require_transport = __commonJS({
2655
2655
  return buildStream(fixTarget(target), options, worker, sync);
2656
2656
  function fixTarget(origin) {
2657
2657
  origin = bundlerOverrides[origin] || origin;
2658
- if (isAbsolute2(origin) || origin.indexOf("file://") === 0) {
2658
+ if (isAbsolute3(origin) || origin.indexOf("file://") === 0) {
2659
2659
  return origin;
2660
2660
  }
2661
2661
  if (origin === "pino/file") {
2662
- return join48(__dirname, "..", "file.js");
2662
+ return join50(__dirname, "..", "file.js");
2663
2663
  }
2664
2664
  let fixTarget2;
2665
2665
  for (const filePath of callers) {
2666
2666
  try {
2667
- const context = filePath === "node:repl" ? process.cwd() + sep2 : filePath;
2667
+ const context = filePath === "node:repl" ? process.cwd() + sep4 : filePath;
2668
2668
  fixTarget2 = createRequire3(context).resolve(origin);
2669
2669
  break;
2670
2670
  } catch (err) {
@@ -3648,7 +3648,7 @@ var require_safe_stable_stringify = __commonJS({
3648
3648
  return circularValue;
3649
3649
  }
3650
3650
  let res = "";
3651
- let join48 = ",";
3651
+ let join50 = ",";
3652
3652
  const originalIndentation = indentation;
3653
3653
  if (Array.isArray(value)) {
3654
3654
  if (value.length === 0) {
@@ -3662,7 +3662,7 @@ var require_safe_stable_stringify = __commonJS({
3662
3662
  indentation += spacer;
3663
3663
  res += `
3664
3664
  ${indentation}`;
3665
- join48 = `,
3665
+ join50 = `,
3666
3666
  ${indentation}`;
3667
3667
  }
3668
3668
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -3670,13 +3670,13 @@ ${indentation}`;
3670
3670
  for (; i < maximumValuesToStringify - 1; i++) {
3671
3671
  const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
3672
3672
  res += tmp2 !== void 0 ? tmp2 : "null";
3673
- res += join48;
3673
+ res += join50;
3674
3674
  }
3675
3675
  const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
3676
3676
  res += tmp !== void 0 ? tmp : "null";
3677
3677
  if (value.length - 1 > maximumBreadth) {
3678
3678
  const removedKeys = value.length - maximumBreadth - 1;
3679
- res += `${join48}"... ${getItemCount(removedKeys)} not stringified"`;
3679
+ res += `${join50}"... ${getItemCount(removedKeys)} not stringified"`;
3680
3680
  }
3681
3681
  if (spacer !== "") {
3682
3682
  res += `
@@ -3697,7 +3697,7 @@ ${originalIndentation}`;
3697
3697
  let separator = "";
3698
3698
  if (spacer !== "") {
3699
3699
  indentation += spacer;
3700
- join48 = `,
3700
+ join50 = `,
3701
3701
  ${indentation}`;
3702
3702
  whitespace = " ";
3703
3703
  }
@@ -3711,13 +3711,13 @@ ${indentation}`;
3711
3711
  const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
3712
3712
  if (tmp !== void 0) {
3713
3713
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
3714
- separator = join48;
3714
+ separator = join50;
3715
3715
  }
3716
3716
  }
3717
3717
  if (keyLength > maximumBreadth) {
3718
3718
  const removedKeys = keyLength - maximumBreadth;
3719
3719
  res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
3720
- separator = join48;
3720
+ separator = join50;
3721
3721
  }
3722
3722
  if (spacer !== "" && separator.length > 1) {
3723
3723
  res = `
@@ -3758,7 +3758,7 @@ ${originalIndentation}`;
3758
3758
  }
3759
3759
  const originalIndentation = indentation;
3760
3760
  let res = "";
3761
- let join48 = ",";
3761
+ let join50 = ",";
3762
3762
  if (Array.isArray(value)) {
3763
3763
  if (value.length === 0) {
3764
3764
  return "[]";
@@ -3771,7 +3771,7 @@ ${originalIndentation}`;
3771
3771
  indentation += spacer;
3772
3772
  res += `
3773
3773
  ${indentation}`;
3774
- join48 = `,
3774
+ join50 = `,
3775
3775
  ${indentation}`;
3776
3776
  }
3777
3777
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -3779,13 +3779,13 @@ ${indentation}`;
3779
3779
  for (; i < maximumValuesToStringify - 1; i++) {
3780
3780
  const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
3781
3781
  res += tmp2 !== void 0 ? tmp2 : "null";
3782
- res += join48;
3782
+ res += join50;
3783
3783
  }
3784
3784
  const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
3785
3785
  res += tmp !== void 0 ? tmp : "null";
3786
3786
  if (value.length - 1 > maximumBreadth) {
3787
3787
  const removedKeys = value.length - maximumBreadth - 1;
3788
- res += `${join48}"... ${getItemCount(removedKeys)} not stringified"`;
3788
+ res += `${join50}"... ${getItemCount(removedKeys)} not stringified"`;
3789
3789
  }
3790
3790
  if (spacer !== "") {
3791
3791
  res += `
@@ -3798,7 +3798,7 @@ ${originalIndentation}`;
3798
3798
  let whitespace = "";
3799
3799
  if (spacer !== "") {
3800
3800
  indentation += spacer;
3801
- join48 = `,
3801
+ join50 = `,
3802
3802
  ${indentation}`;
3803
3803
  whitespace = " ";
3804
3804
  }
@@ -3807,7 +3807,7 @@ ${indentation}`;
3807
3807
  const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
3808
3808
  if (tmp !== void 0) {
3809
3809
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
3810
- separator = join48;
3810
+ separator = join50;
3811
3811
  }
3812
3812
  }
3813
3813
  if (spacer !== "" && separator.length > 1) {
@@ -3865,20 +3865,20 @@ ${originalIndentation}`;
3865
3865
  indentation += spacer;
3866
3866
  let res2 = `
3867
3867
  ${indentation}`;
3868
- const join49 = `,
3868
+ const join51 = `,
3869
3869
  ${indentation}`;
3870
3870
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
3871
3871
  let i = 0;
3872
3872
  for (; i < maximumValuesToStringify - 1; i++) {
3873
3873
  const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
3874
3874
  res2 += tmp2 !== void 0 ? tmp2 : "null";
3875
- res2 += join49;
3875
+ res2 += join51;
3876
3876
  }
3877
3877
  const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
3878
3878
  res2 += tmp !== void 0 ? tmp : "null";
3879
3879
  if (value.length - 1 > maximumBreadth) {
3880
3880
  const removedKeys = value.length - maximumBreadth - 1;
3881
- res2 += `${join49}"... ${getItemCount(removedKeys)} not stringified"`;
3881
+ res2 += `${join51}"... ${getItemCount(removedKeys)} not stringified"`;
3882
3882
  }
3883
3883
  res2 += `
3884
3884
  ${originalIndentation}`;
@@ -3894,16 +3894,16 @@ ${originalIndentation}`;
3894
3894
  return '"[Object]"';
3895
3895
  }
3896
3896
  indentation += spacer;
3897
- const join48 = `,
3897
+ const join50 = `,
3898
3898
  ${indentation}`;
3899
3899
  let res = "";
3900
3900
  let separator = "";
3901
3901
  let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
3902
3902
  if (isTypedArrayWithEntries(value)) {
3903
- res += stringifyTypedArray(value, join48, maximumBreadth);
3903
+ res += stringifyTypedArray(value, join50, maximumBreadth);
3904
3904
  keys = keys.slice(value.length);
3905
3905
  maximumPropertiesToStringify -= value.length;
3906
- separator = join48;
3906
+ separator = join50;
3907
3907
  }
3908
3908
  if (deterministic) {
3909
3909
  keys = sort(keys, comparator);
@@ -3914,13 +3914,13 @@ ${indentation}`;
3914
3914
  const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
3915
3915
  if (tmp !== void 0) {
3916
3916
  res += `${separator}${strEscape(key2)}: ${tmp}`;
3917
- separator = join48;
3917
+ separator = join50;
3918
3918
  }
3919
3919
  }
3920
3920
  if (keyLength > maximumBreadth) {
3921
3921
  const removedKeys = keyLength - maximumBreadth;
3922
3922
  res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
3923
- separator = join48;
3923
+ separator = join50;
3924
3924
  }
3925
3925
  if (separator !== "") {
3926
3926
  res = `
@@ -4721,7 +4721,8 @@ function defaultSync() {
4721
4721
  function isPersonalPath(projectPath, includeProjects = []) {
4722
4722
  if (includeProjects.some((inc) => inc && projectPath.includes(inc)))
4723
4723
  return false;
4724
- return PERSONAL_DIR_PATTERNS.some((pat) => projectPath.includes(pat));
4724
+ const norm = (projectPath.replace(/\\/g, "/") + "/").toLowerCase();
4725
+ return PERSONAL_DIR_NAMES.some((n) => norm.includes(`/${n.toLowerCase()}/`));
4725
4726
  }
4726
4727
  function defaultTeam() {
4727
4728
  return {
@@ -4843,6 +4844,16 @@ function loadSecrets() {
4843
4844
  const p = secretsPath();
4844
4845
  if (!existsSync2(p))
4845
4846
  return out;
4847
+ let mtimeMs = -1, size = -1;
4848
+ try {
4849
+ const st = statSync2(p);
4850
+ mtimeMs = st.mtimeMs;
4851
+ size = st.size;
4852
+ } catch {
4853
+ }
4854
+ if (_secretsCache && _secretsCache.mtimeMs === mtimeMs && _secretsCache.size === size) {
4855
+ return { ..._secretsCache.value };
4856
+ }
4846
4857
  try {
4847
4858
  for (const line of readFileSync(p, "utf-8").split("\n")) {
4848
4859
  const t = line.trim();
@@ -4860,9 +4871,12 @@ function loadSecrets() {
4860
4871
  }
4861
4872
  } catch {
4862
4873
  }
4874
+ if (mtimeMs >= 0)
4875
+ _secretsCache = { mtimeMs, size, value: { ...out } };
4863
4876
  return out;
4864
4877
  }
4865
4878
  function writeSecrets(secrets) {
4879
+ _secretsCache = null;
4866
4880
  const p = secretsPath();
4867
4881
  mkdirSync2(dirname2(p), { recursive: true });
4868
4882
  const body = "# chat-recall secrets \u2014 NOT committed/synced. Mode 0600.\n" + Object.entries(secrets).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
@@ -4872,10 +4886,31 @@ function writeSecrets(secrets) {
4872
4886
  } catch {
4873
4887
  }
4874
4888
  }
4889
+ function applySecretOverlay(result) {
4890
+ const secrets = loadSecrets();
4891
+ for (const { block, field, env } of SECRET_FIELDS) {
4892
+ const blk = result[block];
4893
+ const value = process.env[env] || secrets[env] || blk[field];
4894
+ if (value)
4895
+ blk[field] = value;
4896
+ }
4897
+ return result;
4898
+ }
4875
4899
  function loadSettings() {
4876
4900
  const path = settingsPath();
4877
4901
  if (!existsSync2(path))
4878
4902
  return freshDefaults();
4903
+ let mtimeMs = -1, size = -1;
4904
+ try {
4905
+ const st = statSync2(path);
4906
+ mtimeMs = st.mtimeMs;
4907
+ size = st.size;
4908
+ } catch {
4909
+ }
4910
+ if (_settingsCache && _settingsCache.mtimeMs === mtimeMs && _settingsCache.size === size) {
4911
+ const cached = structuredClone(_settingsCache.value);
4912
+ return applySecretOverlay(cached);
4913
+ }
4879
4914
  try {
4880
4915
  const parsed = JSON.parse(readFileSync(path, "utf-8"));
4881
4916
  const base2 = freshDefaults();
@@ -4888,19 +4923,15 @@ function loadSettings() {
4888
4923
  sync: mergeSync(base2.sync, parsed.sync),
4889
4924
  team: mergeTeam(base2.team, parsed.team)
4890
4925
  };
4891
- const secrets = loadSecrets();
4892
- for (const { block, field, env } of SECRET_FIELDS) {
4893
- const blk = result[block];
4894
- const value = process.env[env] || secrets[env] || blk[field];
4895
- if (value)
4896
- blk[field] = value;
4897
- }
4898
- return result;
4926
+ if (mtimeMs >= 0)
4927
+ _settingsCache = { mtimeMs, size, value: structuredClone(result) };
4928
+ return applySecretOverlay(result);
4899
4929
  } catch {
4900
4930
  return freshDefaults();
4901
4931
  }
4902
4932
  }
4903
4933
  function saveSettings(settings2) {
4934
+ _settingsCache = null;
4904
4935
  const secrets = loadSecrets();
4905
4936
  const stripped = {
4906
4937
  ...settings2,
@@ -4924,7 +4955,7 @@ function saveSettings(settings2) {
4924
4955
  } catch {
4925
4956
  }
4926
4957
  }
4927
- var CLI_DETECTION_ORDER, SCHEMA_VERSION, PERSONAL_DIR_PATTERNS, _sourceSettingsCache, SECRET_FIELDS;
4958
+ var CLI_DETECTION_ORDER, SCHEMA_VERSION, PERSONAL_DIR_NAMES, PERSONAL_DIR_PATTERNS, _sourceSettingsCache, SECRET_FIELDS, _secretsCache, _settingsCache;
4928
4959
  var init_settings = __esm({
4929
4960
  "../engine/dist/core/settings.js"() {
4930
4961
  "use strict";
@@ -4938,17 +4969,18 @@ var init_settings = __esm({
4938
4969
  { preset: "aichat", bin: "aichat", cmd: "aichat --no-stream" }
4939
4970
  ];
4940
4971
  SCHEMA_VERSION = 3;
4941
- PERSONAL_DIR_PATTERNS = [
4942
- "/Pictures/",
4943
- "/Music/",
4944
- "/Movies/",
4945
- "/Videos/",
4946
- "/Photos Library",
4947
- "/Documents/",
4948
- "/Desktop/",
4949
- "/Downloads/",
4950
- "/Library/"
4972
+ PERSONAL_DIR_NAMES = [
4973
+ "Pictures",
4974
+ "Music",
4975
+ "Movies",
4976
+ "Videos",
4977
+ "Photos Library",
4978
+ "Documents",
4979
+ "Desktop",
4980
+ "Downloads",
4981
+ "Library"
4951
4982
  ];
4983
+ PERSONAL_DIR_PATTERNS = PERSONAL_DIR_NAMES.map((n) => `/${n}/`);
4952
4984
  _sourceSettingsCache = null;
4953
4985
  SECRET_FIELDS = [
4954
4986
  { block: "summary", field: "apiKey", env: "SUMMARY_API_KEY" },
@@ -4959,6 +4991,8 @@ var init_settings = __esm({
4959
4991
  { block: "embedding", field: "ollamaCloudApiKey", env: "OLLAMA_API_KEY" },
4960
4992
  { block: "embedding", field: "openaiCompatApiKey", env: "OPENAI_COMPAT_API_KEY" }
4961
4993
  ];
4994
+ _secretsCache = null;
4995
+ _settingsCache = null;
4962
4996
  }
4963
4997
  });
4964
4998
 
@@ -5048,14 +5082,14 @@ function primaryHomes() {
5048
5082
  s = void 0;
5049
5083
  }
5050
5084
  const opencodeDb = env.CHAT_RECALL_OPENCODE_DB || s?.opencodeDbPath || join3(home, ".local", "share", "opencode", "opencode.db");
5051
- const candidates = [
5085
+ const candidates2 = [
5052
5086
  env.CHAT_RECALL_CLAUDE_HOME || s?.claudeHome || join3(home, ".claude"),
5053
5087
  env.CHAT_RECALL_GEMINI_HOME || s?.geminiHome || join3(home, ".gemini"),
5054
5088
  env.CHAT_RECALL_CODEX_HOME || s?.codexHome || join3(home, ".codex"),
5055
5089
  env.CHAT_RECALL_AGY_HOME || s?.agyHome || join3(home, ".gemini", "antigravity-cli"),
5056
5090
  dirOf(opencodeDb)
5057
5091
  ];
5058
- return candidates.map(normalizeHomePath);
5092
+ return candidates2.map(normalizeHomePath);
5059
5093
  }
5060
5094
  function homeDecision(path) {
5061
5095
  const p = normalizeHomePath(path);
@@ -5408,28 +5442,55 @@ function maskSecret(s) {
5408
5442
  return "*".repeat(s.length);
5409
5443
  return "*".repeat(Math.min(s.length - 4, 60)) + s.slice(-4);
5410
5444
  }
5445
+ function globalRegexFor(pattern) {
5446
+ const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
5447
+ const key = pattern.source + "\0" + flags;
5448
+ let re = globalRegexCache.get(key);
5449
+ if (!re) {
5450
+ re = new RegExp(pattern.source, flags);
5451
+ globalRegexCache.set(key, re);
5452
+ }
5453
+ return re;
5454
+ }
5455
+ function makeLineResolver(text) {
5456
+ const starts = [];
5457
+ for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1))
5458
+ starts.push(i);
5459
+ return (index) => {
5460
+ let lo = 0, hi = starts.length;
5461
+ while (lo < hi) {
5462
+ const mid = lo + hi >> 1;
5463
+ if (starts[mid] < index)
5464
+ lo = mid + 1;
5465
+ else
5466
+ hi = mid;
5467
+ }
5468
+ return lo + 1;
5469
+ };
5470
+ }
5411
5471
  function scanTextForFindings(text, opts = {}) {
5412
5472
  if (!text)
5413
5473
  return [];
5414
5474
  const rules = opts.rules || activeRules();
5415
5475
  const findings = [];
5416
5476
  const seenSpans = /* @__PURE__ */ new Set();
5477
+ const lineAt = makeLineResolver(text);
5417
5478
  for (const r of rules) {
5418
- const re = new RegExp(r.pattern.source, r.pattern.flags.includes("g") ? r.pattern.flags : r.pattern.flags + "g");
5479
+ const re = globalRegexFor(r.pattern);
5480
+ re.lastIndex = 0;
5419
5481
  let m;
5420
5482
  while ((m = re.exec(text)) !== null) {
5421
5483
  const span = `${m.index}:${m[0].length}`;
5422
5484
  if (!seenSpans.has(span)) {
5423
5485
  seenSpans.add(span);
5424
- const line = text.slice(0, m.index).split("\n").length;
5425
- findings.push({ rule: r.label, line, preview: maskSecret(m[0]) });
5486
+ findings.push({ rule: r.label, line: lineAt(m.index), preview: maskSecret(m[0]) });
5426
5487
  }
5427
5488
  if (re.lastIndex === m.index)
5428
5489
  re.lastIndex++;
5429
5490
  }
5430
5491
  }
5431
5492
  for (const { start, end } of findContextualSecrets(text)) {
5432
- const line = text.slice(0, start).split("\n").length;
5493
+ const line = lineAt(start);
5433
5494
  findings.push({ rule: "secret-context", line, preview: maskSecret(text.slice(start, end)) });
5434
5495
  }
5435
5496
  return findings;
@@ -5460,7 +5521,7 @@ function redactToolResultBody(toolName, body) {
5460
5521
  return body;
5461
5522
  return `[redacted: ${toolName} output, ${body.length} chars]`;
5462
5523
  }
5463
- var DEFAULT_REDACTION_RULES, SECRET_CONTEXT_RE, HIGH_ENTROPY_TOKEN_RE, cache, CANARY, MAX_PACK_RULES, MAX_PATTERN_LEN, serverPack, privacyCache;
5524
+ var DEFAULT_REDACTION_RULES, SECRET_CONTEXT_RE, HIGH_ENTROPY_TOKEN_RE, cache, CANARY, MAX_PACK_RULES, MAX_PATTERN_LEN, serverPack, globalRegexCache, privacyCache;
5464
5525
  var init_secret_redactor = __esm({
5465
5526
  "../engine/dist/core/secret-redactor.js"() {
5466
5527
  "use strict";
@@ -5598,6 +5659,7 @@ var init_secret_redactor = __esm({
5598
5659
  MAX_PACK_RULES = 200;
5599
5660
  MAX_PATTERN_LEN = 512;
5600
5661
  serverPack = null;
5662
+ globalRegexCache = /* @__PURE__ */ new Map();
5601
5663
  privacyCache = null;
5602
5664
  }
5603
5665
  });
@@ -6357,8 +6419,8 @@ function decodeDirName(dirName) {
6357
6419
  }
6358
6420
  for (let len = 2; len <= segments.length - i; len++) {
6359
6421
  const joinedSegments = segments.slice(i, i + len);
6360
- for (const sep2 of ["-", "_", "."]) {
6361
- const candidate = join5(currentPath, joinedSegments.join(sep2));
6422
+ for (const sep4 of ["-", "_", "."]) {
6423
+ const candidate = join5(currentPath, joinedSegments.join(sep4));
6362
6424
  if (existsSync5(candidate)) {
6363
6425
  currentPath = candidate;
6364
6426
  i += len;
@@ -7110,7 +7172,7 @@ async function archiveRawSession(store, sessionId) {
7110
7172
  }
7111
7173
  function detectForkPredecessor(sessionPath) {
7112
7174
  const { readFileSync: readFileSync41, readdirSync: readdirSync31 } = requireFs();
7113
- const { dirname: dirname21, join: join48, basename: basename26 } = requirePath();
7175
+ const { dirname: dirname22, join: join50, basename: basename26 } = requirePath();
7114
7176
  let text;
7115
7177
  try {
7116
7178
  text = readFileSync41(sessionPath, "utf-8");
@@ -7135,7 +7197,7 @@ function detectForkPredecessor(sessionPath) {
7135
7197
  return null;
7136
7198
  if (text.includes(`"uuid":"${headParent}"`))
7137
7199
  return null;
7138
- const dir = dirname21(sessionPath);
7200
+ const dir = dirname22(sessionPath);
7139
7201
  const self = basename26(sessionPath);
7140
7202
  let entries;
7141
7203
  try {
@@ -7147,7 +7209,7 @@ function detectForkPredecessor(sessionPath) {
7147
7209
  if (!f.endsWith(".jsonl") || f === self || f === "sessions-index.json")
7148
7210
  continue;
7149
7211
  try {
7150
- if (readFileSync41(join48(dir, f), "utf-8").includes(`"uuid":"${headParent}"`)) {
7212
+ if (readFileSync41(join50(dir, f), "utf-8").includes(`"uuid":"${headParent}"`)) {
7151
7213
  return basename26(f, ".jsonl");
7152
7214
  }
7153
7215
  } catch {
@@ -7351,18 +7413,43 @@ function updateShadow(sessionId, exp) {
7351
7413
  path
7352
7414
  };
7353
7415
  }
7354
- async function snapshotShadow(sessionId) {
7416
+ async function snapshotShadow(sessionId, opts = {}) {
7355
7417
  await Promise.resolve().then(() => (init_backends(), backends_exports));
7356
7418
  const { getBackendForId: getBackendForId2, getBackend: getBackend2 } = await Promise.resolve().then(() => (init_tool_backend(), tool_backend_exports));
7357
7419
  const backend = getBackendForId2(sessionId) ?? getBackend2("claude");
7358
7420
  const rawId = backend.toRawId(sessionId);
7421
+ const fp = liveFingerprint(backend, rawId);
7422
+ if (!opts.force && fp && lastLiveFingerprint.get(rawId) === fp) {
7423
+ return {
7424
+ status: "skipped",
7425
+ sessionId,
7426
+ tool: backend.id,
7427
+ container: null,
7428
+ recovered: 0,
7429
+ path: shadowFileFor(backend.id, rawId)
7430
+ };
7431
+ }
7359
7432
  let exp = null;
7360
7433
  try {
7361
7434
  exp = backend.exportRawSession(rawId);
7362
7435
  } catch {
7363
7436
  exp = null;
7364
7437
  }
7365
- return updateShadow(rawId, exp);
7438
+ const result = updateShadow(rawId, exp);
7439
+ if (fp && result.status !== "unavailable")
7440
+ lastLiveFingerprint.set(rawId, fp);
7441
+ return result;
7442
+ }
7443
+ function liveFingerprint(backend, rawId) {
7444
+ try {
7445
+ const loc = backend.findSession(rawId);
7446
+ if (!loc?.path)
7447
+ return "";
7448
+ const st = statSync5(loc.path);
7449
+ return `${st.mtimeMs}:${st.size}`;
7450
+ } catch {
7451
+ return "";
7452
+ }
7366
7453
  }
7367
7454
  function seedShadow(sessionId, container) {
7368
7455
  const tool = container.tool;
@@ -7381,7 +7468,7 @@ function shadowSizeOnDisk(tool, rawId) {
7381
7468
  return 0;
7382
7469
  }
7383
7470
  }
7384
- var WHOLE_FILE_TOOLS, SINGLETON_META;
7471
+ var WHOLE_FILE_TOOLS, SINGLETON_META, lastLiveFingerprint;
7385
7472
  var init_shadow = __esm({
7386
7473
  "../engine/dist/transcript/shadow.js"() {
7387
7474
  "use strict";
@@ -7389,6 +7476,27 @@ var init_shadow = __esm({
7389
7476
  init_raw();
7390
7477
  WHOLE_FILE_TOOLS = /* @__PURE__ */ new Set(["gemini"]);
7391
7478
  SINGLETON_META = /* @__PURE__ */ new Set(["mode", "permission-mode", "ai-title", "last-prompt", "pr-link", "summary"]);
7479
+ lastLiveFingerprint = /* @__PURE__ */ new Map();
7480
+ }
7481
+ });
7482
+
7483
+ // ../engine/dist/core/project-dir-name.js
7484
+ function looksWindowsEncoded(dirName) {
7485
+ return /^[A-Za-z]--/.test(dirName);
7486
+ }
7487
+ function decodeProjectDirName(dirName) {
7488
+ if (!dirName)
7489
+ return "";
7490
+ if (looksWindowsEncoded(dirName)) {
7491
+ const drive = dirName[0].toUpperCase();
7492
+ const rest = dirName.slice(3).replace(/-/g, "\\");
7493
+ return `${drive}:\\${rest}`;
7494
+ }
7495
+ return "/" + dirName.replace(/^-/, "").replace(/-/g, "/");
7496
+ }
7497
+ var init_project_dir_name = __esm({
7498
+ "../engine/dist/core/project-dir-name.js"() {
7499
+ "use strict";
7392
7500
  }
7393
7501
  });
7394
7502
 
@@ -7415,7 +7523,7 @@ function findSessionFile(sessionId) {
7415
7523
  path: candidate,
7416
7524
  projectDir: entry.name,
7417
7525
  // Project path is encoded by replacing slashes with dashes.
7418
- projectPath: entry.name.replace(/-/g, "/").replace(/^\//, "/")
7526
+ projectPath: decodeProjectDirName(entry.name)
7419
7527
  };
7420
7528
  }
7421
7529
  }
@@ -7444,7 +7552,7 @@ function findSessionFiles(sessionId) {
7444
7552
  out.push({
7445
7553
  path: candidate,
7446
7554
  projectDir: entry.name,
7447
- projectPath: entry.name.replace(/-/g, "/").replace(/^\//, "/")
7555
+ projectPath: decodeProjectDirName(entry.name)
7448
7556
  });
7449
7557
  }
7450
7558
  }
@@ -7468,7 +7576,31 @@ function resolveSessionContentGroups(sessionId) {
7468
7576
  const names = [...groups.keys()].sort((a, b) => a === "main" ? -1 : b === "main" ? 1 : a.localeCompare(b));
7469
7577
  return names.map((name) => ({ name, paths: groups.get(name) }));
7470
7578
  }
7579
+ function withSessionReadCache(fn) {
7580
+ readScopeDepth++;
7581
+ try {
7582
+ return fn();
7583
+ } finally {
7584
+ if (--readScopeDepth === 0)
7585
+ readScope = null;
7586
+ }
7587
+ }
7471
7588
  function readSessionGroupText(group) {
7589
+ let key = "";
7590
+ if (readScopeDepth > 0) {
7591
+ const parts = [];
7592
+ for (const p of group.paths) {
7593
+ try {
7594
+ const st = statSync6(p);
7595
+ parts.push(`${p}:${st.mtimeMs}:${st.size}`);
7596
+ } catch {
7597
+ parts.push(`${p}:missing`);
7598
+ }
7599
+ }
7600
+ key = parts.join("|");
7601
+ if (readScope && readScope.key === key)
7602
+ return readScope.value;
7603
+ }
7472
7604
  let text = "";
7473
7605
  let mtime = 0;
7474
7606
  for (const p of group.paths) {
@@ -7484,7 +7616,10 @@ function readSessionGroupText(group) {
7484
7616
  }
7485
7617
  text = text ? mergeLineText(text, raw).text : raw;
7486
7618
  }
7487
- return { text, mtime };
7619
+ const out = { text, mtime };
7620
+ if (readScopeDepth > 0 && key)
7621
+ readScope = { key, value: out };
7622
+ return out;
7488
7623
  }
7489
7624
  function resolveSessionContentPaths(sessionFile) {
7490
7625
  if (!hasSubagentsDir(sessionFile))
@@ -7707,7 +7842,7 @@ function findCodexSessionFileIn(root, sessionId) {
7707
7842
  }
7708
7843
  return scanDir(root);
7709
7844
  }
7710
- var geminiProjectMapCache, MAX_EVENT_LINE_BYTES;
7845
+ var readScope, readScopeDepth, geminiProjectMapCache, MAX_EVENT_LINE_BYTES;
7711
7846
  var init_live_session_scan = __esm({
7712
7847
  "../engine/dist/core/live-session-scan.js"() {
7713
7848
  "use strict";
@@ -7716,6 +7851,9 @@ var init_live_session_scan = __esm({
7716
7851
  init_tool_backend();
7717
7852
  init_tool_paths();
7718
7853
  init_backends();
7854
+ init_project_dir_name();
7855
+ readScope = null;
7856
+ readScopeDepth = 0;
7719
7857
  geminiProjectMapCache = null;
7720
7858
  MAX_EVENT_LINE_BYTES = 8 * 1024 * 1024;
7721
7859
  }
@@ -8176,74 +8314,123 @@ var init_session_outcome = __esm({
8176
8314
  }
8177
8315
  });
8178
8316
 
8317
+ // ../engine/dist/core/flat-string.js
8318
+ function flatString(s) {
8319
+ if (!s)
8320
+ return "";
8321
+ if (s.length < 13)
8322
+ return s;
8323
+ return Buffer.from(s, "utf8").toString("utf8");
8324
+ }
8325
+ var init_flat_string = __esm({
8326
+ "../engine/dist/core/flat-string.js"() {
8327
+ "use strict";
8328
+ }
8329
+ });
8330
+
8179
8331
  // ../engine/dist/core/first-prompt.js
8180
- import { createReadStream as createReadStream2, existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
8332
+ import { createReadStream as createReadStream2, existsSync as existsSync9, openSync as openSync3, readSync as readSync3, closeSync as closeSync3 } from "fs";
8333
+ import { StringDecoder } from "string_decoder";
8181
8334
  function stripBanners2(s) {
8182
8335
  let out = s;
8183
8336
  for (const re of BANNERS)
8184
8337
  out = out.replace(re, " ");
8185
8338
  return out.replace(/[ \t]{2,}/g, " ").trim();
8186
8339
  }
8340
+ function promptFromLine(raw, maxLength) {
8341
+ if (!raw.trim())
8342
+ return null;
8343
+ let obj;
8344
+ try {
8345
+ obj = JSON.parse(raw);
8346
+ } catch {
8347
+ return null;
8348
+ }
8349
+ if (obj.type !== "user")
8350
+ return null;
8351
+ if (obj.isSidechain === true)
8352
+ return null;
8353
+ const message = obj.message;
8354
+ if (!message)
8355
+ return null;
8356
+ const content = message.content;
8357
+ let text = "";
8358
+ if (typeof content === "string") {
8359
+ text = content;
8360
+ } else if (Array.isArray(content)) {
8361
+ const parts = [];
8362
+ for (const block of content) {
8363
+ if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
8364
+ parts.push(block.text);
8365
+ }
8366
+ }
8367
+ text = parts.join("\n");
8368
+ }
8369
+ if (!text)
8370
+ return null;
8371
+ if (text.includes("<system-reminder>"))
8372
+ return null;
8373
+ if (text.trim() === "[Request interrupted by user]")
8374
+ return null;
8375
+ const cleaned = stripBanners2(text);
8376
+ if (cleaned.length < 10)
8377
+ return null;
8378
+ return flatString(cleaned.slice(0, maxLength));
8379
+ }
8187
8380
  function extractFirstUserPromptSync(filePath, options = {}) {
8188
8381
  const maxLines = options.maxLines ?? 500;
8189
8382
  const maxLength = options.maxLength ?? 1e3;
8190
- if (!existsSync9(filePath))
8191
- return "";
8192
- let raw;
8383
+ const maxBytes = options.maxBytes ?? 4 * 1024 * 1024;
8384
+ let fd;
8193
8385
  try {
8194
- raw = readFileSync5(filePath, "utf-8");
8386
+ fd = openSync3(filePath, "r");
8195
8387
  } catch {
8196
8388
  return "";
8197
8389
  }
8198
- const lines = raw.split("\n");
8199
- const upTo = Math.min(lines.length, maxLines);
8200
- for (let i = 0; i < upTo; i++) {
8201
- const line = lines[i];
8202
- if (!line.trim())
8203
- continue;
8204
- let obj;
8390
+ try {
8391
+ const CHUNK = 64 * 1024;
8392
+ const buf = Buffer.allocUnsafe(CHUNK);
8393
+ const decoder = new StringDecoder("utf-8");
8394
+ let carry = "";
8395
+ let pos = 0;
8396
+ let lineNo = 0;
8397
+ for (; ; ) {
8398
+ const n = readSync3(fd, buf, 0, CHUNK, pos);
8399
+ if (n <= 0)
8400
+ break;
8401
+ pos += n;
8402
+ carry += decoder.write(buf.subarray(0, n));
8403
+ const parts = carry.split("\n");
8404
+ carry = parts.pop() ?? "";
8405
+ for (const line of parts) {
8406
+ if (++lineNo > maxLines)
8407
+ return "";
8408
+ const found = promptFromLine(line, maxLength);
8409
+ if (found !== null)
8410
+ return found;
8411
+ }
8412
+ if (pos >= maxBytes)
8413
+ return "";
8414
+ }
8415
+ carry += decoder.end();
8416
+ if (carry && lineNo < maxLines) {
8417
+ const found = promptFromLine(carry, maxLength);
8418
+ if (found !== null)
8419
+ return found;
8420
+ }
8421
+ return "";
8422
+ } finally {
8205
8423
  try {
8206
- obj = JSON.parse(line);
8424
+ closeSync3(fd);
8207
8425
  } catch {
8208
- continue;
8209
- }
8210
- if (obj.type !== "user")
8211
- continue;
8212
- if (obj.isSidechain === true)
8213
- continue;
8214
- const message = obj.message;
8215
- if (!message)
8216
- continue;
8217
- const content = message.content;
8218
- let text = "";
8219
- if (typeof content === "string") {
8220
- text = content;
8221
- } else if (Array.isArray(content)) {
8222
- const parts = [];
8223
- for (const block of content) {
8224
- if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
8225
- parts.push(block.text);
8226
- }
8227
- }
8228
- text = parts.join("\n");
8229
8426
  }
8230
- if (!text)
8231
- continue;
8232
- if (text.includes("<system-reminder>"))
8233
- continue;
8234
- if (text.trim() === "[Request interrupted by user]")
8235
- continue;
8236
- const cleaned = stripBanners2(text);
8237
- if (cleaned.length < 10)
8238
- continue;
8239
- return cleaned.slice(0, maxLength);
8240
8427
  }
8241
- return "";
8242
8428
  }
8243
8429
  var BANNERS;
8244
8430
  var init_first_prompt = __esm({
8245
8431
  "../engine/dist/core/first-prompt.js"() {
8246
8432
  "use strict";
8433
+ init_flat_string();
8247
8434
  BANNERS = [
8248
8435
  /MCP issues detected\. ?Run \/mcp list for status\.?/g,
8249
8436
  /Context low[^\n]*Run \/compact[^\n]*/g,
@@ -8253,7 +8440,7 @@ var init_first_prompt = __esm({
8253
8440
  });
8254
8441
 
8255
8442
  // ../engine/dist/core/backends/tail-read.js
8256
- import { openSync as openSync3, readSync as readSync3, closeSync as closeSync3, statSync as statSync7 } from "node:fs";
8443
+ import { openSync as openSync4, readSync as readSync4, closeSync as closeSync4, statSync as statSync7 } from "node:fs";
8257
8444
  function readTailFromOffset(path, offset) {
8258
8445
  let size = 0;
8259
8446
  try {
@@ -8267,10 +8454,10 @@ function readTailFromOffset(path, offset) {
8267
8454
  const buf = Buffer.allocUnsafe(len);
8268
8455
  let fd = null;
8269
8456
  try {
8270
- fd = openSync3(path, "r");
8457
+ fd = openSync4(path, "r");
8271
8458
  let got = 0;
8272
8459
  while (got < len) {
8273
- const n = readSync3(fd, buf, got, len - got, offset + got);
8460
+ const n = readSync4(fd, buf, got, len - got, offset + got);
8274
8461
  if (n <= 0)
8275
8462
  break;
8276
8463
  got += n;
@@ -8289,7 +8476,7 @@ function readTailFromOffset(path, offset) {
8289
8476
  } finally {
8290
8477
  if (fd !== null) {
8291
8478
  try {
8292
- closeSync3(fd);
8479
+ closeSync4(fd);
8293
8480
  } catch {
8294
8481
  }
8295
8482
  }
@@ -8574,7 +8761,7 @@ var init_generic_engine = __esm({
8574
8761
  });
8575
8762
 
8576
8763
  // ../engine/dist/core/backends/claude.js
8577
- import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync6, statSync as statSync8, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
8764
+ import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync5, statSync as statSync8, openSync as openSync5, readSync as readSync5, closeSync as closeSync5 } from "fs";
8578
8765
  import { join as join10, basename as basename8 } from "path";
8579
8766
  function stripReminders(text) {
8580
8767
  return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
@@ -8593,6 +8780,7 @@ var init_claude2 = __esm({
8593
8780
  init_session_git();
8594
8781
  init_first_prompt();
8595
8782
  init_tail_read();
8783
+ init_project_dir_name();
8596
8784
  init_generic_engine();
8597
8785
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8598
8786
  ClaudeBackend = class {
@@ -8669,13 +8857,13 @@ var init_claude2 = __esm({
8669
8857
  return null;
8670
8858
  let head;
8671
8859
  try {
8672
- const fd = openSync4(located.path, "r");
8860
+ const fd = openSync5(located.path, "r");
8673
8861
  try {
8674
8862
  const buf = Buffer.alloc(65536);
8675
- const n = readSync4(fd, buf, 0, buf.length, 0);
8863
+ const n = readSync5(fd, buf, 0, buf.length, 0);
8676
8864
  head = buf.toString("utf-8", 0, n);
8677
8865
  } finally {
8678
- closeSync4(fd);
8866
+ closeSync5(fd);
8679
8867
  }
8680
8868
  } catch {
8681
8869
  return null;
@@ -8734,11 +8922,11 @@ var init_claude2 = __esm({
8734
8922
  if (filter && !proj.name.toLowerCase().includes(filter))
8735
8923
  continue;
8736
8924
  const projPath = join10(root, proj.name);
8737
- const projectPath = proj.name.replace(/-/g, "/").replace(/^\//, "/");
8925
+ const projectPath = decodeProjectDirName(proj.name);
8738
8926
  const indexPath = join10(projPath, "sessions-index.json");
8739
8927
  if (existsSync10(indexPath)) {
8740
8928
  try {
8741
- const indexData = JSON.parse(readFileSync6(indexPath, "utf-8"));
8929
+ const indexData = JSON.parse(readFileSync5(indexPath, "utf-8"));
8742
8930
  const entries = indexData.entries || [];
8743
8931
  for (const entry of entries) {
8744
8932
  if (!entry.fullPath || !existsSync10(entry.fullPath))
@@ -9030,7 +9218,7 @@ var init_claude2 = __esm({
9030
9218
  * with quiet main + active subagents still surfaces.
9031
9219
  */
9032
9220
  collectRecentEdits(opts) {
9033
- const candidates = [];
9221
+ const candidates2 = [];
9034
9222
  for (const root of claudeProjectDirs()) {
9035
9223
  if (!existsSync10(root))
9036
9224
  continue;
@@ -9066,14 +9254,14 @@ var init_claude2 = __esm({
9066
9254
  }
9067
9255
  if (mtime < opts.sinceMs)
9068
9256
  continue;
9069
- candidates.push({ rawId: basename8(f, ".jsonl"), mtime });
9257
+ candidates2.push({ rawId: basename8(f, ".jsonl"), mtime });
9070
9258
  } catch {
9071
9259
  }
9072
9260
  }
9073
9261
  }
9074
9262
  }
9075
- candidates.sort((a, b) => b.mtime - a.mtime);
9076
- const limited = opts.limitSessions ? candidates.slice(0, opts.limitSessions) : candidates;
9263
+ candidates2.sort((a, b) => b.mtime - a.mtime);
9264
+ const limited = opts.limitSessions ? candidates2.slice(0, opts.limitSessions) : candidates2;
9077
9265
  const edits = [];
9078
9266
  for (const c of limited) {
9079
9267
  const scan = this.liveScanEdits(c.rawId);
@@ -9160,12 +9348,12 @@ var init_claude2 = __esm({
9160
9348
 
9161
9349
  // ../engine/dist/core/backends/gemini.js
9162
9350
  import { createHash as createHash4 } from "crypto";
9163
- import { existsSync as existsSync11, readdirSync as readdirSync7, readFileSync as readFileSync7, statSync as statSync9 } from "fs";
9351
+ import { existsSync as existsSync11, readdirSync as readdirSync7, readFileSync as readFileSync6, statSync as statSync9 } from "fs";
9164
9352
  import { basename as basename9, join as join11 } from "path";
9165
9353
  function readSessionId(path, format) {
9166
9354
  try {
9167
9355
  if (format === "json") {
9168
- const json = JSON.parse(readFileSync7(path, "utf-8"));
9356
+ const json = JSON.parse(readFileSync6(path, "utf-8"));
9169
9357
  return typeof json?.sessionId === "string" ? json.sessionId : null;
9170
9358
  }
9171
9359
  const head = readHeadSync(path).split("\n", 1)[0] || "";
@@ -9199,6 +9387,7 @@ var init_gemini2 = __esm({
9199
9387
  init_session_git();
9200
9388
  init_generic_engine();
9201
9389
  init_tail_read();
9390
+ init_flat_string();
9202
9391
  PREFIX = "gemini_";
9203
9392
  FULL_PARSE_MAX = 4 * 1024 * 1024;
9204
9393
  GeminiBackend = class {
@@ -9308,7 +9497,7 @@ var init_gemini2 = __esm({
9308
9497
  sessionId = readSessionId(fullPath, format);
9309
9498
  const messages = readGeminiMessages(fullPath, format);
9310
9499
  const firstUser = messages.find((m) => m.type === "user");
9311
- firstPrompt = (typeof firstUser?.text === "string" ? firstUser.text : flatten(firstUser?.content)).slice(0, 200);
9500
+ firstPrompt = flatString((typeof firstUser?.text === "string" ? firstUser.text : flatten(firstUser?.content)).slice(0, 200));
9312
9501
  messageCount = messages.length;
9313
9502
  } else if (format === "jsonl") {
9314
9503
  const lines = readHeadSync(fullPath, 1048576).split("\n");
@@ -9330,7 +9519,7 @@ var init_gemini2 = __esm({
9330
9519
  }
9331
9520
  if (m?.type !== "user")
9332
9521
  continue;
9333
- firstPrompt = (typeof m.text === "string" ? m.text : flatten(m.content)).slice(0, 200);
9522
+ firstPrompt = flatString((typeof m.text === "string" ? m.text : flatten(m.content)).slice(0, 200));
9334
9523
  break;
9335
9524
  }
9336
9525
  }
@@ -9506,7 +9695,7 @@ var init_gemini2 = __esm({
9506
9695
  if (!existsSync11(tmpRoot))
9507
9696
  return [];
9508
9697
  const projMap = this.loadProjectMap();
9509
- const candidates = [];
9698
+ const candidates2 = [];
9510
9699
  for (const proj of readdirSync7(tmpRoot, { withFileTypes: true })) {
9511
9700
  if (!proj.isDirectory())
9512
9701
  continue;
@@ -9537,13 +9726,13 @@ var init_gemini2 = __esm({
9537
9726
  if (st.mtimeMs < opts.sinceMs)
9538
9727
  continue;
9539
9728
  const trimLen = isJsonl ? 6 : 5;
9540
- candidates.push({ rawIdHint: f.slice(0, -trimLen), mtime: st.mtimeMs });
9729
+ candidates2.push({ rawIdHint: f.slice(0, -trimLen), mtime: st.mtimeMs });
9541
9730
  } catch {
9542
9731
  }
9543
9732
  }
9544
9733
  }
9545
- candidates.sort((a, b) => b.mtime - a.mtime);
9546
- const limited = opts.limitSessions ? candidates.slice(0, opts.limitSessions) : candidates;
9734
+ candidates2.sort((a, b) => b.mtime - a.mtime);
9735
+ const limited = opts.limitSessions ? candidates2.slice(0, opts.limitSessions) : candidates2;
9547
9736
  const edits = [];
9548
9737
  for (const c of limited) {
9549
9738
  const scan = this.liveScanEdits(c.rawIdHint);
@@ -9562,7 +9751,7 @@ var init_gemini2 = __esm({
9562
9751
  if (!existsSync11(path))
9563
9752
  return map;
9564
9753
  try {
9565
- const json = JSON.parse(readFileSync7(path, "utf-8"));
9754
+ const json = JSON.parse(readFileSync6(path, "utf-8"));
9566
9755
  const projects = json?.projects ?? {};
9567
9756
  for (const projectPath of Object.keys(projects)) {
9568
9757
  const hash = createHash4("sha256").update(projectPath).digest("hex");
@@ -9580,7 +9769,7 @@ var init_gemini2 = __esm({
9580
9769
  return {
9581
9770
  tool: "gemini",
9582
9771
  mtime: statSync9(loc.path).mtimeMs,
9583
- files: [{ name: basename9(loc.path), bytes: readFileSync7(loc.path) }]
9772
+ files: [{ name: basename9(loc.path), bytes: readFileSync6(loc.path) }]
9584
9773
  };
9585
9774
  } catch {
9586
9775
  return null;
@@ -9675,6 +9864,7 @@ var init_opencode = __esm({
9675
9864
  init_session_outcome();
9676
9865
  init_session_git();
9677
9866
  init_generic_engine();
9867
+ init_flat_string();
9678
9868
  PREFIX2 = "opencode_";
9679
9869
  OpencodeBackend = class {
9680
9870
  id = "opencode";
@@ -9842,7 +10032,7 @@ var init_opencode = __esm({
9842
10032
  if (firstUser) {
9843
10033
  try {
9844
10034
  const parsed = JSON.parse(firstUser.data);
9845
- firstPrompt = String(parsed?.text || "").slice(0, 200);
10035
+ firstPrompt = flatString(String(parsed?.text || "").slice(0, 200));
9846
10036
  } catch {
9847
10037
  }
9848
10038
  }
@@ -10186,7 +10376,7 @@ var init_opencode = __esm({
10186
10376
  });
10187
10377
 
10188
10378
  // ../engine/dist/core/backends/codex.js
10189
- import { readdirSync as readdirSync8, readFileSync as readFileSync8, statSync as statSync10, openSync as openSync5, readSync as readSync5, closeSync as closeSync5 } from "fs";
10379
+ import { readdirSync as readdirSync8, readFileSync as readFileSync7, statSync as statSync10, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
10190
10380
  import { basename as basename10, dirname as dirname10, join as join13 } from "path";
10191
10381
  function parseShellWrites(cmd) {
10192
10382
  const out = [];
@@ -10315,7 +10505,7 @@ function flattenContent(content) {
10315
10505
  }
10316
10506
  function readFirstLine(path) {
10317
10507
  try {
10318
- const head = readFileSync8(path, "utf-8").split("\n", 1)[0] || "";
10508
+ const head = readFileSync7(path, "utf-8").split("\n", 1)[0] || "";
10319
10509
  if (!head)
10320
10510
  return null;
10321
10511
  return JSON.parse(head);
@@ -10333,6 +10523,7 @@ var init_codex2 = __esm({
10333
10523
  init_session_git();
10334
10524
  init_generic_engine();
10335
10525
  init_tail_read();
10526
+ init_flat_string();
10336
10527
  PREFIX3 = "codex_";
10337
10528
  CodexBackend = class {
10338
10529
  id = "codex";
@@ -10432,7 +10623,7 @@ var init_codex2 = __esm({
10432
10623
  let firstPrompt = "";
10433
10624
  let messageCount = 0;
10434
10625
  try {
10435
- const lines = readFileSync8(filePath, "utf-8").split("\n");
10626
+ const lines = readFileSync7(filePath, "utf-8").split("\n");
10436
10627
  for (const line of lines) {
10437
10628
  if (!line.trim())
10438
10629
  continue;
@@ -10445,7 +10636,7 @@ var init_codex2 = __esm({
10445
10636
  if (ev.type === "event_msg" && ev.payload?.type === "user_message") {
10446
10637
  messageCount++;
10447
10638
  if (!firstPrompt && ev.payload.message) {
10448
- firstPrompt = String(ev.payload.message).slice(0, 200);
10639
+ firstPrompt = flatString(String(ev.payload.message).slice(0, 200));
10449
10640
  }
10450
10641
  } else if (ev.type === "response_item" && ev.payload?.type === "message") {
10451
10642
  messageCount++;
@@ -10584,7 +10775,7 @@ var init_codex2 = __esm({
10584
10775
  continue;
10585
10776
  let firstLine = "";
10586
10777
  try {
10587
- firstLine = readFileSync8(path, "utf-8").split("\n", 1)[0] || "";
10778
+ firstLine = readFileSync7(path, "utf-8").split("\n", 1)[0] || "";
10588
10779
  } catch {
10589
10780
  continue;
10590
10781
  }
@@ -10608,7 +10799,7 @@ var init_codex2 = __esm({
10608
10799
  let raw = "";
10609
10800
  let mtime = 0;
10610
10801
  try {
10611
- raw = readFileSync8(path, "utf-8");
10802
+ raw = readFileSync7(path, "utf-8");
10612
10803
  mtime = statSync10(path).mtimeMs;
10613
10804
  } catch {
10614
10805
  return [];
@@ -10785,7 +10976,7 @@ var init_codex2 = __esm({
10785
10976
  let parentId = null;
10786
10977
  let isSubagent = false;
10787
10978
  try {
10788
- const first = readFileSync8(p, "utf-8").split("\n")[0];
10979
+ const first = readFileSync7(p, "utf-8").split("\n")[0];
10789
10980
  if (first) {
10790
10981
  const meta = JSON.parse(first);
10791
10982
  const payload = meta?.payload || {};
@@ -10841,7 +11032,7 @@ var init_codex2 = __esm({
10841
11032
  const push = (path, name) => {
10842
11033
  try {
10843
11034
  const st = statSync10(path);
10844
- files.push({ name, bytes: readFileSync8(path) });
11035
+ files.push({ name, bytes: readFileSync7(path) });
10845
11036
  if (st.mtimeMs > mtime)
10846
11037
  mtime = st.mtimeMs;
10847
11038
  } catch {
@@ -10859,10 +11050,10 @@ var init_codex2 = __esm({
10859
11050
  if (p === loc.path)
10860
11051
  continue;
10861
11052
  try {
10862
- const fd = openSync5(p, "r");
11053
+ const fd = openSync6(p, "r");
10863
11054
  const buf = Buffer.alloc(4096);
10864
- const n = readSync5(fd, buf, 0, 4096, 0);
10865
- closeSync5(fd);
11055
+ const n = readSync6(fd, buf, 0, 4096, 0);
11056
+ closeSync6(fd);
10866
11057
  const head = buf.toString("utf-8", 0, n);
10867
11058
  if (head.includes(parentId) && head.includes("parent_thread_id"))
10868
11059
  push(p, `subagents/${f}`);
@@ -10899,13 +11090,13 @@ var init_codex2 = __esm({
10899
11090
  });
10900
11091
 
10901
11092
  // ../engine/dist/core/backends/agy.js
10902
- import { existsSync as existsSync13, readdirSync as readdirSync9, readFileSync as readFileSync9, statSync as statSync11 } from "fs";
11093
+ import { existsSync as existsSync13, readdirSync as readdirSync9, readFileSync as readFileSync8, statSync as statSync11 } from "fs";
10903
11094
  import { basename as basename11, dirname as dirname11, join as join14 } from "path";
10904
11095
  function getFallbackProjectPath(homeDir) {
10905
11096
  try {
10906
11097
  const settingsPath2 = join14(homeDir, "settings.json");
10907
11098
  if (existsSync13(settingsPath2)) {
10908
- const json = JSON.parse(readFileSync9(settingsPath2, "utf-8"));
11099
+ const json = JSON.parse(readFileSync8(settingsPath2, "utf-8"));
10909
11100
  if (Array.isArray(json?.trustedWorkspaces) && json.trustedWorkspaces[0]) {
10910
11101
  return json.trustedWorkspaces[0];
10911
11102
  }
@@ -10932,7 +11123,7 @@ function resolveAgyProject(logsDir, homeDir) {
10932
11123
  function extractProjectPathFromTranscript(path, homeDir) {
10933
11124
  let content;
10934
11125
  try {
10935
- content = readFileSync9(path, "utf-8");
11126
+ content = readFileSync8(path, "utf-8");
10936
11127
  } catch {
10937
11128
  return "";
10938
11129
  }
@@ -11018,6 +11209,7 @@ var init_agy = __esm({
11018
11209
  init_session_outcome();
11019
11210
  init_session_git();
11020
11211
  init_generic_engine();
11212
+ init_flat_string();
11021
11213
  PREFIX4 = "agy_";
11022
11214
  AgyBackend = class {
11023
11215
  id = "agy";
@@ -11110,7 +11302,7 @@ var init_agy = __esm({
11110
11302
  let firstPrompt = "";
11111
11303
  let messageCount = 0;
11112
11304
  try {
11113
- const lines = readFileSync9(filePath, "utf-8").split("\n").filter(Boolean);
11305
+ const lines = readFileSync8(filePath, "utf-8").split("\n").filter(Boolean);
11114
11306
  messageCount = lines.length;
11115
11307
  for (const line of lines) {
11116
11308
  const obj = JSON.parse(line);
@@ -11118,7 +11310,7 @@ var init_agy = __esm({
11118
11310
  const content = obj.content;
11119
11311
  const match = content.match(/<USER_REQUEST>([\s\S]*?)<\/USER_REQUEST>/);
11120
11312
  const rawText = match ? match[1].trim() : content.trim();
11121
- firstPrompt = rawText.slice(0, 200);
11313
+ firstPrompt = flatString(rawText.slice(0, 200));
11122
11314
  break;
11123
11315
  }
11124
11316
  }
@@ -11200,7 +11392,7 @@ var init_agy = __esm({
11200
11392
  return [];
11201
11393
  let rawText;
11202
11394
  try {
11203
- rawText = readFileSync9(located.path, "utf-8");
11395
+ rawText = readFileSync8(located.path, "utf-8");
11204
11396
  } catch {
11205
11397
  return [];
11206
11398
  }
@@ -11352,7 +11544,7 @@ var init_agy = __esm({
11352
11544
  if (brainRoots.length === 0)
11353
11545
  return [];
11354
11546
  const brainDir = brainRoots[0];
11355
- const candidates = [];
11547
+ const candidates2 = [];
11356
11548
  let sessionDirs;
11357
11549
  try {
11358
11550
  sessionDirs = readdirSync9(brainDir);
@@ -11374,12 +11566,12 @@ var init_agy = __esm({
11374
11566
  const st = statSync11(filePath);
11375
11567
  if (st.mtimeMs < opts.sinceMs)
11376
11568
  continue;
11377
- candidates.push({ rawId, mtime: st.mtimeMs });
11569
+ candidates2.push({ rawId, mtime: st.mtimeMs });
11378
11570
  } catch {
11379
11571
  }
11380
11572
  }
11381
- candidates.sort((a, b) => b.mtime - a.mtime);
11382
- const limited = opts.limitSessions ? candidates.slice(0, opts.limitSessions) : candidates;
11573
+ candidates2.sort((a, b) => b.mtime - a.mtime);
11574
+ const limited = opts.limitSessions ? candidates2.slice(0, opts.limitSessions) : candidates2;
11383
11575
  const edits = [];
11384
11576
  for (const c of limited) {
11385
11577
  const scan = this.liveScanEdits(c.rawId);
@@ -11400,7 +11592,7 @@ var init_agy = __esm({
11400
11592
  return {
11401
11593
  tool: "agy",
11402
11594
  mtime: statSync11(loc.path).mtimeMs,
11403
- files: [{ name: basename11(loc.path), bytes: readFileSync9(loc.path) }]
11595
+ files: [{ name: basename11(loc.path), bytes: readFileSync8(loc.path) }]
11404
11596
  };
11405
11597
  } catch {
11406
11598
  return null;
@@ -11472,6 +11664,24 @@ var init_backends = __esm({
11472
11664
  }
11473
11665
  });
11474
11666
 
11667
+ // ../engine/dist/core/credentials-env.js
11668
+ function envCredentials(env = process.env) {
11669
+ const token = (env.CHAT_RECALL_TOKEN || "").trim();
11670
+ const server = (env.CHAT_RECALL_SERVER || "").trim();
11671
+ if (!token || !server)
11672
+ return null;
11673
+ if (!/^https?:\/\//i.test(server))
11674
+ return null;
11675
+ return { serverUrl: normalise(server), token };
11676
+ }
11677
+ var normalise;
11678
+ var init_credentials_env = __esm({
11679
+ "../engine/dist/core/credentials-env.js"() {
11680
+ "use strict";
11681
+ normalise = (u) => u.trim().replace(/\/+$/, "");
11682
+ }
11683
+ });
11684
+
11475
11685
  // ../engine/dist/core/source-discovery.js
11476
11686
  import { createHash as createHash5 } from "crypto";
11477
11687
  import { existsSync as existsSync14, readdirSync as readdirSync10, statSync as statSync12 } from "fs";
@@ -11578,7 +11788,7 @@ var init_source_discovery = __esm({
11578
11788
 
11579
11789
  // ../engine/dist/core/home-discovery.js
11580
11790
  import { execFileSync as execFileSync2 } from "child_process";
11581
- import { existsSync as existsSync15, readdirSync as readdirSync11, readFileSync as readFileSync10, realpathSync as realpathSync2, statSync as statSync13 } from "fs";
11791
+ import { existsSync as existsSync15, readdirSync as readdirSync11, readFileSync as readFileSync9, realpathSync as realpathSync2, statSync as statSync13 } from "fs";
11582
11792
  import { homedir as homedir5, platform as osPlatform } from "os";
11583
11793
  import { join as join16 } from "path";
11584
11794
  function candidateRoots(plat = osPlatform(), env = process.env) {
@@ -11700,7 +11910,7 @@ function declaredHomes(env = process.env, plat = osPlatform()) {
11700
11910
  for (const rc of rcs) {
11701
11911
  let text;
11702
11912
  try {
11703
- text = readFileSync10(rc, "utf-8");
11913
+ text = readFileSync9(rc, "utf-8");
11704
11914
  } catch {
11705
11915
  continue;
11706
11916
  }
@@ -11725,7 +11935,7 @@ function runningToolHomes(plat = osPlatform()) {
11725
11935
  for (const pid of pids) {
11726
11936
  let env;
11727
11937
  try {
11728
- env = readFileSync10(`/proc/${pid}/environ`, "utf-8");
11938
+ env = readFileSync9(`/proc/${pid}/environ`, "utf-8");
11729
11939
  } catch {
11730
11940
  continue;
11731
11941
  }
@@ -11846,7 +12056,7 @@ var init_home_discovery = __esm({
11846
12056
 
11847
12057
  // ../engine/dist/core/detector-install.js
11848
12058
  import { createHash as createHash6 } from "crypto";
11849
- import { existsSync as existsSync16, mkdirSync as mkdirSync4, mkdtempSync, chmodSync as chmodSync2, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync11, statSync as statSync14, copyFileSync as copyFileSync2 } from "fs";
12059
+ import { existsSync as existsSync16, mkdirSync as mkdirSync4, mkdtempSync, chmodSync as chmodSync2, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync10, statSync as statSync14, copyFileSync as copyFileSync2 } from "fs";
11850
12060
  import { join as join17 } from "path";
11851
12061
  function managedDetectorPath(name) {
11852
12062
  return join17(getDetectorBinDir(), name + exeSuffix());
@@ -11855,11 +12065,11 @@ function receiptPath(name) {
11855
12065
  return join17(getDetectorBinDir(), `${name}.receipt.json`);
11856
12066
  }
11857
12067
  function sha256File(path) {
11858
- return createHash6("sha256").update(readFileSync11(path)).digest("hex");
12068
+ return createHash6("sha256").update(readFileSync10(path)).digest("hex");
11859
12069
  }
11860
12070
  function readReceipt(name) {
11861
12071
  try {
11862
- return JSON.parse(readFileSync11(receiptPath(name), "utf-8"));
12072
+ return JSON.parse(readFileSync10(receiptPath(name), "utf-8"));
11863
12073
  } catch {
11864
12074
  return null;
11865
12075
  }
@@ -11950,7 +12160,7 @@ var init_detector_install = __esm({
11950
12160
 
11951
12161
  // ../engine/dist/core/secret-scanner.js
11952
12162
  import { spawnSync } from "child_process";
11953
- import { existsSync as existsSync17, unlinkSync, readFileSync as readFileSync12, mkdtempSync as mkdtempSync2, rmSync as rmSync3 } from "fs";
12163
+ import { existsSync as existsSync17, unlinkSync, readFileSync as readFileSync11, mkdtempSync as mkdtempSync2, rmSync as rmSync3 } from "fs";
11954
12164
  import { tmpdir } from "os";
11955
12165
  import { join as join18 } from "path";
11956
12166
  function mask(s) {
@@ -12011,7 +12221,7 @@ function runGitleaks(binary, filePath) {
12011
12221
  return [];
12012
12222
  let parsed = [];
12013
12223
  try {
12014
- parsed = JSON.parse(readFileSync12(reportPath, "utf-8")) || [];
12224
+ parsed = JSON.parse(readFileSync11(reportPath, "utf-8")) || [];
12015
12225
  } catch {
12016
12226
  }
12017
12227
  try {
@@ -12075,7 +12285,7 @@ function runGitleaksDir(binary, dir) {
12075
12285
  }
12076
12286
  let parsed = [];
12077
12287
  try {
12078
- parsed = JSON.parse(readFileSync12(reportPath, "utf-8")) || [];
12288
+ parsed = JSON.parse(readFileSync11(reportPath, "utf-8")) || [];
12079
12289
  } catch {
12080
12290
  }
12081
12291
  try {
@@ -12166,7 +12376,7 @@ function scanFileForSecrets(filePath, opts = {}) {
12166
12376
  out.push({ detector: "trufflehog", ...f });
12167
12377
  if (opts.tenantRules && opts.tenantRules.length > 0) {
12168
12378
  try {
12169
- const text = readFileSync12(filePath, "utf-8");
12379
+ const text = readFileSync11(filePath, "utf-8");
12170
12380
  out.push(...scanTenantRules(text, opts.tenantRules));
12171
12381
  } catch {
12172
12382
  }
@@ -12291,10 +12501,10 @@ var init_types = __esm({
12291
12501
 
12292
12502
  // ../engine/dist/transcript/opencode.js
12293
12503
  async function parseOpenCodeSubagents(parentSessionId) {
12294
- const { existsSync: existsSync45 } = await import("fs");
12504
+ const { existsSync: existsSync46 } = await import("fs");
12295
12505
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
12296
12506
  const dbPath = opencodeBackend2.dbPath();
12297
- if (!existsSync45(dbPath))
12507
+ if (!existsSync46(dbPath))
12298
12508
  return [];
12299
12509
  const realParent = parentSessionId.replace("opencode_", "");
12300
12510
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -12335,10 +12545,10 @@ async function parseOpenCodeSubagents(parentSessionId) {
12335
12545
  }
12336
12546
  }
12337
12547
  async function parseOpenCodeTranscript(sessionId) {
12338
- const { existsSync: existsSync45 } = await import("fs");
12548
+ const { existsSync: existsSync46 } = await import("fs");
12339
12549
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
12340
12550
  const dbPath = opencodeBackend2.dbPath();
12341
- if (!existsSync45(dbPath))
12551
+ if (!existsSync46(dbPath))
12342
12552
  return [];
12343
12553
  const realId = sessionId.replace("opencode_", "");
12344
12554
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -12572,7 +12782,7 @@ var init_transcript = __esm({
12572
12782
  // ../engine/dist/core/project-resolver.js
12573
12783
  import { execSync } from "child_process";
12574
12784
  import { createHash as createHash7 } from "crypto";
12575
- import { existsSync as existsSync18, readFileSync as readFileSync13, realpathSync as realpathSync3, readdirSync as readdirSync12, statSync as statSync16 } from "fs";
12785
+ import { existsSync as existsSync18, readFileSync as readFileSync12, realpathSync as realpathSync3, readdirSync as readdirSync12, statSync as statSync16 } from "fs";
12576
12786
  import { basename as basename12, dirname as dirname12, isAbsolute, join as join19, resolve, sep } from "path";
12577
12787
  function getProjectsConfigPath() {
12578
12788
  return join19(getDataDir(), "projects.json");
@@ -12590,7 +12800,7 @@ function loadProjectsConfig() {
12590
12800
  if (cachedConfig && cachedConfig.mtime === mtime)
12591
12801
  return cachedConfig.cfg;
12592
12802
  try {
12593
- const raw = readFileSync13(path, "utf-8");
12803
+ const raw = readFileSync12(path, "utf-8");
12594
12804
  const cfg = JSON.parse(raw);
12595
12805
  cachedConfig = { mtime, cfg };
12596
12806
  return cfg;
@@ -12658,8 +12868,8 @@ function resolveBotWorktree(realPath) {
12658
12868
  };
12659
12869
  }
12660
12870
  function matchDeclaredProject(realPath, projects) {
12661
- const candidates = [...projects].sort((a, b) => b.root.length - a.root.length);
12662
- for (const proj of candidates) {
12871
+ const candidates2 = [...projects].sort((a, b) => b.root.length - a.root.length);
12872
+ for (const proj of candidates2) {
12663
12873
  const root = safeRealpath(proj.root);
12664
12874
  if (!isInside(realPath, root))
12665
12875
  continue;
@@ -13693,7 +13903,7 @@ var init_utils = __esm({
13693
13903
  });
13694
13904
 
13695
13905
  // ../engine/dist/parsers/plan-source.js
13696
- import { existsSync as existsSync20, readdirSync as readdirSync14, readFileSync as readFileSync14, statSync as statSync18 } from "fs";
13906
+ import { existsSync as existsSync20, readdirSync as readdirSync14, readFileSync as readFileSync13, statSync as statSync18 } from "fs";
13697
13907
  import { homedir as homedir7 } from "os";
13698
13908
  import { join as join21, basename as basename13 } from "path";
13699
13909
  import { createHash as createHash8 } from "crypto";
@@ -13735,6 +13945,7 @@ var init_plan_source = __esm({
13735
13945
  init_claude2();
13736
13946
  init_agy();
13737
13947
  init_settings();
13948
+ init_project_dir_name();
13738
13949
  MAX_CHUNK_CHARS = 2e3;
13739
13950
  UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13740
13951
  PlanSource = class {
@@ -13759,7 +13970,7 @@ var init_plan_source = __esm({
13759
13970
  for (const entry of entries) {
13760
13971
  if (!entry.isDirectory() || entry.name.startsWith("."))
13761
13972
  continue;
13762
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
13973
+ const projectPath = decodeProjectDirName(entry.name);
13763
13974
  if (existsSync20(projectPath)) {
13764
13975
  this._knownProjectDirs.push(projectPath);
13765
13976
  }
@@ -13797,7 +14008,7 @@ var init_plan_source = __esm({
13797
14008
  const stat3 = statSync18(filePath);
13798
14009
  const planName = basename13(file, ".md");
13799
14010
  const isAgentPlan = planName.includes("-agent-");
13800
- const content = readFileSync14(filePath, "utf-8");
14011
+ const content = readFileSync13(filePath, "utf-8");
13801
14012
  const fm = parseFrontmatter(content);
13802
14013
  const body = fm.body;
13803
14014
  const firstLine = body.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || planName;
@@ -13839,7 +14050,7 @@ var init_plan_source = __esm({
13839
14050
  const filePath = join21(plansDir, file);
13840
14051
  try {
13841
14052
  const stat3 = statSync18(filePath);
13842
- const content = readFileSync14(filePath, "utf-8");
14053
+ const content = readFileSync13(filePath, "utf-8");
13843
14054
  const firstLine = content.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || file;
13844
14055
  const planName = `${opencodeBackend.idPrefix}plan_${basename13(file, ".md")}`;
13845
14056
  const projectPath = this.extractProjectPath(content);
@@ -13869,7 +14080,7 @@ var init_plan_source = __esm({
13869
14080
  const projectsPath = geminiBackend.projectsJson();
13870
14081
  if (existsSync20(projectsPath)) {
13871
14082
  try {
13872
- const data = JSON.parse(readFileSync14(projectsPath, "utf-8"));
14083
+ const data = JSON.parse(readFileSync13(projectsPath, "utf-8"));
13873
14084
  for (const path of Object.keys(data.projects || {})) {
13874
14085
  projMap.set(createHash8("sha256").update(path).digest("hex"), path);
13875
14086
  }
@@ -13905,7 +14116,7 @@ var init_plan_source = __esm({
13905
14116
  const filePath = join21(plansDir, file);
13906
14117
  try {
13907
14118
  const stat3 = statSync18(filePath);
13908
- const content = readFileSync14(filePath, "utf-8");
14119
+ const content = readFileSync13(filePath, "utf-8");
13909
14120
  const firstLine = content.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || file;
13910
14121
  const planName = `${geminiBackend.idPrefix}plan_${sess}_${basename13(file, ".md")}`;
13911
14122
  yield {
@@ -13984,7 +14195,7 @@ var init_plan_source = __esm({
13984
14195
  async parse(item) {
13985
14196
  if (!existsSync20(item.filePath))
13986
14197
  return [];
13987
- const content = readFileSync14(item.filePath, "utf-8");
14198
+ const content = readFileSync13(item.filePath, "utf-8");
13988
14199
  const chunks = [];
13989
14200
  const { body } = parseFrontmatter(content);
13990
14201
  const sections = splitByHeaders(body);
@@ -14128,7 +14339,7 @@ var init_plan_source = __esm({
14128
14339
  const filePath = join21(sessionPath, file);
14129
14340
  try {
14130
14341
  const stat3 = statSync18(filePath);
14131
- const content = readFileSync14(filePath, "utf-8");
14342
+ const content = readFileSync13(filePath, "utf-8");
14132
14343
  const firstLine = content.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || file;
14133
14344
  const sessionLoc = agyBackend.findSession(rawId);
14134
14345
  const projectPath = sessionLoc?.projectPath || this.extractProjectPath(content);
@@ -14157,7 +14368,7 @@ var init_plan_source = __esm({
14157
14368
  });
14158
14369
 
14159
14370
  // ../engine/dist/parsers/task-source.js
14160
- import { existsSync as existsSync21, readdirSync as readdirSync15, readFileSync as readFileSync15, statSync as statSync19 } from "fs";
14371
+ import { existsSync as existsSync21, readdirSync as readdirSync15, readFileSync as readFileSync14, statSync as statSync19 } from "fs";
14161
14372
  import { join as join22 } from "path";
14162
14373
  var MAX_CHUNK_CHARS2, TaskSource;
14163
14374
  var init_task_source = __esm({
@@ -14207,7 +14418,7 @@ var init_task_source = __esm({
14207
14418
  continue;
14208
14419
  let parsed;
14209
14420
  try {
14210
- const raw = readFileSync15(filePath, "utf-8");
14421
+ const raw = readFileSync14(filePath, "utf-8");
14211
14422
  parsed = JSON.parse(raw);
14212
14423
  if (!Array.isArray(parsed) || parsed.length === 0)
14213
14424
  continue;
@@ -14345,7 +14556,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
14345
14556
  return [];
14346
14557
  let parsed;
14347
14558
  try {
14348
- parsed = JSON.parse(readFileSync15(item.filePath, "utf-8"));
14559
+ parsed = JSON.parse(readFileSync14(item.filePath, "utf-8"));
14349
14560
  } catch {
14350
14561
  return [];
14351
14562
  }
@@ -14377,7 +14588,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
14377
14588
  });
14378
14589
  for (const file of sorted) {
14379
14590
  try {
14380
- const content = readFileSync15(join22(taskDir, file), "utf-8");
14591
+ const content = readFileSync14(join22(taskDir, file), "utf-8");
14381
14592
  const task = JSON.parse(content);
14382
14593
  if (task.subject) {
14383
14594
  tasks.push(task);
@@ -14393,7 +14604,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
14393
14604
  });
14394
14605
 
14395
14606
  // ../engine/dist/parsers/claude-md-source.js
14396
- import { existsSync as existsSync22, readFileSync as readFileSync16, readdirSync as readdirSync16, statSync as statSync20 } from "fs";
14607
+ import { existsSync as existsSync22, readFileSync as readFileSync15, readdirSync as readdirSync16, statSync as statSync20 } from "fs";
14397
14608
  import { homedir as homedir8 } from "os";
14398
14609
  import { join as join23, basename as basename14, dirname as dirname13 } from "path";
14399
14610
  import { createHash as createHash9 } from "crypto";
@@ -14414,7 +14625,7 @@ function discoverProjectDirs(claudeDir) {
14414
14625
  for (const entry of entries) {
14415
14626
  if (!entry.isDirectory() || entry.name.startsWith("."))
14416
14627
  continue;
14417
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
14628
+ const projectPath = decodeProjectDirName(entry.name);
14418
14629
  addProject(projectPath);
14419
14630
  }
14420
14631
  } catch {
@@ -14437,7 +14648,7 @@ function discoverProjectDirs(claudeDir) {
14437
14648
  const gemProjects = geminiBackend.projectsJson();
14438
14649
  if (existsSync22(gemProjects)) {
14439
14650
  try {
14440
- const data = JSON.parse(readFileSync16(gemProjects, "utf-8"));
14651
+ const data = JSON.parse(readFileSync15(gemProjects, "utf-8"));
14441
14652
  for (const path of Object.keys(data.projects || {}))
14442
14653
  addProject(path);
14443
14654
  } catch {
@@ -14471,6 +14682,7 @@ var init_claude_md_source = __esm({
14471
14682
  init_gemini2();
14472
14683
  init_settings();
14473
14684
  init_opencode();
14685
+ init_project_dir_name();
14474
14686
  MAX_CHUNK_CHARS3 = 2e3;
14475
14687
  NOTE_FILES = [
14476
14688
  { filename: "CLAUDE.md", tool: "claude" },
@@ -14551,7 +14763,7 @@ var init_claude_md_source = __esm({
14551
14763
  async parse(item) {
14552
14764
  if (!existsSync22(item.filePath))
14553
14765
  return [];
14554
- const content = readFileSync16(item.filePath, "utf-8");
14766
+ const content = readFileSync15(item.filePath, "utf-8");
14555
14767
  const chunks = [];
14556
14768
  const sections = splitByHeaders(content);
14557
14769
  for (let i = 0; i < sections.length; i++) {
@@ -14593,7 +14805,7 @@ var init_claude_md_source = __esm({
14593
14805
  const projectDir = dirname13(filePath);
14594
14806
  const projectName = basename14(projectDir);
14595
14807
  const projectPath = projectDir;
14596
- const content = readFileSync16(filePath, "utf-8");
14808
+ const content = readFileSync15(filePath, "utf-8");
14597
14809
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"))?.trim()?.slice(0, 200) || `${filename} for ${projectName}`;
14598
14810
  const id = tool === "claude" ? projectName : `${projectName}_${tool}`;
14599
14811
  return {
@@ -14618,7 +14830,7 @@ var init_claude_md_source = __esm({
14618
14830
  });
14619
14831
 
14620
14832
  // ../engine/dist/parsers/agent-memory-source.js
14621
- import { existsSync as existsSync23, readFileSync as readFileSync17, readdirSync as readdirSync17, statSync as statSync21 } from "fs";
14833
+ import { existsSync as existsSync23, readFileSync as readFileSync16, readdirSync as readdirSync17, statSync as statSync21 } from "fs";
14622
14834
  import { homedir as homedir9 } from "os";
14623
14835
  import { join as join24, basename as basename15 } from "path";
14624
14836
  function parseFrontmatter2(content) {
@@ -14646,7 +14858,7 @@ function parseFrontmatter2(content) {
14646
14858
  return { fm, body };
14647
14859
  }
14648
14860
  function projectHashToPath(hashDir) {
14649
- return hashDir.replace(/^-/, "/").replace(/-/g, "/");
14861
+ return decodeProjectDirName(hashDir);
14650
14862
  }
14651
14863
  function discoverMemoryDirs() {
14652
14864
  const out = [];
@@ -14688,6 +14900,7 @@ var init_agent_memory_source = __esm({
14688
14900
  init_utils();
14689
14901
  init_claude2();
14690
14902
  init_settings();
14903
+ init_project_dir_name();
14691
14904
  MAX_CHUNK_CHARS4 = 2e3;
14692
14905
  MAX_CHUNKS = 12;
14693
14906
  AgentMemorySource = class {
@@ -14718,7 +14931,7 @@ var init_agent_memory_source = __esm({
14718
14931
  async parse(item) {
14719
14932
  if (!existsSync23(item.filePath))
14720
14933
  return [];
14721
- const content = readFileSync17(item.filePath, "utf-8");
14934
+ const content = readFileSync16(item.filePath, "utf-8");
14722
14935
  const { body } = parseFrontmatter2(content);
14723
14936
  const chunks = [];
14724
14937
  const sections = splitByHeaders(body);
@@ -14771,7 +14984,7 @@ var init_agent_memory_source = __esm({
14771
14984
  fileToItem(filePath, filename, projectPath, _projectHash) {
14772
14985
  try {
14773
14986
  const stat3 = statSync21(filePath);
14774
- const content = readFileSync17(filePath, "utf-8");
14987
+ const content = readFileSync16(filePath, "utf-8");
14775
14988
  const { fm } = parseFrontmatter2(content);
14776
14989
  const stem = filename.replace(/\.md$/, "");
14777
14990
  const idBase = fm.name || stem;
@@ -14986,7 +15199,7 @@ var init_history_source = __esm({
14986
15199
  });
14987
15200
 
14988
15201
  // ../engine/dist/parsers/paste-source.js
14989
- import { existsSync as existsSync25, readdirSync as readdirSync19, readFileSync as readFileSync18, statSync as statSync23 } from "fs";
15202
+ import { existsSync as existsSync25, readdirSync as readdirSync19, readFileSync as readFileSync17, statSync as statSync23 } from "fs";
14990
15203
  import { homedir as homedir11 } from "os";
14991
15204
  import { join as join26, basename as basename16 } from "path";
14992
15205
  var MAX_CHUNK_CHARS6, MAX_FILE_SIZE, PasteSource;
@@ -15042,7 +15255,7 @@ var init_paste_source = __esm({
15042
15255
  if (stat3.size < 10)
15043
15256
  continue;
15044
15257
  const hash = basename16(file, ".txt");
15045
- const content = readFileSync18(filePath, "utf-8");
15258
+ const content = readFileSync17(filePath, "utf-8");
15046
15259
  const firstLine = content.split("\n").find((l) => l.trim().length > 5)?.trim()?.slice(0, 100) || `Paste ${hash.slice(0, 8)}`;
15047
15260
  yield {
15048
15261
  id: hash,
@@ -15063,7 +15276,7 @@ var init_paste_source = __esm({
15063
15276
  async parse(item) {
15064
15277
  if (!existsSync25(item.filePath))
15065
15278
  return [];
15066
- const content = readFileSync18(item.filePath, "utf-8");
15279
+ const content = readFileSync17(item.filePath, "utf-8");
15067
15280
  if (content.trim().length < 20)
15068
15281
  return [];
15069
15282
  return [{
@@ -15086,7 +15299,7 @@ var init_paste_source = __esm({
15086
15299
  });
15087
15300
 
15088
15301
  // ../engine/dist/parsers/gemini-source.js
15089
- import { existsSync as existsSync26, readdirSync as readdirSync20, readFileSync as readFileSync19, statSync as statSync24 } from "fs";
15302
+ import { existsSync as existsSync26, readdirSync as readdirSync20, readFileSync as readFileSync18, statSync as statSync24 } from "fs";
15090
15303
  import { join as join27, basename as basename17 } from "path";
15091
15304
  import { createHash as createHash10 } from "crypto";
15092
15305
  function loadGeminiProjectMap2() {
@@ -15095,7 +15308,7 @@ function loadGeminiProjectMap2() {
15095
15308
  if (!existsSync26(projectsPath))
15096
15309
  return map;
15097
15310
  try {
15098
- const data = JSON.parse(readFileSync19(projectsPath, "utf-8"));
15311
+ const data = JSON.parse(readFileSync18(projectsPath, "utf-8"));
15099
15312
  const projects = data.projects || {};
15100
15313
  for (const [path] of Object.entries(projects)) {
15101
15314
  const hash = createHash10("sha256").update(path).digest("hex");
@@ -15140,7 +15353,7 @@ var init_gemini_source = __esm({
15140
15353
  const filePath = join27(chatsDir, file);
15141
15354
  try {
15142
15355
  const stat3 = statSync24(filePath);
15143
- const content = JSON.parse(readFileSync19(filePath, "utf-8"));
15356
+ const content = JSON.parse(readFileSync18(filePath, "utf-8"));
15144
15357
  const sessionId = content.sessionId || basename17(file, ".json");
15145
15358
  if (seen.has(sessionId))
15146
15359
  continue;
@@ -15221,7 +15434,7 @@ var init_gemini_source = __esm({
15221
15434
  async parse(item) {
15222
15435
  if (!existsSync26(item.filePath))
15223
15436
  return [];
15224
- const content = JSON.parse(readFileSync19(item.filePath, "utf-8"));
15437
+ const content = JSON.parse(readFileSync18(item.filePath, "utf-8"));
15225
15438
  const messages = content.messages || [];
15226
15439
  const chunks = [];
15227
15440
  const getText = (m) => {
@@ -15269,7 +15482,7 @@ var init_gemini_source = __esm({
15269
15482
  });
15270
15483
 
15271
15484
  // ../engine/dist/parsers/gemini-brain-source.js
15272
- import { existsSync as existsSync27, readdirSync as readdirSync21, readFileSync as readFileSync20, statSync as statSync25 } from "fs";
15485
+ import { existsSync as existsSync27, readdirSync as readdirSync21, readFileSync as readFileSync19, statSync as statSync25 } from "fs";
15273
15486
  import { join as join28 } from "path";
15274
15487
  var MAX_CHUNK_CHARS8, GeminiBrainSource;
15275
15488
  var init_gemini_brain_source = __esm({
@@ -15302,14 +15515,14 @@ var init_gemini_brain_source = __esm({
15302
15515
  const filePath = join28(sessionDir, file);
15303
15516
  try {
15304
15517
  const stat3 = statSync25(filePath);
15305
- const content = readFileSync20(filePath, "utf-8");
15518
+ const content = readFileSync19(filePath, "utf-8");
15306
15519
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"))?.trim() || "";
15307
15520
  const heading = content.split("\n").find((l) => l.startsWith("#"))?.replace(/^#+\s*/, "").trim() || file;
15308
15521
  const metaPath = `${filePath}.metadata.json`;
15309
15522
  let metaSummary = "";
15310
15523
  if (existsSync27(metaPath)) {
15311
15524
  try {
15312
- const meta = JSON.parse(readFileSync20(metaPath, "utf-8"));
15525
+ const meta = JSON.parse(readFileSync19(metaPath, "utf-8"));
15313
15526
  metaSummary = meta.summary || "";
15314
15527
  } catch {
15315
15528
  }
@@ -15340,7 +15553,7 @@ var init_gemini_brain_source = __esm({
15340
15553
  async parse(item) {
15341
15554
  if (!existsSync27(item.filePath))
15342
15555
  return [];
15343
- const content = readFileSync20(item.filePath, "utf-8");
15556
+ const content = readFileSync19(item.filePath, "utf-8");
15344
15557
  const chunks = [];
15345
15558
  const sections = splitByHeaders(content);
15346
15559
  for (let i = 0; i < sections.length; i++) {
@@ -15683,7 +15896,7 @@ var init_opencode_todo_source = __esm({
15683
15896
  });
15684
15897
 
15685
15898
  // ../engine/dist/parsers/codex-session-source.js
15686
- import { existsSync as existsSync30, readdirSync as readdirSync22, readFileSync as readFileSync21, statSync as statSync26 } from "fs";
15899
+ import { existsSync as existsSync30, readdirSync as readdirSync22, readFileSync as readFileSync20, statSync as statSync26 } from "fs";
15687
15900
  import { join as join29, basename as basename18 } from "path";
15688
15901
  function getCodexSessionsDir() {
15689
15902
  return codexBackend.sessionsDir();
@@ -15711,7 +15924,7 @@ function extractReasoningText(event) {
15711
15924
  return "";
15712
15925
  }
15713
15926
  function readJsonlLines(filePath) {
15714
- const text = readFileSync21(filePath, "utf-8");
15927
+ const text = readFileSync20(filePath, "utf-8");
15715
15928
  const lines = text.split("\n");
15716
15929
  const events = [];
15717
15930
  for (const line of lines) {
@@ -15746,7 +15959,7 @@ var init_codex_session_source = __esm({
15746
15959
  for (const filePath of jsonlFiles) {
15747
15960
  try {
15748
15961
  const stat3 = statSync26(filePath);
15749
- const lines = readFileSync21(filePath, "utf-8").split("\n");
15962
+ const lines = readFileSync20(filePath, "utf-8").split("\n");
15750
15963
  const firstLine = lines.find((l) => l.trim());
15751
15964
  if (!firstLine)
15752
15965
  continue;
@@ -15919,7 +16132,7 @@ var init_codex_session_source = __esm({
15919
16132
  });
15920
16133
 
15921
16134
  // ../engine/dist/parsers/diary-source.js
15922
- import { existsSync as existsSync31, mkdirSync as mkdirSync5, readdirSync as readdirSync23, readFileSync as readFileSync22, writeFileSync as writeFileSync4, statSync as statSync27 } from "fs";
16135
+ import { existsSync as existsSync31, mkdirSync as mkdirSync5, readdirSync as readdirSync23, readFileSync as readFileSync21, writeFileSync as writeFileSync4, statSync as statSync27 } from "fs";
15923
16136
  import { join as join30, basename as basename19 } from "path";
15924
16137
  var MAX_CHUNK_CHARS11, DiarySource;
15925
16138
  var init_diary_source = __esm({
@@ -15945,7 +16158,7 @@ var init_diary_source = __esm({
15945
16158
  const filePath = join30(agentPath2, file);
15946
16159
  try {
15947
16160
  const stat3 = statSync27(filePath);
15948
- const raw = readFileSync22(filePath, "utf-8");
16161
+ const raw = readFileSync21(filePath, "utf-8");
15949
16162
  const entry = JSON.parse(raw);
15950
16163
  const entryId = basename19(file, ".json");
15951
16164
  const title = `[${entry.agent}] ${entry.topic}: ${entry.content.slice(0, 80)}`;
@@ -15972,7 +16185,7 @@ var init_diary_source = __esm({
15972
16185
  async parse(item) {
15973
16186
  if (!existsSync31(item.filePath))
15974
16187
  return [];
15975
- const raw = readFileSync22(item.filePath, "utf-8");
16188
+ const raw = readFileSync21(item.filePath, "utf-8");
15976
16189
  const entry = JSON.parse(raw);
15977
16190
  if (!entry.content || entry.content.trim().length < 10)
15978
16191
  return [];
@@ -16035,7 +16248,7 @@ var init_diary_source = __esm({
16035
16248
  const entries = [];
16036
16249
  for (const file of files) {
16037
16250
  try {
16038
- const raw = readFileSync22(join30(agentDir, file), "utf-8");
16251
+ const raw = readFileSync21(join30(agentDir, file), "utf-8");
16039
16252
  entries.push(JSON.parse(raw));
16040
16253
  } catch {
16041
16254
  continue;
@@ -16048,7 +16261,7 @@ var init_diary_source = __esm({
16048
16261
  });
16049
16262
 
16050
16263
  // ../engine/dist/parsers/skills-source.js
16051
- import { existsSync as existsSync32, readdirSync as readdirSync24, readFileSync as readFileSync23, statSync as statSync28 } from "fs";
16264
+ import { existsSync as existsSync32, readdirSync as readdirSync24, readFileSync as readFileSync22, statSync as statSync28 } from "fs";
16052
16265
  import { homedir as homedir12 } from "os";
16053
16266
  import { join as join31 } from "path";
16054
16267
  function resolveRoots() {
@@ -16141,7 +16354,7 @@ var init_skills_source = __esm({
16141
16354
  continue;
16142
16355
  let content;
16143
16356
  try {
16144
- content = readFileSync23(skillMd, "utf-8");
16357
+ content = readFileSync22(skillMd, "utf-8");
16145
16358
  } catch {
16146
16359
  continue;
16147
16360
  }
@@ -16201,7 +16414,7 @@ var init_skills_source = __esm({
16201
16414
  async parse(item) {
16202
16415
  if (!existsSync32(item.filePath))
16203
16416
  return [];
16204
- const content = readFileSync23(item.filePath, "utf-8");
16417
+ const content = readFileSync22(item.filePath, "utf-8");
16205
16418
  if (!content.trim())
16206
16419
  return [];
16207
16420
  return [{
@@ -16224,14 +16437,14 @@ var init_skills_source = __esm({
16224
16437
  });
16225
16438
 
16226
16439
  // ../engine/dist/parsers/mcps-source.js
16227
- import { existsSync as existsSync33, readdirSync as readdirSync25, readFileSync as readFileSync24, statSync as statSync29 } from "fs";
16440
+ import { existsSync as existsSync33, readdirSync as readdirSync25, readFileSync as readFileSync23, statSync as statSync29 } from "fs";
16228
16441
  import { homedir as homedir13 } from "os";
16229
16442
  import { join as join32 } from "path";
16230
16443
  function readJson(path) {
16231
16444
  if (!existsSync33(path))
16232
16445
  return null;
16233
16446
  try {
16234
- const raw = readFileSync24(path, "utf-8");
16447
+ const raw = readFileSync23(path, "utf-8");
16235
16448
  const stripped = path.endsWith(".jsonc") ? raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/,(\s*[}\]])/g, "$1") : raw;
16236
16449
  return JSON.parse(stripped);
16237
16450
  } catch {
@@ -16344,7 +16557,7 @@ var init_mcps_source = __esm({
16344
16557
  return;
16345
16558
  let content;
16346
16559
  try {
16347
- content = readFileSync24(path, "utf-8");
16560
+ content = readFileSync23(path, "utf-8");
16348
16561
  } catch {
16349
16562
  return;
16350
16563
  }
@@ -16513,7 +16726,7 @@ var init_toolkit_format = __esm({
16513
16726
  });
16514
16727
 
16515
16728
  // ../engine/dist/parsers/slash-commands-source.js
16516
- import { existsSync as existsSync34, readdirSync as readdirSync26, readFileSync as readFileSync25, statSync as statSync30 } from "fs";
16729
+ import { existsSync as existsSync34, readdirSync as readdirSync26, readFileSync as readFileSync24, statSync as statSync30 } from "fs";
16517
16730
  import { join as join33, basename as basename20 } from "path";
16518
16731
  function parseCommand(content, fallbackName, format) {
16519
16732
  if (format === "toml") {
@@ -16541,6 +16754,7 @@ var init_slash_commands_source = __esm({
16541
16754
  init_codex2();
16542
16755
  init_settings();
16543
16756
  init_toolkit_format();
16757
+ init_project_dir_name();
16544
16758
  MAX_CHUNK_CHARS13 = 2e3;
16545
16759
  SlashCommandsSource = class {
16546
16760
  sourceType = "command";
@@ -16554,7 +16768,7 @@ var init_slash_commands_source = __esm({
16554
16768
  for (const entry of readdirSync26(projectsRoot, { withFileTypes: true })) {
16555
16769
  if (!entry.isDirectory() || entry.name.startsWith("."))
16556
16770
  continue;
16557
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
16771
+ const projectPath = decodeProjectDirName(entry.name);
16558
16772
  const cmdDir = join33(projectPath, ".claude", "commands");
16559
16773
  if (existsSync34(cmdDir))
16560
16774
  roots.push({ path: cmdDir, tool: "claude", scope: "project", projectPath, format: "md" });
@@ -16587,7 +16801,7 @@ var init_slash_commands_source = __esm({
16587
16801
  const filePath = join33(root.path, file);
16588
16802
  try {
16589
16803
  const stat3 = statSync30(filePath);
16590
- const content = readFileSync25(filePath, "utf-8");
16804
+ const content = readFileSync24(filePath, "utf-8");
16591
16805
  const parsed = parseCommand(content, basename20(file, ext), root.format);
16592
16806
  yield {
16593
16807
  id: `${root.tool}_cmd_${root.scope}_${parsed.name}`,
@@ -16613,7 +16827,7 @@ var init_slash_commands_source = __esm({
16613
16827
  async parse(item) {
16614
16828
  if (!existsSync34(item.filePath))
16615
16829
  return [];
16616
- const content = readFileSync25(item.filePath, "utf-8");
16830
+ const content = readFileSync24(item.filePath, "utf-8");
16617
16831
  if (!content.trim())
16618
16832
  return [];
16619
16833
  return [{
@@ -16636,7 +16850,7 @@ var init_slash_commands_source = __esm({
16636
16850
  });
16637
16851
 
16638
16852
  // ../engine/dist/parsers/subagents-source.js
16639
- import { existsSync as existsSync35, readdirSync as readdirSync27, readFileSync as readFileSync26, statSync as statSync31 } from "fs";
16853
+ import { existsSync as existsSync35, readdirSync as readdirSync27, readFileSync as readFileSync25, statSync as statSync31 } from "fs";
16640
16854
  import { join as join34, basename as basename21 } from "path";
16641
16855
  function parseAgent(content, fallbackName, format) {
16642
16856
  if (format === "toml") {
@@ -16666,6 +16880,7 @@ var init_subagents_source = __esm({
16666
16880
  init_codex2();
16667
16881
  init_settings();
16668
16882
  init_toolkit_format();
16883
+ init_project_dir_name();
16669
16884
  MAX_CHUNK_CHARS14 = 2e3;
16670
16885
  SubagentsSource = class {
16671
16886
  sourceType = "agent";
@@ -16679,7 +16894,7 @@ var init_subagents_source = __esm({
16679
16894
  for (const entry of readdirSync27(projectsRoot, { withFileTypes: true })) {
16680
16895
  if (!entry.isDirectory() || entry.name.startsWith("."))
16681
16896
  continue;
16682
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
16897
+ const projectPath = decodeProjectDirName(entry.name);
16683
16898
  const agentDir = join34(projectPath, ".claude", "agents");
16684
16899
  if (existsSync35(agentDir))
16685
16900
  roots.push({ path: agentDir, tool: "claude", scope: "project", projectPath, format: "md" });
@@ -16712,7 +16927,7 @@ var init_subagents_source = __esm({
16712
16927
  const filePath = join34(root.path, file);
16713
16928
  try {
16714
16929
  const stat3 = statSync31(filePath);
16715
- const content = readFileSync26(filePath, "utf-8");
16930
+ const content = readFileSync25(filePath, "utf-8");
16716
16931
  const parsed = parseAgent(content, basename21(file, ext), root.format);
16717
16932
  yield {
16718
16933
  id: `${root.tool}_agent_${root.scope}_${parsed.name}`,
@@ -16739,7 +16954,7 @@ var init_subagents_source = __esm({
16739
16954
  async parse(item) {
16740
16955
  if (!existsSync35(item.filePath))
16741
16956
  return [];
16742
- const content = readFileSync26(item.filePath, "utf-8");
16957
+ const content = readFileSync25(item.filePath, "utf-8");
16743
16958
  if (!content.trim())
16744
16959
  return [];
16745
16960
  return [{
@@ -16762,13 +16977,13 @@ var init_subagents_source = __esm({
16762
16977
  });
16763
16978
 
16764
16979
  // ../engine/dist/parsers/hooks-source.js
16765
- import { existsSync as existsSync36, readFileSync as readFileSync27, readdirSync as readdirSync28, statSync as statSync32 } from "fs";
16980
+ import { existsSync as existsSync36, readFileSync as readFileSync26, readdirSync as readdirSync28, statSync as statSync32 } from "fs";
16766
16981
  import { join as join35 } from "path";
16767
16982
  function readJson2(path) {
16768
16983
  if (!existsSync36(path))
16769
16984
  return null;
16770
16985
  try {
16771
- return JSON.parse(readFileSync27(path, "utf-8"));
16986
+ return JSON.parse(readFileSync26(path, "utf-8"));
16772
16987
  } catch {
16773
16988
  return null;
16774
16989
  }
@@ -16779,6 +16994,7 @@ var init_hooks_source = __esm({
16779
16994
  "use strict";
16780
16995
  init_claude2();
16781
16996
  init_settings();
16997
+ init_project_dir_name();
16782
16998
  HooksSource = class {
16783
16999
  sourceType = "hook";
16784
17000
  async *discover() {
@@ -16791,7 +17007,7 @@ var init_hooks_source = __esm({
16791
17007
  for (const entry of readdirSync28(projectsRoot, { withFileTypes: true })) {
16792
17008
  if (!entry.isDirectory() || entry.name.startsWith("."))
16793
17009
  continue;
16794
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
17010
+ const projectPath = decodeProjectDirName(entry.name);
16795
17011
  const settingsPath2 = join35(projectPath, ".claude", "settings.json");
16796
17012
  if (existsSync36(settingsPath2)) {
16797
17013
  yield* this.fromSettings(settingsPath2, "project", projectPath);
@@ -16884,14 +17100,14 @@ var init_hooks_source = __esm({
16884
17100
  });
16885
17101
 
16886
17102
  // ../engine/dist/parsers/plugins-source.js
16887
- import { existsSync as existsSync37, readFileSync as readFileSync28, readdirSync as readdirSync29, statSync as statSync33 } from "fs";
17103
+ import { existsSync as existsSync37, readFileSync as readFileSync27, readdirSync as readdirSync29, statSync as statSync33 } from "fs";
16888
17104
  import { homedir as homedir14 } from "os";
16889
17105
  import { join as join36 } from "path";
16890
17106
  function readJson3(path) {
16891
17107
  if (!existsSync37(path))
16892
17108
  return null;
16893
17109
  try {
16894
- return JSON.parse(readFileSync28(path, "utf-8"));
17110
+ return JSON.parse(readFileSync27(path, "utf-8"));
16895
17111
  } catch {
16896
17112
  return null;
16897
17113
  }
@@ -17148,7 +17364,7 @@ var init_extractor_version = __esm({
17148
17364
  });
17149
17365
 
17150
17366
  // src/sync-ledger.ts
17151
- import { readFileSync as readFileSync29, writeFileSync as writeFileSync5, renameSync as renameSync4, mkdirSync as mkdirSync6 } from "node:fs";
17367
+ import { readFileSync as readFileSync28, writeFileSync as writeFileSync5, renameSync as renameSync4, mkdirSync as mkdirSync6 } from "node:fs";
17152
17368
  import { join as join37, dirname as dirname14 } from "node:path";
17153
17369
  function toRow(entry) {
17154
17370
  if (entry === void 0) return void 0;
@@ -17157,24 +17373,77 @@ function toRow(entry) {
17157
17373
  function load() {
17158
17374
  if (cache3) return cache3;
17159
17375
  try {
17160
- const parsed = JSON.parse(readFileSync29(ledgerPath(), "utf-8"));
17376
+ const parsed = JSON.parse(readFileSync28(ledgerPath(), "utf-8"));
17161
17377
  cache3 = parsed && typeof parsed === "object" ? parsed : {};
17162
17378
  } catch {
17163
17379
  cache3 = {};
17164
17380
  }
17165
17381
  return cache3;
17166
17382
  }
17167
- function persist(data) {
17383
+ function persistNow(data) {
17168
17384
  const path = ledgerPath();
17169
17385
  mkdirSync6(dirname14(path), { recursive: true });
17170
17386
  const tmp = `${path}.tmp`;
17171
17387
  writeFileSync5(tmp, JSON.stringify(data));
17172
17388
  renameSync4(tmp, path);
17389
+ pendingWrite = false;
17390
+ }
17391
+ function persist(data) {
17392
+ pendingWrite = true;
17393
+ if (flushTimer) return;
17394
+ flushTimer = setTimeout(() => {
17395
+ flushTimer = null;
17396
+ if (pendingWrite) {
17397
+ try {
17398
+ persistNow(data);
17399
+ } catch {
17400
+ }
17401
+ }
17402
+ }, LEDGER_FLUSH_MS);
17403
+ flushTimer.unref?.();
17404
+ }
17405
+ function pruneLedgerTargets(configuredServers) {
17406
+ const data = load();
17407
+ const keep = new Set(configuredServers);
17408
+ const orphans = {};
17409
+ for (const server of Object.keys(data)) {
17410
+ if (!keep.has(server)) {
17411
+ orphans[server] = data[server];
17412
+ delete data[server];
17413
+ }
17414
+ }
17415
+ const names = Object.keys(orphans);
17416
+ if (names.length === 0) return 0;
17417
+ const path = ledgerPath();
17418
+ const side = `${path}.orphans.json`;
17419
+ try {
17420
+ let prior = {};
17421
+ try {
17422
+ prior = JSON.parse(readFileSync28(side, "utf-8"));
17423
+ } catch {
17424
+ }
17425
+ writeFileSync5(side, JSON.stringify({ ...prior, ...orphans }));
17426
+ } catch {
17427
+ for (const n of names) data[n] = orphans[n];
17428
+ return 0;
17429
+ }
17430
+ const rows = names.reduce((n, s) => n + Object.keys(orphans[s]).length, 0);
17431
+ console.error(`[sync] ledger: parked ${rows} row(s) for ${names.length} server(s) no longer configured (${names.join(", ")}) in ${side}`);
17432
+ persistNow(data);
17433
+ return names.length;
17434
+ }
17435
+ function flushLedger() {
17436
+ if (flushTimer) {
17437
+ clearTimeout(flushTimer);
17438
+ flushTimer = null;
17439
+ }
17440
+ if (!pendingWrite || !cache3) return;
17441
+ persistNow(cache3);
17173
17442
  }
17174
17443
  function loadItemVersionsAll() {
17175
17444
  if (itemVersionsCache) return itemVersionsCache;
17176
17445
  try {
17177
- const parsed = JSON.parse(readFileSync29(itemVersionsPath(), "utf-8"));
17446
+ const parsed = JSON.parse(readFileSync28(itemVersionsPath(), "utf-8"));
17178
17447
  itemVersionsCache = parsed && typeof parsed === "object" ? parsed : {};
17179
17448
  } catch {
17180
17449
  itemVersionsCache = {};
@@ -17256,7 +17525,7 @@ function markFieldCoverage(server, field, rows) {
17256
17525
  function loadReconcile() {
17257
17526
  if (reconcileCache) return reconcileCache;
17258
17527
  try {
17259
- const parsed = JSON.parse(readFileSync29(reconcilePath(), "utf-8"));
17528
+ const parsed = JSON.parse(readFileSync28(reconcilePath(), "utf-8"));
17260
17529
  reconcileCache = parsed && typeof parsed === "object" ? parsed : {};
17261
17530
  } catch {
17262
17531
  reconcileCache = {};
@@ -17304,7 +17573,7 @@ function syncMode(row, mtime, fileSize, extractorVersion, isAppendOnly) {
17304
17573
  if (row.m >= Math.floor(mtime)) return "skip";
17305
17574
  return "full";
17306
17575
  }
17307
- var ledgerPath, cache3, itemVersionsPath, itemVersionsCache, unackedCursorAttempts, reconcilePath, reconcileCache;
17576
+ var ledgerPath, cache3, LEDGER_FLUSH_MS, pendingWrite, flushTimer, itemVersionsPath, itemVersionsCache, unackedCursorAttempts, reconcilePath, reconcileCache;
17308
17577
  var init_sync_ledger = __esm({
17309
17578
  "src/sync-ledger.ts"() {
17310
17579
  "use strict";
@@ -17312,6 +17581,9 @@ var init_sync_ledger = __esm({
17312
17581
  init_extractor_version();
17313
17582
  ledgerPath = () => join37(getDataDir(), "sync-ledger.json");
17314
17583
  cache3 = null;
17584
+ LEDGER_FLUSH_MS = 2e3;
17585
+ pendingWrite = false;
17586
+ flushTimer = null;
17315
17587
  itemVersionsPath = () => join37(getDataDir(), "item-versions.json");
17316
17588
  itemVersionsCache = null;
17317
17589
  unackedCursorAttempts = 0;
@@ -17346,7 +17618,7 @@ var init_sync_fields = __esm({
17346
17618
  });
17347
17619
 
17348
17620
  // ../engine/dist/core/index-lock.js
17349
- import { existsSync as existsSync38, mkdirSync as mkdirSync7, openSync as openSync6, writeFileSync as writeFileSync6, readFileSync as readFileSync30, rmSync as rmSync4, statSync as statSync34, closeSync as closeSync6 } from "fs";
17621
+ import { existsSync as existsSync38, mkdirSync as mkdirSync7, openSync as openSync7, writeFileSync as writeFileSync6, readFileSync as readFileSync29, rmSync as rmSync4, statSync as statSync34, closeSync as closeSync7 } from "fs";
17350
17622
  import { dirname as dirname15, join as join38 } from "path";
17351
17623
  function acquireIndexLock(opts) {
17352
17624
  const lockPath = lockFilePath();
@@ -17358,7 +17630,7 @@ function acquireIndexLock(opts) {
17358
17630
  }
17359
17631
  let fd;
17360
17632
  try {
17361
- fd = openSync6(lockPath, "wx", 384);
17633
+ fd = openSync7(lockPath, "wx", 384);
17362
17634
  } catch (err) {
17363
17635
  if (err.code === "EEXIST")
17364
17636
  return null;
@@ -17368,7 +17640,7 @@ function acquireIndexLock(opts) {
17368
17640
  try {
17369
17641
  writeFileSync6(fd, JSON.stringify(holder));
17370
17642
  } finally {
17371
- closeSync6(fd);
17643
+ closeSync7(fd);
17372
17644
  }
17373
17645
  let released = false;
17374
17646
  return {
@@ -17378,7 +17650,7 @@ function acquireIndexLock(opts) {
17378
17650
  return;
17379
17651
  released = true;
17380
17652
  try {
17381
- const current = JSON.parse(readFileSync30(lockPath, "utf-8"));
17653
+ const current = JSON.parse(readFileSync29(lockPath, "utf-8"));
17382
17654
  if (current.pid !== holder.pid || current.startedAt !== holder.startedAt)
17383
17655
  return;
17384
17656
  } catch {
@@ -17400,7 +17672,7 @@ function stealStaleLock(lockPath, staleAfterMs) {
17400
17672
  const age = Date.now() - s.mtimeMs;
17401
17673
  let payload;
17402
17674
  try {
17403
- payload = JSON.parse(readFileSync30(lockPath, "utf-8"));
17675
+ payload = JSON.parse(readFileSync29(lockPath, "utf-8"));
17404
17676
  } catch {
17405
17677
  rmSync4(lockPath, { force: true });
17406
17678
  return true;
@@ -17438,7 +17710,7 @@ var init_index_lock = __esm({
17438
17710
  });
17439
17711
 
17440
17712
  // src/http.ts
17441
- import { readFileSync as readFileSync31 } from "node:fs";
17713
+ import { readFileSync as readFileSync30 } from "node:fs";
17442
17714
  function clientHeaders() {
17443
17715
  return {
17444
17716
  "x-chat-recall-cli": CLI_VERSION,
@@ -17456,10 +17728,10 @@ var init_http = __esm({
17456
17728
  "use strict";
17457
17729
  CLI_VERSION = "0.0.0";
17458
17730
  try {
17459
- CLI_VERSION = JSON.parse(readFileSync31(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
17731
+ CLI_VERSION = JSON.parse(readFileSync30(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
17460
17732
  } catch {
17461
17733
  try {
17462
- if (true) CLI_VERSION = "0.5.5";
17734
+ if (true) CLI_VERSION = "0.5.6";
17463
17735
  } catch {
17464
17736
  }
17465
17737
  }
@@ -17472,7 +17744,7 @@ __export(client_events_exports, {
17472
17744
  isTelemetryEnabled: () => isTelemetryEnabled,
17473
17745
  reportClientEvent: () => reportClientEvent
17474
17746
  });
17475
- import { readFileSync as readFileSync32 } from "node:fs";
17747
+ import { readFileSync as readFileSync31 } from "node:fs";
17476
17748
  function scrub(msg) {
17477
17749
  if (!msg) return "";
17478
17750
  const noHome = msg.replace(/\/(?:home|Users)\/[^/\s]+/g, "~");
@@ -17525,7 +17797,7 @@ var init_client_events = __esm({
17525
17797
  init_secret_redactor();
17526
17798
  CLI_VERSION2 = "0.0.0";
17527
17799
  try {
17528
- CLI_VERSION2 = JSON.parse(readFileSync32(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION2;
17800
+ CLI_VERSION2 = JSON.parse(readFileSync31(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION2;
17529
17801
  } catch {
17530
17802
  }
17531
17803
  }
@@ -17645,13 +17917,15 @@ var init_auto_update = __esm({
17645
17917
 
17646
17918
  // src/sync-client.ts
17647
17919
  import { join as join40, dirname as dirname16, basename as basename22 } from "node:path";
17648
- import { readFileSync as readFileSync33, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync4, rmSync as rmSync5, chmodSync as chmodSync3, unlinkSync as unlinkSync2, readdirSync as readdirSync30, statSync as statSync35 } from "node:fs";
17920
+ import { readFileSync as readFileSync32, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync4, rmSync as rmSync5, chmodSync as chmodSync3, unlinkSync as unlinkSync2, readdirSync as readdirSync30, statSync as statSync35 } from "node:fs";
17649
17921
  import { tmpdir as tmpdir3, hostname } from "node:os";
17650
17922
  import { createHash as createHash12 } from "node:crypto";
17651
17923
  import { fileURLToPath } from "node:url";
17652
17924
  function loadAllCredentials() {
17925
+ const fromEnv = envCredentials();
17926
+ if (fromEnv) return [{ serverUrl: fromEnv.serverUrl, token: fromEnv.token }];
17653
17927
  try {
17654
- const parsed = JSON.parse(readFileSync33(credPath(), "utf-8"));
17928
+ const parsed = JSON.parse(readFileSync32(credPath(), "utf-8"));
17655
17929
  if (Array.isArray(parsed?.targets)) return parsed.targets.filter((t) => t?.serverUrl).map((t) => ({ serverUrl: t.serverUrl, token: t.token || "" }));
17656
17930
  if (parsed?.serverUrl) return [{ serverUrl: parsed.serverUrl, token: parsed.token || "" }];
17657
17931
  } catch {
@@ -17797,6 +18071,10 @@ function isLocalHost(serverUrl) {
17797
18071
  async function syncSessions(opts = {}) {
17798
18072
  const targets = loadAllCredentials();
17799
18073
  if (targets.length === 0) throw new Error("Not logged in \u2014 run `chat-recall login <server-url> --token <token>`");
18074
+ try {
18075
+ pruneLedgerTargets(targets.map((t) => t.serverUrl));
18076
+ } catch {
18077
+ }
17800
18078
  let agg = null;
17801
18079
  const errors = [];
17802
18080
  for (const cred of targets) {
@@ -17819,6 +18097,7 @@ async function syncSessions(opts = {}) {
17819
18097
  errors.push(`${cred.serverUrl}: ${e instanceof Error ? e.message : e}`);
17820
18098
  }
17821
18099
  }
18100
+ flushLedger();
17822
18101
  if (!agg) throw new Error(`sync failed for all targets:
17823
18102
  ${errors.join("\n ")}`);
17824
18103
  if (errors.length > 0) console.error(`[sync] ${errors.length} target(s) failed (others succeeded):
@@ -17844,11 +18123,11 @@ async function reconcileFieldsForTarget(cred, fullRefs, opts = {}) {
17844
18123
  let pushed = 0, absent = 0, scanned = 0;
17845
18124
  for (const field of SYNC_FIELDS) {
17846
18125
  const needFull = !!opts.force || fieldNeedsFullPass(base2, field);
17847
- const candidates = needFull ? fullRefs() : opts.convRefs ?? fullRefs();
18126
+ const candidates2 = needFull ? fullRefs() : opts.convRefs ?? fullRefs();
17848
18127
  const send = [];
17849
18128
  const mtimeById = /* @__PURE__ */ new Map();
17850
18129
  const absentIds = [];
17851
- for (const ref of candidates) {
18130
+ for (const ref of candidates2) {
17852
18131
  if (!fieldNeedsScan(ledger.get(ref.prefixedId), field, ref.mtime, opts.force)) continue;
17853
18132
  scanned++;
17854
18133
  mtimeById.set(ref.prefixedId, ref.mtime);
@@ -17947,6 +18226,17 @@ function batchScanExternal(items, opts = {}) {
17947
18226
  }
17948
18227
  return { findings, scanned, slices, scanMs };
17949
18228
  }
18229
+ async function verifyServerApi(serverUrl, minApi) {
18230
+ const base2 = serverUrl.replace(/\/$/, "");
18231
+ const seen = capsOk.get(base2);
18232
+ if (seen !== void 0 && Date.now() - seen < CAPS_TTL_MS) return true;
18233
+ const caps = await fetchWithTimeout(`${base2}/api/capabilities`).then((r) => r.json());
18234
+ if ((caps.apiVersion ?? 0) < minApi) {
18235
+ throw new Error(`server too old: apiVersion ${caps.apiVersion ?? "none"} < required ${minApi} \u2014 update the server image before syncing`);
18236
+ }
18237
+ capsOk.set(base2, Date.now());
18238
+ return true;
18239
+ }
17950
18240
  async function syncToTarget(cred, opts = {}) {
17951
18241
  const sync = loadSettings().sync;
17952
18242
  const serverCfg = await fetchTenantSyncConfig(cred);
@@ -17967,14 +18257,9 @@ async function syncToTarget(cred, opts = {}) {
17967
18257
  return isProjectSyncable(r.source === "ignored" ? "" : r.id, projectPath, { syncMode: "only", syncOnly });
17968
18258
  };
17969
18259
  const MIN_SERVER_API = 2;
17970
- try {
17971
- const caps = await fetchWithTimeout(`${cred.serverUrl.replace(/\/$/, "")}/api/capabilities`).then((r) => r.json());
17972
- if ((caps.apiVersion ?? 0) < MIN_SERVER_API) {
17973
- throw new Error(`server too old: apiVersion ${caps.apiVersion ?? "none"} < required ${MIN_SERVER_API} \u2014 update the server image before syncing`);
17974
- }
17975
- } catch (e) {
17976
- if (e instanceof Error && e.message.startsWith("server too old")) throw e;
17977
- throw new Error(`cannot verify server version (${e instanceof Error ? e.message : e}) \u2014 refusing to sync blind`);
18260
+ {
18261
+ const ok = await verifyServerApi(cred.serverUrl, MIN_SERVER_API);
18262
+ if (!ok) throw new Error(`cannot verify server version \u2014 refusing to sync blind`);
17978
18263
  }
17979
18264
  const { tenantRules, verifySecrets } = await fetchTenantSecurityConfig(cred);
17980
18265
  const CHANGED_WALK_OVERLAP_MS = 15 * 6e4;
@@ -18031,8 +18316,11 @@ async function syncToTarget(cred, opts = {}) {
18031
18316
  try {
18032
18317
  const body2 = JSON.parse(text);
18033
18318
  detail = body2.error ?? text;
18319
+ if (body2.detail) detail += `
18320
+ ${body2.detail}`;
18034
18321
  if (body2.resetsAt) detail += ` (resets ${new Date(body2.resetsAt).toISOString().slice(0, 10)})`;
18035
- if (body2.upgradeUrl) detail += ` \u2014 ${body2.upgradeUrl}`;
18322
+ if (body2.upgradeUrl) detail += `
18323
+ ${body2.upgradeUrl}`;
18036
18324
  } catch {
18037
18325
  }
18038
18326
  throw new Error(`sync paused by the server: ${detail}`);
@@ -18179,16 +18467,21 @@ async function syncToTarget(cred, opts = {}) {
18179
18467
  const findingsBatch = makeBatcher("findings", 200, true);
18180
18468
  const scanSecrets = isSecretScannerAvailable() && upload.findings && process.env.CHAT_RECALL_SCAN_SECRETS !== "0";
18181
18469
  const verifySecretsScan = verifySecrets;
18470
+ let skipped = 0, redactions = 0, walked = 0;
18182
18471
  const kgSeen = /* @__PURE__ */ new Set();
18183
18472
  const kgEntityType = /* @__PURE__ */ new Map();
18184
- const kgRows = [];
18185
- const accumulateKg = (triples, sourceSession) => {
18473
+ const kgEntityNames = /* @__PURE__ */ new Set();
18474
+ const kgKey = (subject, predicate, object, validFrom) => createHash12("sha1").update(`${subject}|${predicate}|${object}|${validFrom}`).digest("hex").slice(0, 16);
18475
+ const accumulateKg = async (triples, sourceSession) => {
18186
18476
  for (const t of triples) {
18187
- const key = `${t.subject}|${t.predicate}|${t.object}|${t.validFrom ?? ""}`;
18477
+ const key = kgKey(t.subject, t.predicate, t.object, t.validFrom ?? "");
18188
18478
  if (kgSeen.has(key)) continue;
18189
18479
  kgSeen.add(key);
18190
18480
  if (t.predicate === "is_a" && !kgEntityType.has(t.subject)) kgEntityType.set(t.subject, t.object);
18191
- kgRows.push({
18481
+ kgEntityNames.add(t.subject);
18482
+ kgEntityNames.add(t.object);
18483
+ const count = { redactions: 0 };
18484
+ await kgTripleBatch.add(redactDeep({
18192
18485
  subject: t.subject,
18193
18486
  predicate: t.predicate,
18194
18487
  object: t.object,
@@ -18196,10 +18489,10 @@ async function syncToTarget(cred, opts = {}) {
18196
18489
  valid_to: null,
18197
18490
  confidence: t.confidence ?? 1,
18198
18491
  source_session: sourceSession
18199
- });
18492
+ }, count));
18493
+ redactions += count.redactions;
18200
18494
  }
18201
18495
  };
18202
- let skipped = 0, redactions = 0, walked = 0;
18203
18496
  let scanMs = 0, scanned = 0;
18204
18497
  const slice = refs.slice(0, opts.limit ?? refs.length);
18205
18498
  const trace = process.env.CHAT_RECALL_SYNC_TRACE === "1" ? (msg) => {
@@ -18332,7 +18625,7 @@ async function syncToTarget(cred, opts = {}) {
18332
18625
  await convBatch.add(built.conv);
18333
18626
  if (built.findings.length > 0) await findingsBatch.add(built.findings);
18334
18627
  if (upload.sessionMeta) {
18335
- 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);
18628
+ 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);
18336
18629
  }
18337
18630
  if (upload.sessionMeta && sessionFileBytes(ref) <= FULL_BUILD_MAX_BYTES) {
18338
18631
  try {
@@ -18411,7 +18704,7 @@ async function syncToTarget(cred, opts = {}) {
18411
18704
  chunks: redactedChunks
18412
18705
  });
18413
18706
  if (upload.sessionMeta && redactedChunks.length > 0) {
18414
- accumulateKg(
18707
+ await accumulateKg(
18415
18708
  extractEntities(redactedChunks.map((c) => c.text).join("\n"), { projectPath: item.projectPath || "", sourceType: item.sourceType, validFrom: item.mtime ? new Date(Number(item.mtime)).toISOString().slice(0, 10) : void 0 }),
18416
18709
  null
18417
18710
  );
@@ -18436,16 +18729,8 @@ async function syncToTarget(cred, opts = {}) {
18436
18729
  }
18437
18730
  }
18438
18731
  }
18439
- if (upload.sessionMeta && kgRows.length > 0) {
18440
- const entityNames = /* @__PURE__ */ new Set();
18441
- for (const t of kgRows) {
18442
- const count = { redactions: 0 };
18443
- await kgTripleBatch.add(redactDeep(t, count));
18444
- redactions += count.redactions;
18445
- entityNames.add(t.subject);
18446
- entityNames.add(t.object);
18447
- }
18448
- for (const name of entityNames) {
18732
+ if (upload.sessionMeta && kgEntityNames.size > 0) {
18733
+ for (const name of kgEntityNames) {
18449
18734
  const count = { redactions: 0 };
18450
18735
  await kgEntityBatch.add(redactDeep({ name, type: kgEntityType.get(name) ?? "unknown", properties: {} }, count));
18451
18736
  redactions += count.redactions;
@@ -18805,56 +19090,58 @@ function collectDerived(ref, mtime, count) {
18805
19090
  const id = ref.prefixedId;
18806
19091
  const compute = [];
18807
19092
  let fullOutcome = null;
18808
- for (const kind of COMPUTE_KINDS) {
18809
- let data = null;
18810
- try {
18811
- if (kind === "diff") {
18812
- const replay = replaySessionAny(id);
18813
- if (replay.found) data = replay;
18814
- } else if (kind === "outcome") {
18815
- fullOutcome = computeOutcome(id);
18816
- if (fullOutcome.found) data = fullOutcome;
18817
- } else if (kind === "commits") {
18818
- if (!fullOutcome) fullOutcome = computeOutcome(id);
18819
- if (fullOutcome.found) data = fullOutcome.commits;
18820
- } else if (kind === "markers") {
18821
- const turns = extractTurnsAny(id, { maxTurns: 5e4 });
18822
- const prompts = turns.turns.filter((t) => t.kind === "user" && t.text).map((t) => ({ line: t.line, ts: t.ts, tsIso: t.tsIso, ...markPrompt(t.text) }));
18823
- data = { sessionId: id, prompts, summary: summarizeMarkers(prompts) };
18824
- }
18825
- } catch {
18826
- data = null;
19093
+ return withSessionReadCache(() => {
19094
+ for (const kind of COMPUTE_KINDS) {
19095
+ let data = null;
19096
+ try {
19097
+ if (kind === "diff") {
19098
+ const replay = replaySessionAny(id);
19099
+ if (replay.found) data = replay;
19100
+ } else if (kind === "outcome") {
19101
+ fullOutcome = computeOutcome(id);
19102
+ if (fullOutcome.found) data = fullOutcome;
19103
+ } else if (kind === "commits") {
19104
+ if (!fullOutcome) fullOutcome = computeOutcome(id);
19105
+ if (fullOutcome.found) data = fullOutcome.commits;
19106
+ } else if (kind === "markers") {
19107
+ const turns = extractTurnsAny(id, { maxTurns: 5e4 });
19108
+ const prompts = turns.turns.filter((t) => t.kind === "user" && t.text).map((t) => ({ line: t.line, ts: t.ts, tsIso: t.tsIso, ...markPrompt(t.text) }));
19109
+ data = { sessionId: id, prompts, summary: summarizeMarkers(prompts) };
19110
+ }
19111
+ } catch {
19112
+ data = null;
19113
+ }
19114
+ if (data === null) continue;
19115
+ const redacted = redactDeep(data, count);
19116
+ if (JSON.stringify(redacted).length > MAX_DERIVED_ROW_BYTES) continue;
19117
+ compute.push({ kind, mtime, data: redacted });
19118
+ }
19119
+ let outcomeRow = null;
19120
+ if (fullOutcome?.found) {
19121
+ outcomeRow = {
19122
+ sessionId: id,
19123
+ tool: ref.toolId,
19124
+ status: fullOutcome.status,
19125
+ reason: fullOutcome.reason,
19126
+ fileMtime: mtime,
19127
+ fileSize: 0,
19128
+ contentHash: "",
19129
+ fileCount: fullOutcome.fileCount,
19130
+ linesAdded: fullOutcome.totalLinesAdded,
19131
+ linesRemoved: fullOutcome.totalLinesRemoved,
19132
+ commits: fullOutcome.commits?.totalCommits ?? 0,
19133
+ isFull: true,
19134
+ classifiedAt: Date.now(),
19135
+ lastScannedOffset: 0
19136
+ };
18827
19137
  }
18828
- if (data === null) continue;
18829
- const redacted = redactDeep(data, count);
18830
- if (JSON.stringify(redacted).length > MAX_DERIVED_ROW_BYTES) continue;
18831
- compute.push({ kind, mtime, data: redacted });
18832
- }
18833
- let outcomeRow = null;
18834
- if (fullOutcome?.found) {
18835
- outcomeRow = {
18836
- sessionId: id,
18837
- tool: ref.toolId,
18838
- status: fullOutcome.status,
18839
- reason: fullOutcome.reason,
18840
- fileMtime: mtime,
18841
- fileSize: 0,
18842
- contentHash: "",
18843
- fileCount: fullOutcome.fileCount,
18844
- linesAdded: fullOutcome.totalLinesAdded,
18845
- linesRemoved: fullOutcome.totalLinesRemoved,
18846
- commits: fullOutcome.commits?.totalCommits ?? 0,
18847
- isFull: true,
18848
- classifiedAt: Date.now(),
18849
- lastScannedOffset: 0
19138
+ return {
19139
+ session_id: id,
19140
+ mtime,
19141
+ compute,
19142
+ outcome_row: outcomeRow ? redactDeep({ ...outcomeRow, sessionId: void 0 }, count) : null
18850
19143
  };
18851
- }
18852
- return {
18853
- session_id: id,
18854
- mtime,
18855
- compute,
18856
- outcome_row: outcomeRow ? redactDeep({ ...outcomeRow, sessionId: void 0 }, count) : null
18857
- };
19144
+ });
18858
19145
  }
18859
19146
  function isSyncSkip(r) {
18860
19147
  return typeof r.skipped === "string";
@@ -18883,10 +19170,11 @@ async function syncIncremental(opts = {}) {
18883
19170
  lock.release();
18884
19171
  }
18885
19172
  }
18886
- var ownVersion, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, hashPath, COMPUTE_KINDS, MAX_DERIVED_ROW_BYTES, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
19173
+ var ownVersion, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, hashPath, COMPUTE_KINDS, MAX_DERIVED_ROW_BYTES, CAPS_TTL_MS, capsOk, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
18887
19174
  var init_sync_client = __esm({
18888
19175
  "src/sync-client.ts"() {
18889
19176
  "use strict";
19177
+ init_credentials_env();
18890
19178
  init_secret_redactor();
18891
19179
  init_source_discovery();
18892
19180
  init_home_discovery();
@@ -18914,14 +19202,15 @@ var init_sync_client = __esm({
18914
19202
  init_sync_fields();
18915
19203
  init_index_lock();
18916
19204
  init_http();
19205
+ init_live_session_scan();
18917
19206
  init_backends();
18918
19207
  ownVersion = "0.0.0";
18919
19208
  try {
18920
19209
  const dir = dirname16(fileURLToPath(import.meta.url));
18921
- ownVersion = JSON.parse(readFileSync33(join40(dir, "../package.json"), "utf-8")).version || ownVersion;
19210
+ ownVersion = JSON.parse(readFileSync32(join40(dir, "../package.json"), "utf-8")).version || ownVersion;
18922
19211
  } catch {
18923
19212
  try {
18924
- if (true) ownVersion = "0.5.5";
19213
+ if (true) ownVersion = "0.5.6";
18925
19214
  } catch {
18926
19215
  }
18927
19216
  }
@@ -18932,6 +19221,8 @@ var init_sync_client = __esm({
18932
19221
  hashPath = (p) => p ? "p_" + createHash12("sha256").update(p).digest("hex").slice(0, 12) : "";
18933
19222
  COMPUTE_KINDS = ["diff", "outcome", "commits", "markers"];
18934
19223
  MAX_DERIVED_ROW_BYTES = 2 * 1024 * 1024;
19224
+ CAPS_TTL_MS = 60 * 6e4;
19225
+ capsOk = /* @__PURE__ */ new Map();
18935
19226
  FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "64", 10)) * 1024 * 1024;
18936
19227
  FULL_BUILD_TAIL_BYTES = 16 * 1024 * 1024;
18937
19228
  BATCH_SCAN_MAX_BYTES = Math.max(1, parseInt(process.env.CHAT_RECALL_BATCHSCAN_MAX_MB || "64", 10)) * 1024 * 1024;
@@ -18988,11 +19279,11 @@ async function repairSession(id, opts = {}) {
18988
19279
  const log6 = (m) => {
18989
19280
  if (opts.verbose) console.error(` [repair ${id}] ${m}`);
18990
19281
  };
18991
- const candidates = [];
19282
+ const candidates2 = [];
18992
19283
  const localShadow = readShadowContainer(tool, rawId);
18993
19284
  if (localShadow) {
18994
19285
  try {
18995
- candidates.push({ container: localShadow, messages: parseTranscriptFromContainer(localShadow).messages.length, source: "local-shadow" });
19286
+ candidates2.push({ container: localShadow, messages: parseTranscriptFromContainer(localShadow).messages.length, source: "local-shadow" });
18996
19287
  } catch {
18997
19288
  }
18998
19289
  }
@@ -19004,15 +19295,15 @@ async function repairSession(id, opts = {}) {
19004
19295
  if (arch?.gzB64) {
19005
19296
  try {
19006
19297
  const container = gunzipContainer(Buffer.from(arch.gzB64, "base64"));
19007
- if (container) candidates.push({ container, messages: parseTranscriptFromContainer(container).messages.length, source: ep.url });
19298
+ if (container) candidates2.push({ container, messages: parseTranscriptFromContainer(container).messages.length, source: ep.url });
19008
19299
  } catch {
19009
19300
  }
19010
19301
  }
19011
19302
  }
19012
- if (candidates.length === 0) {
19303
+ if (candidates2.length === 0) {
19013
19304
  return { sessionId: id, status: "no-archive", fullestMessages: 0, fullestSource: "", pushed: [], note: "no shadow or server archive found" };
19014
19305
  }
19015
- const fullest = candidates.reduce((a, b) => b.messages > a.messages ? b : a);
19306
+ const fullest = candidates2.reduce((a, b) => b.messages > a.messages ? b : a);
19016
19307
  log6(`fullest archive = ${fullest.messages} msg(s) from ${fullest.source}`);
19017
19308
  if (opts.dryRun) {
19018
19309
  const needy2 = endpoints.filter((ep) => (currentOnServer.get(ep.url) ?? 0) < fullest.messages || opts.force);
@@ -19114,7 +19405,7 @@ async function repairAll(opts = {}) {
19114
19405
  if (!page?.hasMore || sessions.length === 0) break;
19115
19406
  }
19116
19407
  log6(`window=${sinceHours}h \u2192 ${ids.length} session(s) to check on ${primary.url}`);
19117
- const candidates = [];
19408
+ const candidates2 = [];
19118
19409
  let scanned = 0;
19119
19410
  for (const id of ids) {
19120
19411
  scanned++;
@@ -19127,9 +19418,9 @@ async function repairAll(opts = {}) {
19127
19418
  if (archiveMsgs <= current) continue;
19128
19419
  const r = await repairSession(id, { dryRun: !opts.apply, verbose: opts.verbose, server: opts.server });
19129
19420
  if (r.status === "already-full") continue;
19130
- candidates.push(r);
19421
+ candidates2.push(r);
19131
19422
  }
19132
- return { scanned, candidates, discoveryServer: primary.url };
19423
+ return { scanned, candidates: candidates2, discoveryServer: primary.url };
19133
19424
  }
19134
19425
  var hashPath2;
19135
19426
  var init_repair = __esm({
@@ -19145,23 +19436,93 @@ var init_repair = __esm({
19145
19436
  }
19146
19437
  });
19147
19438
 
19439
+ // ../engine/dist/core/which.js
19440
+ import { existsSync as existsSync43, statSync as statSync38, accessSync, constants } from "node:fs";
19441
+ import { join as join46, isAbsolute as isAbsolute2, delimiter as delimiter2, sep as sep2 } from "node:path";
19442
+ function executable(p) {
19443
+ try {
19444
+ if (!statSync38(p).isFile())
19445
+ return false;
19446
+ } catch {
19447
+ return false;
19448
+ }
19449
+ if (isWindows)
19450
+ return true;
19451
+ try {
19452
+ accessSync(p, constants.X_OK);
19453
+ return true;
19454
+ } catch {
19455
+ return false;
19456
+ }
19457
+ }
19458
+ function candidates(name) {
19459
+ if (!isWindows)
19460
+ return [name];
19461
+ const exts = (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
19462
+ if (exts.some((e) => name.toLowerCase().endsWith(e.toLowerCase())))
19463
+ return [name];
19464
+ return exts.map((e) => name + e);
19465
+ }
19466
+ function resolveOnPath(name) {
19467
+ if (!name)
19468
+ return null;
19469
+ if (name.includes("/") || isWindows && name.includes(sep2) || isAbsolute2(name)) {
19470
+ for (const c of candidates(name))
19471
+ if (executable(c))
19472
+ return c;
19473
+ return null;
19474
+ }
19475
+ for (const raw of (process.env.PATH || "").split(delimiter2)) {
19476
+ const dir = raw.replace(/^"(.*)"$/, "$1");
19477
+ if (!dir)
19478
+ continue;
19479
+ for (const c of candidates(name)) {
19480
+ const full = join46(dir, c);
19481
+ if (existsSync43(full) && executable(full))
19482
+ return full;
19483
+ }
19484
+ }
19485
+ return null;
19486
+ }
19487
+ function isOnPath(name) {
19488
+ return resolveOnPath(name) !== null;
19489
+ }
19490
+ var isWindows;
19491
+ var init_which = __esm({
19492
+ "../engine/dist/core/which.js"() {
19493
+ "use strict";
19494
+ isWindows = process.platform === "win32";
19495
+ }
19496
+ });
19497
+
19148
19498
  // ../engine/dist/core/companions.js
19149
- import { existsSync as existsSync43, mkdirSync as mkdirSync12, statSync as statSync38, chmodSync as chmodSync4, unlinkSync as unlinkSync3, renameSync as renameSync5, createWriteStream, readFileSync as readFileSync38, writeFileSync as writeFileSync12 } from "node:fs";
19499
+ import { existsSync as existsSync44, mkdirSync as mkdirSync13, statSync as statSync39, chmodSync as chmodSync4, unlinkSync as unlinkSync3, renameSync as renameSync5, createWriteStream, readFileSync as readFileSync38, writeFileSync as writeFileSync13 } from "node:fs";
19150
19500
  import { homedir as homedir19 } from "node:os";
19151
- import { join as join45 } from "node:path";
19501
+ import { join as join47 } from "node:path";
19152
19502
  import { execSync as execSync3 } from "node:child_process";
19503
+ import { createHash as createHash14 } from "node:crypto";
19504
+ function expectedCodeindexSha256(artifact) {
19505
+ return CODEINDEX_SHA256[artifact.replace(/^codeindex-/, "").replace(/\.exe$/, "")] ?? null;
19506
+ }
19153
19507
  function detectPlatform() {
19154
19508
  const arch = process.arch;
19155
19509
  const platform = process.platform;
19156
19510
  const archMap = { x64: "x86_64", arm64: "aarch64" };
19157
- const platformMap = { linux: "linux", darwin: "darwin", win32: "windows" };
19511
+ const platformMap = { linux: "linux", darwin: "macos", win32: "windows" };
19158
19512
  const a = archMap[arch];
19159
19513
  const p = platformMap[platform];
19160
19514
  if (!a || !p) {
19161
19515
  return { reason: `Unsupported platform: ${arch}-${platform}` };
19162
19516
  }
19163
- const prebuiltCombos = /* @__PURE__ */ new Set(["x86_64-linux"]);
19164
- const artifact = `codeindex-${a}-${p}`;
19517
+ const prebuiltCombos = /* @__PURE__ */ new Set([
19518
+ "x86_64-linux",
19519
+ "aarch64-linux",
19520
+ "x86_64-macos",
19521
+ "aarch64-macos",
19522
+ "x86_64-windows",
19523
+ "aarch64-windows"
19524
+ ]);
19525
+ const artifact = `codeindex-${a}-${p}${p === "windows" ? ".exe" : ""}`;
19165
19526
  if (!prebuiltCombos.has(`${a}-${p}`)) {
19166
19527
  return {
19167
19528
  artifact,
@@ -19178,20 +19539,15 @@ function checkCodeindexStatus() {
19178
19539
  artifactName: platform.artifact,
19179
19540
  unsupportedReason: platform.reason
19180
19541
  };
19181
- let foundPath = null;
19182
- try {
19183
- const out = execSync3("command -v codeindex", { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
19184
- if (out)
19185
- foundPath = out;
19186
- } catch {
19542
+ let foundPath = resolveOnPath(CODEINDEX_BIN_NAME);
19543
+ if (!foundPath) {
19544
+ foundPath = resolveOnPath(CODEINDEX_BIN_PATH);
19187
19545
  }
19188
- if (!foundPath && existsSync43(CODEINDEX_BIN_PATH))
19189
- foundPath = CODEINDEX_BIN_PATH;
19190
19546
  if (foundPath) {
19191
19547
  status.installed = true;
19192
19548
  status.path = foundPath;
19193
19549
  try {
19194
- status.size = statSync38(foundPath).size;
19550
+ status.size = statSync39(foundPath).size;
19195
19551
  } catch {
19196
19552
  }
19197
19553
  try {
@@ -19203,16 +19559,9 @@ function checkCodeindexStatus() {
19203
19559
  return status;
19204
19560
  }
19205
19561
  async function downloadArtifact(artifact, dest) {
19206
- const hasGh = (() => {
19207
- try {
19208
- execSync3("command -v gh", { stdio: "ignore" });
19209
- return true;
19210
- } catch {
19211
- return false;
19212
- }
19213
- })();
19562
+ const hasGh = isOnPath("gh");
19214
19563
  if (hasGh) {
19215
- execSync3(`gh release download ${CODEINDEX_VERSION} --repo ${CODEINDEX_REPO} -p ${artifact} -O ${dest}`, {
19564
+ execSync3(`gh release download ${CODEINDEX_VERSION} --repo ${CODEINDEX_REPO} -p ${artifact} -O "${dest}"`, {
19216
19565
  stdio: ["ignore", "pipe", "pipe"],
19217
19566
  timeout: 6e4
19218
19567
  });
@@ -19225,7 +19574,7 @@ async function downloadArtifact(artifact, dest) {
19225
19574
  if (!res.body)
19226
19575
  throw new Error("Download returned empty body");
19227
19576
  const file = createWriteStream(dest);
19228
- await new Promise((resolve2, reject) => {
19577
+ await new Promise((resolve3, reject) => {
19229
19578
  const reader = res.body.getReader();
19230
19579
  const pump = async () => {
19231
19580
  try {
@@ -19236,7 +19585,7 @@ async function downloadArtifact(artifact, dest) {
19236
19585
  file.write(value);
19237
19586
  }
19238
19587
  file.end();
19239
- file.on("finish", () => resolve2());
19588
+ file.on("finish", () => resolve3());
19240
19589
  file.on("error", reject);
19241
19590
  } catch (e) {
19242
19591
  reject(e);
@@ -19251,15 +19600,26 @@ async function installCodeindex(opts = {}) {
19251
19600
  return status;
19252
19601
  if (!status.prebuiltAvailable)
19253
19602
  return status;
19254
- mkdirSync12(CODEINDEX_INSTALL_DIR, { recursive: true });
19603
+ mkdirSync13(CODEINDEX_INSTALL_DIR, { recursive: true });
19255
19604
  const tmpPath = `${CODEINDEX_BIN_PATH}.partial`;
19256
19605
  try {
19257
19606
  await downloadArtifact(status.artifactName, tmpPath);
19607
+ const expected = expectedCodeindexSha256(status.artifactName);
19608
+ if (!expected) {
19609
+ throw new Error(`No pinned checksum for ${status.artifactName} \u2014 refusing to install an unverified binary. Add its sha256 to CODEINDEX_SHA256 in companions.ts.`);
19610
+ }
19611
+ const actual = createHash14("sha256").update(readFileSync38(tmpPath)).digest("hex");
19612
+ if (actual !== expected) {
19613
+ throw new Error(`Checksum mismatch for ${status.artifactName}.
19614
+ expected ${expected}
19615
+ actual ${actual}
19616
+ Refusing to install. The release may have been re-uploaded, or something is serving different bytes.`);
19617
+ }
19258
19618
  chmodSync4(tmpPath, 493);
19259
19619
  renameSync5(tmpPath, CODEINDEX_BIN_PATH);
19260
19620
  } catch (err) {
19261
19621
  try {
19262
- if (existsSync43(tmpPath))
19622
+ if (existsSync44(tmpPath))
19263
19623
  unlinkSync3(tmpPath);
19264
19624
  } catch {
19265
19625
  }
@@ -19267,15 +19627,24 @@ async function installCodeindex(opts = {}) {
19267
19627
  }
19268
19628
  return checkCodeindexStatus();
19269
19629
  }
19270
- var CODEINDEX_REPO, CODEINDEX_VERSION, CODEINDEX_INSTALL_DIR, CODEINDEX_BIN_NAME, CODEINDEX_BIN_PATH;
19630
+ var CODEINDEX_REPO, CODEINDEX_VERSION, CODEINDEX_SHA256, CODEINDEX_INSTALL_DIR, CODEINDEX_BIN_NAME, CODEINDEX_BIN_PATH;
19271
19631
  var init_companions = __esm({
19272
19632
  "../engine/dist/core/companions.js"() {
19273
19633
  "use strict";
19634
+ init_which();
19274
19635
  CODEINDEX_REPO = "munhq/codeindex";
19275
- CODEINDEX_VERSION = "v0.1.0";
19276
- CODEINDEX_INSTALL_DIR = join45(homedir19(), ".local", "bin");
19277
- CODEINDEX_BIN_NAME = "codeindex";
19278
- CODEINDEX_BIN_PATH = join45(CODEINDEX_INSTALL_DIR, CODEINDEX_BIN_NAME);
19636
+ CODEINDEX_VERSION = "v0.3.1";
19637
+ CODEINDEX_SHA256 = {
19638
+ "x86_64-linux": "4378f12e7c80fa8bb53e41c02db4b95955f2f878369abfb28b5edd94b4adbb85",
19639
+ "aarch64-linux": "5a1d6c9c2592a3721547c42cd9b6b11f460e903bfc46407ae27d5879e7259587",
19640
+ "x86_64-macos": "6ad2a2b40faa4ec72ac35fee84f35ea7c848cddc4f6382b4cafedb644f30389f",
19641
+ "aarch64-macos": "f366d00918f79a4a0acc09c6091d97c6c378c79edbdbe3cb22e4014ea57f08d2",
19642
+ "x86_64-windows": "a96fc8c8949bb399a44cc0615b12e458460a529c69f01e8829cc39a20f45ab29",
19643
+ "aarch64-windows": "42620d56272cd9ba9d785569d8d754d6b4a3e80f36f28db3e0c483fd600ed117"
19644
+ };
19645
+ CODEINDEX_INSTALL_DIR = join47(homedir19(), ".local", "bin");
19646
+ CODEINDEX_BIN_NAME = process.platform === "win32" ? "codeindex.exe" : "codeindex";
19647
+ CODEINDEX_BIN_PATH = join47(CODEINDEX_INSTALL_DIR, CODEINDEX_BIN_NAME);
19279
19648
  }
19280
19649
  });
19281
19650
 
@@ -19381,7 +19750,9 @@ var init_prompts = __esm({
19381
19750
  var collector_exports = {};
19382
19751
  __export(collector_exports, {
19383
19752
  COLLECTOR_VERSION: () => COLLECTOR_VERSION,
19753
+ GENERATED_PATTERNS: () => GENERATED_PATTERNS,
19384
19754
  collectCode: () => collectCode,
19755
+ isGenerated: () => isGenerated,
19385
19756
  isJunkWorkspacePath: () => isJunkWorkspacePath,
19386
19757
  isSecretRule: () => isSecretRule,
19387
19758
  resolveCodeindexBin: () => resolveCodeindexBin
@@ -19389,7 +19760,7 @@ __export(collector_exports, {
19389
19760
  import { spawn as spawn2 } from "node:child_process";
19390
19761
  import { execSync as execSync4 } from "node:child_process";
19391
19762
  import { readFileSync as readFileSync39 } from "node:fs";
19392
- import { join as join46, basename as basename24 } from "node:path";
19763
+ import { join as join48, basename as basename24 } from "node:path";
19393
19764
  async function resolveCodeindexBin(autoInstall = true) {
19394
19765
  let st = checkCodeindexStatus();
19395
19766
  if (!st.installed && autoInstall)
@@ -19400,7 +19771,7 @@ async function resolveCodeindexBin(autoInstall = true) {
19400
19771
  return st.path;
19401
19772
  }
19402
19773
  function runMcp(binPath, workspace, calls, timeoutMs = 18e4) {
19403
- return new Promise((resolve2, reject) => {
19774
+ return new Promise((resolve3, reject) => {
19404
19775
  const p = spawn2(binPath, ["--mcp", "--workspace", workspace], { stdio: ["pipe", "pipe", "ignore"] });
19405
19776
  let out = "";
19406
19777
  const timer = setTimeout(() => {
@@ -19443,7 +19814,7 @@ function runMcp(binPath, workspace, calls, timeoutMs = 18e4) {
19443
19814
  if (m?.id != null && typeof text === "string")
19444
19815
  res.set(Number(m.id), text);
19445
19816
  }
19446
- resolve2(res);
19817
+ resolve3(res);
19447
19818
  });
19448
19819
  p.stdin.write(calls.map((c) => JSON.stringify(c) + "\n").join(""));
19449
19820
  p.stdin.end();
@@ -19476,10 +19847,11 @@ function isSecretRule(rule) {
19476
19847
  return /secret|password|credential|key|token|auth/i.test(rule);
19477
19848
  }
19478
19849
  function isJunkWorkspacePath(ws) {
19479
- return /(^|\/)(scratchpad|\.cache|node_modules|\.git|tmp)(\/|$)|claude-1000/i.test(ws);
19850
+ const norm = ws.replace(/\\/g, "/");
19851
+ return /(^|\/)(scratchpad|\.cache|node_modules|\.git|tmp)(\/|$)|claude-1000/i.test(norm);
19480
19852
  }
19481
19853
  async function collectCode(opts) {
19482
- const ws = opts.workspace.replace(/\/+$/, "");
19854
+ const ws = opts.workspace.replace(/[/\\]+$/, "");
19483
19855
  const log6 = opts.log ?? (() => {
19484
19856
  });
19485
19857
  if (isJunkWorkspacePath(ws)) {
@@ -19487,9 +19859,11 @@ async function collectCode(opts) {
19487
19859
  }
19488
19860
  const bin = opts.binPath ?? await resolveCodeindexBin(opts.autoInstall ?? true);
19489
19861
  const projectId = resolveProjectId(ws).id || `path:${ws}`;
19490
- const wsPrefix = ws + "/";
19862
+ const wsNorm = ws.replace(/\\/g, "/");
19863
+ const wsPrefix = wsNorm + "/";
19491
19864
  const rel = (p) => {
19492
- let r = p.startsWith(wsPrefix) ? p.slice(wsPrefix.length) : p;
19865
+ const n = p.replace(/\\/g, "/");
19866
+ let r = n.startsWith(wsPrefix) ? n.slice(wsPrefix.length) : n;
19493
19867
  if (r.startsWith("./"))
19494
19868
  r = r.slice(2);
19495
19869
  return r;
@@ -19550,7 +19924,7 @@ async function collectCode(opts) {
19550
19924
  }
19551
19925
  const complexity = (r) => {
19552
19926
  try {
19553
- const txt = readFileSync39(join46(ws, r), "utf8");
19927
+ const txt = readFileSync39(join48(ws, r), "utf8");
19554
19928
  if (txt.length > 2e6)
19555
19929
  return 1;
19556
19930
  const m = txt.match(CF_RE);
@@ -20134,7 +20508,7 @@ async function collectCode(opts) {
20134
20508
  void health;
20135
20509
  return { project, findings, hotspots, actions };
20136
20510
  }
20137
- var COLLECTOR_VERSION, ANALYSES, GENERATED, CF_RE, DEPTH, MCP_OUT_MAX_BYTES, call, COMMON_SYMBOL_NAMES;
20511
+ var COLLECTOR_VERSION, ANALYSES, GENERATED, CF_RE, DEPTH, MCP_OUT_MAX_BYTES, call, GENERATED_PATTERNS, COMMON_SYMBOL_NAMES;
20138
20512
  var init_collector = __esm({
20139
20513
  "../engine/dist/core/code/collector.js"() {
20140
20514
  "use strict";
@@ -20176,7 +20550,13 @@ var init_collector = __esm({
20176
20550
  "/vendor/",
20177
20551
  "/third_party/",
20178
20552
  "/site-packages/",
20553
+ // Lockfiles. '.lock' alone catches yarn.lock, Cargo.lock, poetry.lock and
20554
+ // composer.lock, and MISSES the two biggest ones in a JS repo —
20555
+ // package-lock.json and pnpm-lock.yaml are named '-lock.<ext>'. Found by
20556
+ // replacing this file's mirror test with one that calls the real predicate.
20179
20557
  ".lock",
20558
+ "-lock.json",
20559
+ "-lock.yaml",
20180
20560
  "/dist/",
20181
20561
  "/build/",
20182
20562
  "/out/",
@@ -20193,6 +20573,7 @@ var init_collector = __esm({
20193
20573
  DEPTH = 2;
20194
20574
  MCP_OUT_MAX_BYTES = 64 * 1024 * 1024;
20195
20575
  call = (id, name, args = {}) => ({ jsonrpc: "2.0", id, method: "tools/call", params: { name, arguments: args } });
20576
+ GENERATED_PATTERNS = GENERATED;
20196
20577
  COMMON_SYMBOL_NAMES = /* @__PURE__ */ new Set([
20197
20578
  "error",
20198
20579
  "err",
@@ -20264,12 +20645,13 @@ init_settings();
20264
20645
  init_sync_client();
20265
20646
  init_http();
20266
20647
  import chokidar from "chokidar";
20267
- import { dirname as dirname20, basename as basename25, join as join47 } from "path";
20648
+ import { dirname as dirname21, basename as basename25, join as join49, resolve as resolve2, sep as sep3 } from "path";
20649
+ import { homedir as homedir20 } from "os";
20268
20650
 
20269
20651
  // src/intent-drain.ts
20270
20652
  init_http();
20271
20653
  init_sync_client();
20272
- import { existsSync as existsSync41, readFileSync as readFileSync37, writeFileSync as writeFileSync11, appendFileSync, mkdirSync as mkdirSync11 } from "node:fs";
20654
+ import { existsSync as existsSync41, readFileSync as readFileSync36, writeFileSync as writeFileSync11, appendFileSync, mkdirSync as mkdirSync11 } from "node:fs";
20273
20655
  import { join as join44 } from "node:path";
20274
20656
  import { homedir as homedir18 } from "node:os";
20275
20657
 
@@ -20279,7 +20661,7 @@ init_gemini2();
20279
20661
  init_opencode();
20280
20662
  init_codex2();
20281
20663
  init_agy();
20282
- import { existsSync as existsSync39, mkdirSync as mkdirSync9, readFileSync as readFileSync35, writeFileSync as writeFileSync9, cpSync } from "fs";
20664
+ import { existsSync as existsSync39, mkdirSync as mkdirSync9, readFileSync as readFileSync34, writeFileSync as writeFileSync9, cpSync } from "fs";
20283
20665
  import { dirname as dirname17, join as join42, basename as basename23 } from "path";
20284
20666
  import { homedir as homedir16 } from "os";
20285
20667
 
@@ -20290,11 +20672,11 @@ init_opencode();
20290
20672
  init_codex2();
20291
20673
  init_agy();
20292
20674
  init_toolkit_format();
20293
- import { readFileSync as readFileSync34 } from "fs";
20675
+ import { readFileSync as readFileSync33 } from "fs";
20294
20676
  import { join as join41 } from "path";
20295
20677
  import { homedir as homedir15 } from "os";
20296
20678
  function readCommand(filePath, format) {
20297
- const content = readFileSync34(filePath, "utf-8");
20679
+ const content = readFileSync33(filePath, "utf-8");
20298
20680
  if (format === "toml") {
20299
20681
  const t = parseScalarToml(content);
20300
20682
  return { name: t.name || base(filePath), description: t.description || "", body: t.prompt || "" };
@@ -20303,7 +20685,7 @@ function readCommand(filePath, format) {
20303
20685
  return { name: fm.name || base(filePath), description: fm.description || "", body: body.trim() };
20304
20686
  }
20305
20687
  function readAgent(filePath, format) {
20306
- const content = readFileSync34(filePath, "utf-8");
20688
+ const content = readFileSync33(filePath, "utf-8");
20307
20689
  if (format === "toml") {
20308
20690
  const t = parseScalarToml(content);
20309
20691
  return {
@@ -20316,7 +20698,7 @@ function readAgent(filePath, format) {
20316
20698
  return { name: fm.name || base(filePath), description: fm.description || "", tools: fm.tools, body: body.trim() };
20317
20699
  }
20318
20700
  function readInstructions(filePath, name) {
20319
- const content = readFileSync34(filePath, "utf-8");
20701
+ const content = readFileSync33(filePath, "utf-8");
20320
20702
  return { name, description: "", body: content };
20321
20703
  }
20322
20704
  function commandPath(tool, name) {
@@ -20503,7 +20885,7 @@ function readMcpEntry(tool, name) {
20503
20885
  if (!existsSync39(t.path))
20504
20886
  continue;
20505
20887
  try {
20506
- const cfg = JSON.parse(readFileSync35(t.path, "utf-8"));
20888
+ const cfg = JSON.parse(readFileSync34(t.path, "utf-8"));
20507
20889
  const block = cfg[t.key] || {};
20508
20890
  if (block[name])
20509
20891
  return block[name];
@@ -20517,7 +20899,7 @@ function readCodexMcpEntry(path, target) {
20517
20899
  return null;
20518
20900
  let content;
20519
20901
  try {
20520
- content = readFileSync35(path, "utf-8");
20902
+ content = readFileSync34(path, "utf-8");
20521
20903
  } catch {
20522
20904
  return null;
20523
20905
  }
@@ -20568,7 +20950,7 @@ function readCodexMcpEntry(path, target) {
20568
20950
  function writeCodexMcpEntry(path, name, entry) {
20569
20951
  let content = "";
20570
20952
  if (existsSync39(path))
20571
- content = readFileSync35(path, "utf-8");
20953
+ content = readFileSync34(path, "utf-8");
20572
20954
  const header = `[mcp_servers.${name}]`;
20573
20955
  if (content.includes(header))
20574
20956
  return false;
@@ -20627,7 +21009,7 @@ function writeMcpEntry(toTool, name, entry) {
20627
21009
  let cfg = {};
20628
21010
  if (existsSync39(path)) {
20629
21011
  try {
20630
- cfg = JSON.parse(readFileSync35(path, "utf-8"));
21012
+ cfg = JSON.parse(readFileSync34(path, "utf-8"));
20631
21013
  } catch {
20632
21014
  return { ok: false, status: 500, error: `Target config is malformed: ${path}` };
20633
21015
  }
@@ -20814,7 +21196,7 @@ async function executeCopy(type, name, fromTool, toTool) {
20814
21196
 
20815
21197
  // src/project-tasks.ts
20816
21198
  init_http();
20817
- import { existsSync as existsSync40, readFileSync as readFileSync36, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10, statSync as statSync36 } from "node:fs";
21199
+ import { existsSync as existsSync40, readFileSync as readFileSync35, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10, statSync as statSync36 } from "node:fs";
20818
21200
  import { join as join43, dirname as dirname18 } from "node:path";
20819
21201
  import { homedir as homedir17 } from "node:os";
20820
21202
 
@@ -20831,7 +21213,7 @@ function isCodeTaskStatus(v) {
20831
21213
  }
20832
21214
 
20833
21215
  // src/project-tasks.ts
20834
- var TEAM_STATUSES = /* @__PURE__ */ new Set(["todo", "in_progress", "blocked", "done"]);
21216
+ var TEAM_STATUSES = /* @__PURE__ */ new Set(["todo", "in_progress", "done", "rejected"]);
20835
21217
  function isTeamTaskStatus(v) {
20836
21218
  return !!v && TEAM_STATUSES.has(v);
20837
21219
  }
@@ -20849,7 +21231,7 @@ function reconcile(kind, was, token, checked) {
20849
21231
  const done = kind === "secret" ? "rotated" : "done";
20850
21232
  if (token && token !== was) return token;
20851
21233
  if (kind === "team") {
20852
- if (checked && was !== "done") return "done";
21234
+ if (checked && was === "todo") return "in_progress";
20853
21235
  if (!checked && was === "done") return "todo";
20854
21236
  return token ?? was;
20855
21237
  }
@@ -20888,7 +21270,7 @@ function ledgerPath2() {
20888
21270
  }
20889
21271
  function readLedger() {
20890
21272
  try {
20891
- return JSON.parse(readFileSync36(ledgerPath2(), "utf8"));
21273
+ return JSON.parse(readFileSync35(ledgerPath2(), "utf8"));
20892
21274
  } catch {
20893
21275
  return {};
20894
21276
  }
@@ -20932,7 +21314,7 @@ async function pushProjectTaskStatuses(base2, authHeaders, opts = {}) {
20932
21314
  if (ledger[file] === mtime) continue;
20933
21315
  let changed;
20934
21316
  try {
20935
- changed = parseTaskFile(readFileSync36(file, "utf8")).filter((t) => t.kind === fk.kind);
21317
+ changed = parseTaskFile(readFileSync35(file, "utf8")).filter((t) => t.kind === fk.kind);
20936
21318
  } catch {
20937
21319
  continue;
20938
21320
  }
@@ -20989,7 +21371,7 @@ function applyCodeRecommendation(intent) {
20989
21371
  if (!meta.rootPath) return { status: "error", result: JSON.stringify({ error: "missing rootPath" }) };
20990
21372
  file = join44(meta.rootPath, "CLAUDE.md");
20991
21373
  }
20992
- const existing = existsSync41(file) ? readFileSync37(file, "utf8") : "";
21374
+ const existing = existsSync41(file) ? readFileSync36(file, "utf8") : "";
20993
21375
  if (existing.includes(text)) return { status: "done", result: JSON.stringify({ skipped: "rule already present", file }) };
20994
21376
  const block = `${existing && !existing.endsWith("\n") ? "\n" : ""}
20995
21377
  ## Rule (added by chat-recall recommendation)
@@ -21035,10 +21417,10 @@ async function runIntent(intent, ctx) {
21035
21417
  var ownVersion2 = "0.0.0";
21036
21418
  try {
21037
21419
  const dir = dirname19(fileURLToPath2(import.meta.url));
21038
- ownVersion2 = JSON.parse(readFileSync37(join44(dir, "../package.json"), "utf-8")).version || ownVersion2;
21420
+ ownVersion2 = JSON.parse(readFileSync36(join44(dir, "../package.json"), "utf-8")).version || ownVersion2;
21039
21421
  } catch {
21040
21422
  try {
21041
- if (true) ownVersion2 = "0.5.5";
21423
+ if (true) ownVersion2 = "0.5.6";
21042
21424
  } catch {
21043
21425
  }
21044
21426
  }
@@ -21212,8 +21594,37 @@ async function runCollectorMigration(opts) {
21212
21594
  }
21213
21595
 
21214
21596
  // auto-indexer/indexer.ts
21215
- import { existsSync as existsSync44, readFileSync as readFileSync40 } from "fs";
21216
- var CLI_VERSION3 = true ? "0.5.5" : "0.0.0";
21597
+ init_sync_ledger();
21598
+ import { existsSync as existsSync45, readFileSync as readFileSync40, statSync as statSync40, renameSync as renameSync6, unlinkSync as unlinkSync4 } from "fs";
21599
+
21600
+ // ../engine/dist/core/collector-health.js
21601
+ init_paths();
21602
+ import { readFileSync as readFileSync37, writeFileSync as writeFileSync12, mkdirSync as mkdirSync12 } from "node:fs";
21603
+ import { join as join45, dirname as dirname20 } from "node:path";
21604
+ var healthPath = () => join45(getDataDir(), "collector-health.json");
21605
+ function collectorHealthPath() {
21606
+ return healthPath();
21607
+ }
21608
+ function readCollectorHealth() {
21609
+ try {
21610
+ const h = JSON.parse(readFileSync37(healthPath(), "utf-8"));
21611
+ return h && h.v === 1 && typeof h.updatedAt === "number" ? h : null;
21612
+ } catch {
21613
+ return null;
21614
+ }
21615
+ }
21616
+ function writeCollectorHealth(h) {
21617
+ try {
21618
+ const p = healthPath();
21619
+ mkdirSync12(dirname20(p), { recursive: true });
21620
+ writeFileSync12(p, JSON.stringify(h));
21621
+ } catch {
21622
+ }
21623
+ }
21624
+ var STALE_AFTER_MS = 30 * 6e4;
21625
+
21626
+ // auto-indexer/indexer.ts
21627
+ var CLI_VERSION3 = true ? "0.5.6" : "0.0.0";
21217
21628
  var DEBOUNCE_MS = 5e3;
21218
21629
  function ts() {
21219
21630
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -21226,9 +21637,9 @@ var DIARY_DIR = getDiaryDir();
21226
21637
  var CODEX_SESSIONS_DIR = codexBackend.sessionsDir();
21227
21638
  var GEMINI_TMP_DIR = geminiBackend.tmpDir();
21228
21639
  var OPENCODE_DB_PATH = opencodeBackend.dbPath();
21229
- var OPENCODE_DB_DIR = dirname20(OPENCODE_DB_PATH);
21230
- var CURRENT_RESUME_PATH = join47(claudeBackend.homeDir(), "current-resume");
21231
- var LAST_CLEANUP_PATH = join47(claudeBackend.homeDir(), ".last-cleanup");
21640
+ var OPENCODE_DB_DIR = dirname21(OPENCODE_DB_PATH);
21641
+ var CURRENT_RESUME_PATH = join49(claudeBackend.homeDir(), "current-resume");
21642
+ var LAST_CLEANUP_PATH = join49(claudeBackend.homeDir(), ".last-cleanup");
21232
21643
  var debounceTimer = null;
21233
21644
  var syncInFlight = false;
21234
21645
  var pendingFileCount = 0;
@@ -21250,9 +21661,11 @@ async function shipToServer(trigger) {
21250
21661
  `[${ts()}] Sync (${trigger}): ${result.uploaded} session(s) pushed, ${result.redactions} secret(s) redacted, ${result.skipped} skipped, ${result.items} items, ${result.links} links, ${result.derived} derived, ${result.kgEntities} KG entities, ${result.kgTriples} KG triples`
21251
21662
  );
21252
21663
  }
21664
+ for (const server of syncTargetUrls()) noteSyncOutcome(server, true);
21253
21665
  } catch (err) {
21254
21666
  const msg = err instanceof Error ? err.message : String(err);
21255
21667
  console.error(`[${ts()}] Sync failed (${trigger}, will retry next flush): ${msg.slice(0, 200)}`);
21668
+ for (const server of syncTargetUrls()) noteSyncOutcome(server, false, msg);
21256
21669
  } finally {
21257
21670
  syncInFlight = false;
21258
21671
  }
@@ -21280,39 +21693,58 @@ async function onResumeSignal(path) {
21280
21693
  lastResumeAt.set(id, now);
21281
21694
  const { snapshotShadow: snapshotShadow2 } = await Promise.resolve().then(() => (init_transcript(), transcript_exports));
21282
21695
  const r = await snapshotShadow2(id);
21696
+ if (r.status === "skipped") return;
21283
21697
  console.log(`[${ts()}] [resume-guard] shadow ${r.status} for ${id.slice(0, 8)}\u2026` + (r.recovered > 0 ? ` (recovered ${r.recovered} record(s))` : ""));
21284
21698
  } catch (e) {
21285
21699
  console.error(`[${ts()}] [resume-guard] ${e instanceof Error ? e.message : e}`);
21286
21700
  }
21287
21701
  }
21288
- var sessionWatcher = chokidar.watch(`${CLAUDE_DIR}/**/*.jsonl`, {
21289
- ignored: (p) => /agent-/.test(p) || /^\./.test(basename25(p)),
21702
+ var FORCE_POLL = process.env.CHAT_RECALL_WATCH_POLL === "1";
21703
+ var POLL = (interval) => FORCE_POLL ? { usePolling: true, interval } : {};
21704
+ var watchOpts = (t, poll = FORCE_POLL) => ({
21290
21705
  persistent: true,
21291
- ignoreInitial: true,
21292
- usePolling: true,
21293
- interval: 5e3,
21294
- binaryInterval: 5e3
21706
+ ignoreInitial: t.ignoreInitial ?? true,
21707
+ ...t.ignored ? { ignored: t.ignored } : {},
21708
+ ...poll ? { usePolling: true, interval: t.interval } : {}
21709
+ });
21710
+ function watchWithFallback(name, pattern, t) {
21711
+ let w = chokidar.watch(pattern, watchOpts(t));
21712
+ let swapped = false;
21713
+ w.on("error", (err) => {
21714
+ const code = err?.code;
21715
+ if (swapped || FORCE_POLL || code !== "ENOSPC" && code !== "EMFILE") {
21716
+ console.error(`[${ts()}] ${name} watcher error: ${String(err)}`);
21717
+ return;
21718
+ }
21719
+ swapped = true;
21720
+ console.log(`[${ts()}] ${name} watcher: native watching hit ${code} \u2014 falling back to polling every ${t.interval}ms. Raise fs.inotify.max_user_watches to get native events back.`);
21721
+ void w.close().catch(() => {
21722
+ });
21723
+ w = chokidar.watch(pattern, watchOpts(t, true));
21724
+ });
21725
+ return w;
21726
+ }
21727
+ var sessionWatcher = watchWithFallback("sessions", `${CLAUDE_DIR}/**/*.jsonl`, {
21728
+ ignored: (p) => /agent-/.test(p) || /^\./.test(basename25(p)),
21729
+ interval: 5e3
21295
21730
  });
21296
21731
  var plansWatcher = chokidar.watch(`${PLANS_DIR}/*.md`, {
21297
21732
  persistent: true,
21298
21733
  ignoreInitial: true,
21299
21734
  awaitWriteFinish: { stabilityThreshold: 1e3, pollInterval: 100 },
21300
- usePolling: true,
21301
- interval: 1e4
21735
+ ...POLL(1e4)
21302
21736
  });
21303
21737
  var tasksWatcher = chokidar.watch(`${TASKS_DIR}/**/*.json`, {
21304
21738
  persistent: true,
21305
21739
  ignoreInitial: true,
21306
21740
  awaitWriteFinish: { stabilityThreshold: 1e3, pollInterval: 100 },
21307
- usePolling: true,
21308
- interval: 1e4
21741
+ ...POLL(1e4)
21309
21742
  });
21310
21743
  var historyWatcher = chokidar.watch(HISTORY_PATH, {
21311
21744
  persistent: true,
21312
21745
  ignoreInitial: true,
21313
21746
  awaitWriteFinish: { stabilityThreshold: 2e3, pollInterval: 500 },
21314
- usePolling: true,
21315
- interval: 3e4
21747
+ ...POLL(3e4)
21316
21748
  // check less frequently
21317
21749
  });
21318
21750
  var diaryWatcher = chokidar.watch(`${DIARY_DIR}/**/*.json`, {
@@ -21326,15 +21758,13 @@ var codexWatcher = chokidar.watch(`${CODEX_SESSIONS_DIR}/**/rollout-*.jsonl`, {
21326
21758
  // Codex rolls every event into its own JSONL line — wait for ~2s of
21327
21759
  // write quiescence so we don't ship mid-write.
21328
21760
  awaitWriteFinish: { stabilityThreshold: 2e3, pollInterval: 200 },
21329
- usePolling: true,
21330
- interval: 5e3
21761
+ ...POLL(5e3)
21331
21762
  });
21332
21763
  var geminiWatcher = chokidar.watch(`${GEMINI_TMP_DIR}/**/session-*.json`, {
21333
21764
  persistent: true,
21334
21765
  ignoreInitial: true,
21335
21766
  awaitWriteFinish: { stabilityThreshold: 1500, pollInterval: 200 },
21336
- usePolling: true,
21337
- interval: 5e3
21767
+ ...POLL(5e3)
21338
21768
  });
21339
21769
  var opencodeWatcher = chokidar.watch(
21340
21770
  [`${OPENCODE_DB_PATH}`, `${OPENCODE_DB_PATH}-wal`],
@@ -21342,23 +21772,20 @@ var opencodeWatcher = chokidar.watch(
21342
21772
  persistent: true,
21343
21773
  ignoreInitial: true,
21344
21774
  awaitWriteFinish: { stabilityThreshold: 2500, pollInterval: 500 },
21345
- usePolling: true,
21346
- interval: 1e4
21775
+ ...POLL(1e4)
21347
21776
  }
21348
21777
  );
21349
21778
  var agentMemoryWatcher = chokidar.watch(`${CLAUDE_DIR}/*/memory/*.md`, {
21350
21779
  persistent: true,
21351
21780
  ignoreInitial: true,
21352
21781
  awaitWriteFinish: { stabilityThreshold: 1e3, pollInterval: 100 },
21353
- usePolling: true,
21354
- interval: 1e4
21782
+ ...POLL(1e4)
21355
21783
  });
21356
21784
  var cleanupWatcher = chokidar.watch(LAST_CLEANUP_PATH, {
21357
21785
  persistent: true,
21358
21786
  ignoreInitial: true,
21359
21787
  awaitWriteFinish: { stabilityThreshold: 1e3, pollInterval: 200 },
21360
- usePolling: true,
21361
- interval: 3e4
21788
+ ...POLL(3e4)
21362
21789
  });
21363
21790
  var watchers = {
21364
21791
  sessions: sessionWatcher,
@@ -21372,12 +21799,10 @@ var watchers = {
21372
21799
  agentMemory: agentMemoryWatcher,
21373
21800
  cleanup: cleanupWatcher
21374
21801
  };
21375
- var resumeWatcher = chokidar.watch(CURRENT_RESUME_PATH, {
21376
- persistent: true,
21377
- ignoreInitial: false,
21378
- usePolling: true,
21379
- interval: 1e3
21380
- });
21802
+ var resumeWatcher = chokidar.watch(CURRENT_RESUME_PATH, watchOpts({
21803
+ interval: 1e3,
21804
+ ignoreInitial: false
21805
+ }));
21381
21806
  resumeWatcher.on("add", (p) => {
21382
21807
  void onResumeSignal(p);
21383
21808
  }).on("change", (p) => {
@@ -21421,9 +21846,71 @@ var BOOT_CODE = codeFingerprint();
21421
21846
  if (BOOT_CODE) {
21422
21847
  console.log(`[${ts()}] running bundle ${BOOT_CODE.path} (${BOOT_CODE.size} bytes)`);
21423
21848
  }
21849
+ function rotateLogIfLarge() {
21850
+ const LOG_MAX_BYTES = 32 * 1024 * 1024;
21851
+ try {
21852
+ const logPath = join49(getDataDir(), "watch.log");
21853
+ const st = statSync40(logPath);
21854
+ if (st.size < LOG_MAX_BYTES) return;
21855
+ const prev = `${logPath}.1`;
21856
+ try {
21857
+ unlinkSync4(prev);
21858
+ } catch {
21859
+ }
21860
+ renameSync6(logPath, prev);
21861
+ console.log(`[${ts()}] rotated watch.log at ${(st.size / 1048576).toFixed(0)}MB \u2192 watch.log.1`);
21862
+ } catch {
21863
+ }
21864
+ }
21865
+ rotateLogIfLarge();
21866
+ var PROC_START = Date.now();
21867
+ var targetHealth = {};
21868
+ function recentStarts() {
21869
+ const prior = readCollectorHealth();
21870
+ const cutoff = Date.now() - 36e5;
21871
+ return [...prior?.starts ?? [], PROC_START].filter((t) => typeof t === "number" && t > cutoff).filter((t, i, a) => a.indexOf(t) === i).sort((a, b) => a - b).slice(-20);
21872
+ }
21873
+ function publishHealth() {
21874
+ const starts = recentStarts();
21875
+ writeCollectorHealth(Object.assign({
21876
+ v: 1,
21877
+ updatedAt: Date.now(),
21878
+ startedAt: PROC_START,
21879
+ restartsLastHour: starts.length,
21880
+ targets: targetHealth
21881
+ }, { starts }));
21882
+ }
21883
+ function syncTargetUrls() {
21884
+ try {
21885
+ return loadAllCredentials().map((c) => c.serverUrl);
21886
+ } catch {
21887
+ return [];
21888
+ }
21889
+ }
21890
+ function noteSyncOutcome(server, ok, err) {
21891
+ const t = targetHealth[server] ?? { lastOkAt: null, failures: 0 };
21892
+ if (ok) {
21893
+ t.lastOkAt = Date.now();
21894
+ t.failures = 0;
21895
+ delete t.lastError;
21896
+ } else {
21897
+ t.failures += 1;
21898
+ t.lastError = (err ?? "unknown error").slice(0, 160);
21899
+ }
21900
+ targetHealth[server] = t;
21901
+ publishHealth();
21902
+ }
21903
+ publishHealth();
21904
+ {
21905
+ const n = recentStarts().length;
21906
+ if (n >= 3) {
21907
+ console.error(`[${ts()}] WARNING: this collector has restarted ${n} times in the last hour. Sync is probably falling behind. Health: ${collectorHealthPath()}`);
21908
+ }
21909
+ }
21424
21910
  setInterval(() => {
21425
21911
  if (checkSelfRestart({ boot: BOOT_CODE, log: (m) => console.log(`[${ts()}] ${m}`) })) return;
21426
21912
  console.log(`[${ts()}] Heartbeat - ${pendingFileCount} pending file event(s), sync ${syncInFlight ? "in-flight" : "idle"}`);
21913
+ publishHealth();
21427
21914
  }, 6e4);
21428
21915
  var intentDrainInFlight = false;
21429
21916
  var intentDrainStartedAt = 0;
@@ -21432,7 +21919,7 @@ var DRAIN_STALL_MS = 5 * 6e4;
21432
21919
  async function drainIntentsTick() {
21433
21920
  if (intentDrainInFlight) {
21434
21921
  const stuckMs = Date.now() - intentDrainStartedAt;
21435
- if (stuckMs < DRAIN_STALL_MS) return;
21922
+ if (stuckMs < DRAIN_STALL_MS) return false;
21436
21923
  console.error(`[${ts()}] sync-intents: previous drain has been running ${Math.round(stuckMs / 1e3)}s \u2014 abandoning it and starting a fresh tick`);
21437
21924
  }
21438
21925
  intentDrainInFlight = true;
@@ -21444,18 +21931,28 @@ async function drainIntentsTick() {
21444
21931
  console.log(`[${ts()}] sync-intents: ${r.done} done, ${r.errored} errored`);
21445
21932
  if (r.done > 0) await shipToServer("intent-applied");
21446
21933
  }
21934
+ return r.processed > 0;
21447
21935
  } catch (err) {
21448
21936
  console.error(`[${ts()}] sync-intents drain failed: ${err instanceof Error ? err.message : err}`);
21937
+ return false;
21449
21938
  } finally {
21450
21939
  if (gen === intentDrainGeneration) intentDrainInFlight = false;
21451
21940
  }
21452
21941
  }
21453
- setInterval(() => {
21454
- void drainIntentsTick();
21455
- }, 15e3).unref();
21456
- setTimeout(() => {
21457
- void drainIntentsTick();
21458
- }, 5e3);
21942
+ var INTENT_POLL_MIN_MS = 15e3;
21943
+ var INTENT_POLL_MAX_MS = 5 * 6e4;
21944
+ var intentPollMs = INTENT_POLL_MIN_MS;
21945
+ var intentTimer = null;
21946
+ function scheduleIntentPoll(ms) {
21947
+ if (intentTimer) clearTimeout(intentTimer);
21948
+ intentTimer = setTimeout(async () => {
21949
+ const found = await drainIntentsTick();
21950
+ intentPollMs = found ? INTENT_POLL_MIN_MS : Math.min(intentPollMs * 2, INTENT_POLL_MAX_MS);
21951
+ scheduleIntentPoll(intentPollMs);
21952
+ }, ms);
21953
+ intentTimer.unref?.();
21954
+ }
21955
+ scheduleIntentPoll(5e3);
21459
21956
  var CODE_INDEX_ON = process.env.CHAT_RECALL_CODE_INDEX !== "0";
21460
21957
  var CODE_INDEX_MS = Math.max(15, parseInt(process.env.CHAT_RECALL_CODE_INDEX_MINUTES || "180", 10)) * 60 * 1e3;
21461
21958
  var CODE_INDEX_MAX = Math.max(1, parseInt(process.env.CHAT_RECALL_CODE_INDEX_MAX || "50", 10));
@@ -21491,13 +21988,45 @@ function discoverWorkspaces() {
21491
21988
  } catch {
21492
21989
  }
21493
21990
  const isExcluded = (p) => excluded.some((x) => p.includes(x));
21494
- return [...newest.entries()].filter(([p]) => !isExcluded(p)).filter(([p]) => !isPersonalPath(p, includeProjects)).filter(([p]) => {
21991
+ const candidates2 = [...newest.entries()].filter(([p]) => !isExcluded(p)).filter(([p]) => !isPersonalPath(p, includeProjects)).filter(([p]) => {
21495
21992
  try {
21496
- return existsSync44(p);
21993
+ return existsSync45(p);
21497
21994
  } catch {
21498
21995
  return false;
21499
21996
  }
21500
- }).sort((a, b) => b[1] - a[1]).slice(0, CODE_INDEX_MAX).map(([p]) => p);
21997
+ }).filter(([p]) => isCodeWorkspace(p)).sort((a, b) => b[1] - a[1]);
21998
+ const roots = candidates2.map(([p]) => p);
21999
+ const notAnAncestor = ([p]) => !roots.some((other) => other !== p && other.startsWith(p.endsWith(sep3) ? p : p + sep3));
22000
+ return candidates2.filter(notAnAncestor).slice(0, CODE_INDEX_MAX).map(([p]) => p);
22001
+ }
22002
+ var WORKSPACE_MARKERS = [
22003
+ ".git",
22004
+ "package.json",
22005
+ "go.mod",
22006
+ "Cargo.toml",
22007
+ "pyproject.toml",
22008
+ "setup.py",
22009
+ "pom.xml",
22010
+ "build.gradle",
22011
+ "composer.json",
22012
+ "Gemfile",
22013
+ "mix.exs",
22014
+ "deno.json"
22015
+ ];
22016
+ function isCodeWorkspace(p) {
22017
+ try {
22018
+ const resolved = resolve2(p);
22019
+ if (resolved === sep3 || resolved === homedir20() || dirname21(resolved) === resolved) return false;
22020
+ } catch {
22021
+ return false;
22022
+ }
22023
+ return WORKSPACE_MARKERS.some((m) => {
22024
+ try {
22025
+ return existsSync45(join49(p, m));
22026
+ } catch {
22027
+ return false;
22028
+ }
22029
+ });
21501
22030
  }
21502
22031
  async function codeIndexTick() {
21503
22032
  if (codeIndexInFlight) return;
@@ -21602,6 +22131,11 @@ if (CODE_INDEX_ON) setTimeout(() => {
21602
22131
  }, 15e4);
21603
22132
  function shutdown(signal) {
21604
22133
  console.log(`[${ts()}] Received ${signal}, shutting down...`);
22134
+ try {
22135
+ flushLedger();
22136
+ } catch (e) {
22137
+ console.error(`[${ts()}] ledger flush on shutdown failed: ${String(e)}`);
22138
+ }
21605
22139
  for (const watcher of Object.values(watchers)) {
21606
22140
  void watcher.close();
21607
22141
  }
@@ -21610,3 +22144,13 @@ function shutdown(signal) {
21610
22144
  }
21611
22145
  process.on("SIGTERM", () => shutdown("SIGTERM"));
21612
22146
  process.on("SIGINT", () => shutdown("SIGINT"));
22147
+ if (process.platform === "win32") {
22148
+ process.on("SIGBREAK", () => shutdown("SIGBREAK"));
22149
+ process.on("SIGHUP", () => shutdown("SIGHUP"));
22150
+ }
22151
+ process.on("exit", () => {
22152
+ try {
22153
+ flushLedger();
22154
+ } catch {
22155
+ }
22156
+ });