chat-recall 0.5.4 → 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
  });
@@ -6141,6 +6203,24 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
6141
6203
  }
6142
6204
  const filesToParse = hasSubagentsDir(sessionPath) ? [sessionPath, ...getSubagentPaths(sessionPath)] : [sessionPath];
6143
6205
  let userCount = 0;
6206
+ const seenPrompts = /* @__PURE__ */ new Set();
6207
+ const recordPrompt = (raw, line, dedupe = false) => {
6208
+ if (userCount >= maxMessages)
6209
+ return;
6210
+ const withoutReminders = raw.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "");
6211
+ if (!withoutReminders.trim() || withoutReminders.trim().length <= 10)
6212
+ return;
6213
+ const cleanedText = stripInjectedBanners(withoutReminders).trim();
6214
+ if (cleanedText.length < 10)
6215
+ return;
6216
+ if (dedupe && seenPrompts.has(cleanedText))
6217
+ return;
6218
+ seenPrompts.add(cleanedText);
6219
+ content.userMessages.push({ text: cleanedText, lineNumber: line, contentType: "user" });
6220
+ userCount++;
6221
+ if (!content.firstPrompt)
6222
+ content.firstPrompt = cleanedText.slice(0, 1e3);
6223
+ };
6144
6224
  let assistantCount = 0;
6145
6225
  let toolCount = 0;
6146
6226
  let lineNum = 0;
@@ -6219,19 +6299,11 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
6219
6299
  text = textParts.join("\n");
6220
6300
  }
6221
6301
  }
6222
- if (text && !text.includes("<system-reminder>") && text.trim().length > 10) {
6223
- const cleanedText = stripInjectedBanners(text).trim();
6224
- if (cleanedText.length < 10)
6225
- continue;
6226
- content.userMessages.push({
6227
- text: cleanedText,
6228
- lineNumber: lineNum,
6229
- contentType: "user"
6230
- });
6231
- userCount++;
6232
- if (!content.firstPrompt) {
6233
- content.firstPrompt = cleanedText.slice(0, 1e3);
6234
- }
6302
+ if (text)
6303
+ recordPrompt(text, lineNum);
6304
+ } else if (msgType === "queue-operation") {
6305
+ if (obj.operation === "enqueue" && typeof obj.content === "string") {
6306
+ recordPrompt(obj.content, lineNum, true);
6235
6307
  }
6236
6308
  } else if (msgType === "assistant") {
6237
6309
  const msg = obj.message;
@@ -6347,8 +6419,8 @@ function decodeDirName(dirName) {
6347
6419
  }
6348
6420
  for (let len = 2; len <= segments.length - i; len++) {
6349
6421
  const joinedSegments = segments.slice(i, i + len);
6350
- for (const sep2 of ["-", "_", "."]) {
6351
- const candidate = join5(currentPath, joinedSegments.join(sep2));
6422
+ for (const sep4 of ["-", "_", "."]) {
6423
+ const candidate = join5(currentPath, joinedSegments.join(sep4));
6352
6424
  if (existsSync5(candidate)) {
6353
6425
  currentPath = candidate;
6354
6426
  i += len;
@@ -7100,7 +7172,7 @@ async function archiveRawSession(store, sessionId) {
7100
7172
  }
7101
7173
  function detectForkPredecessor(sessionPath) {
7102
7174
  const { readFileSync: readFileSync41, readdirSync: readdirSync31 } = requireFs();
7103
- const { dirname: dirname21, join: join48, basename: basename26 } = requirePath();
7175
+ const { dirname: dirname22, join: join50, basename: basename26 } = requirePath();
7104
7176
  let text;
7105
7177
  try {
7106
7178
  text = readFileSync41(sessionPath, "utf-8");
@@ -7125,7 +7197,7 @@ function detectForkPredecessor(sessionPath) {
7125
7197
  return null;
7126
7198
  if (text.includes(`"uuid":"${headParent}"`))
7127
7199
  return null;
7128
- const dir = dirname21(sessionPath);
7200
+ const dir = dirname22(sessionPath);
7129
7201
  const self = basename26(sessionPath);
7130
7202
  let entries;
7131
7203
  try {
@@ -7137,7 +7209,7 @@ function detectForkPredecessor(sessionPath) {
7137
7209
  if (!f.endsWith(".jsonl") || f === self || f === "sessions-index.json")
7138
7210
  continue;
7139
7211
  try {
7140
- if (readFileSync41(join48(dir, f), "utf-8").includes(`"uuid":"${headParent}"`)) {
7212
+ if (readFileSync41(join50(dir, f), "utf-8").includes(`"uuid":"${headParent}"`)) {
7141
7213
  return basename26(f, ".jsonl");
7142
7214
  }
7143
7215
  } catch {
@@ -7341,18 +7413,43 @@ function updateShadow(sessionId, exp) {
7341
7413
  path
7342
7414
  };
7343
7415
  }
7344
- async function snapshotShadow(sessionId) {
7416
+ async function snapshotShadow(sessionId, opts = {}) {
7345
7417
  await Promise.resolve().then(() => (init_backends(), backends_exports));
7346
7418
  const { getBackendForId: getBackendForId2, getBackend: getBackend2 } = await Promise.resolve().then(() => (init_tool_backend(), tool_backend_exports));
7347
7419
  const backend = getBackendForId2(sessionId) ?? getBackend2("claude");
7348
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
+ }
7349
7432
  let exp = null;
7350
7433
  try {
7351
7434
  exp = backend.exportRawSession(rawId);
7352
7435
  } catch {
7353
7436
  exp = null;
7354
7437
  }
7355
- 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
+ }
7356
7453
  }
7357
7454
  function seedShadow(sessionId, container) {
7358
7455
  const tool = container.tool;
@@ -7371,7 +7468,7 @@ function shadowSizeOnDisk(tool, rawId) {
7371
7468
  return 0;
7372
7469
  }
7373
7470
  }
7374
- var WHOLE_FILE_TOOLS, SINGLETON_META;
7471
+ var WHOLE_FILE_TOOLS, SINGLETON_META, lastLiveFingerprint;
7375
7472
  var init_shadow = __esm({
7376
7473
  "../engine/dist/transcript/shadow.js"() {
7377
7474
  "use strict";
@@ -7379,6 +7476,27 @@ var init_shadow = __esm({
7379
7476
  init_raw();
7380
7477
  WHOLE_FILE_TOOLS = /* @__PURE__ */ new Set(["gemini"]);
7381
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";
7382
7500
  }
7383
7501
  });
7384
7502
 
@@ -7405,7 +7523,7 @@ function findSessionFile(sessionId) {
7405
7523
  path: candidate,
7406
7524
  projectDir: entry.name,
7407
7525
  // Project path is encoded by replacing slashes with dashes.
7408
- projectPath: entry.name.replace(/-/g, "/").replace(/^\//, "/")
7526
+ projectPath: decodeProjectDirName(entry.name)
7409
7527
  };
7410
7528
  }
7411
7529
  }
@@ -7434,7 +7552,7 @@ function findSessionFiles(sessionId) {
7434
7552
  out.push({
7435
7553
  path: candidate,
7436
7554
  projectDir: entry.name,
7437
- projectPath: entry.name.replace(/-/g, "/").replace(/^\//, "/")
7555
+ projectPath: decodeProjectDirName(entry.name)
7438
7556
  });
7439
7557
  }
7440
7558
  }
@@ -7458,7 +7576,31 @@ function resolveSessionContentGroups(sessionId) {
7458
7576
  const names = [...groups.keys()].sort((a, b) => a === "main" ? -1 : b === "main" ? 1 : a.localeCompare(b));
7459
7577
  return names.map((name) => ({ name, paths: groups.get(name) }));
7460
7578
  }
7579
+ function withSessionReadCache(fn) {
7580
+ readScopeDepth++;
7581
+ try {
7582
+ return fn();
7583
+ } finally {
7584
+ if (--readScopeDepth === 0)
7585
+ readScope = null;
7586
+ }
7587
+ }
7461
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
+ }
7462
7604
  let text = "";
7463
7605
  let mtime = 0;
7464
7606
  for (const p of group.paths) {
@@ -7474,7 +7616,10 @@ function readSessionGroupText(group) {
7474
7616
  }
7475
7617
  text = text ? mergeLineText(text, raw).text : raw;
7476
7618
  }
7477
- return { text, mtime };
7619
+ const out = { text, mtime };
7620
+ if (readScopeDepth > 0 && key)
7621
+ readScope = { key, value: out };
7622
+ return out;
7478
7623
  }
7479
7624
  function resolveSessionContentPaths(sessionFile) {
7480
7625
  if (!hasSubagentsDir(sessionFile))
@@ -7697,7 +7842,7 @@ function findCodexSessionFileIn(root, sessionId) {
7697
7842
  }
7698
7843
  return scanDir(root);
7699
7844
  }
7700
- var geminiProjectMapCache, MAX_EVENT_LINE_BYTES;
7845
+ var readScope, readScopeDepth, geminiProjectMapCache, MAX_EVENT_LINE_BYTES;
7701
7846
  var init_live_session_scan = __esm({
7702
7847
  "../engine/dist/core/live-session-scan.js"() {
7703
7848
  "use strict";
@@ -7706,6 +7851,9 @@ var init_live_session_scan = __esm({
7706
7851
  init_tool_backend();
7707
7852
  init_tool_paths();
7708
7853
  init_backends();
7854
+ init_project_dir_name();
7855
+ readScope = null;
7856
+ readScopeDepth = 0;
7709
7857
  geminiProjectMapCache = null;
7710
7858
  MAX_EVENT_LINE_BYTES = 8 * 1024 * 1024;
7711
7859
  }
@@ -8166,74 +8314,123 @@ var init_session_outcome = __esm({
8166
8314
  }
8167
8315
  });
8168
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
+
8169
8331
  // ../engine/dist/core/first-prompt.js
8170
- 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";
8171
8334
  function stripBanners2(s) {
8172
8335
  let out = s;
8173
8336
  for (const re of BANNERS)
8174
8337
  out = out.replace(re, " ");
8175
8338
  return out.replace(/[ \t]{2,}/g, " ").trim();
8176
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
+ }
8177
8380
  function extractFirstUserPromptSync(filePath, options = {}) {
8178
8381
  const maxLines = options.maxLines ?? 500;
8179
8382
  const maxLength = options.maxLength ?? 1e3;
8180
- if (!existsSync9(filePath))
8181
- return "";
8182
- let raw;
8383
+ const maxBytes = options.maxBytes ?? 4 * 1024 * 1024;
8384
+ let fd;
8183
8385
  try {
8184
- raw = readFileSync5(filePath, "utf-8");
8386
+ fd = openSync3(filePath, "r");
8185
8387
  } catch {
8186
8388
  return "";
8187
8389
  }
8188
- const lines = raw.split("\n");
8189
- const upTo = Math.min(lines.length, maxLines);
8190
- for (let i = 0; i < upTo; i++) {
8191
- const line = lines[i];
8192
- if (!line.trim())
8193
- continue;
8194
- 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 {
8195
8423
  try {
8196
- obj = JSON.parse(line);
8424
+ closeSync3(fd);
8197
8425
  } catch {
8198
- continue;
8199
- }
8200
- if (obj.type !== "user")
8201
- continue;
8202
- if (obj.isSidechain === true)
8203
- continue;
8204
- const message = obj.message;
8205
- if (!message)
8206
- continue;
8207
- const content = message.content;
8208
- let text = "";
8209
- if (typeof content === "string") {
8210
- text = content;
8211
- } else if (Array.isArray(content)) {
8212
- const parts = [];
8213
- for (const block of content) {
8214
- if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
8215
- parts.push(block.text);
8216
- }
8217
- }
8218
- text = parts.join("\n");
8219
8426
  }
8220
- if (!text)
8221
- continue;
8222
- if (text.includes("<system-reminder>"))
8223
- continue;
8224
- if (text.trim() === "[Request interrupted by user]")
8225
- continue;
8226
- const cleaned = stripBanners2(text);
8227
- if (cleaned.length < 10)
8228
- continue;
8229
- return cleaned.slice(0, maxLength);
8230
8427
  }
8231
- return "";
8232
8428
  }
8233
8429
  var BANNERS;
8234
8430
  var init_first_prompt = __esm({
8235
8431
  "../engine/dist/core/first-prompt.js"() {
8236
8432
  "use strict";
8433
+ init_flat_string();
8237
8434
  BANNERS = [
8238
8435
  /MCP issues detected\. ?Run \/mcp list for status\.?/g,
8239
8436
  /Context low[^\n]*Run \/compact[^\n]*/g,
@@ -8243,7 +8440,7 @@ var init_first_prompt = __esm({
8243
8440
  });
8244
8441
 
8245
8442
  // ../engine/dist/core/backends/tail-read.js
8246
- 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";
8247
8444
  function readTailFromOffset(path, offset) {
8248
8445
  let size = 0;
8249
8446
  try {
@@ -8257,10 +8454,10 @@ function readTailFromOffset(path, offset) {
8257
8454
  const buf = Buffer.allocUnsafe(len);
8258
8455
  let fd = null;
8259
8456
  try {
8260
- fd = openSync3(path, "r");
8457
+ fd = openSync4(path, "r");
8261
8458
  let got = 0;
8262
8459
  while (got < len) {
8263
- const n = readSync3(fd, buf, got, len - got, offset + got);
8460
+ const n = readSync4(fd, buf, got, len - got, offset + got);
8264
8461
  if (n <= 0)
8265
8462
  break;
8266
8463
  got += n;
@@ -8279,7 +8476,7 @@ function readTailFromOffset(path, offset) {
8279
8476
  } finally {
8280
8477
  if (fd !== null) {
8281
8478
  try {
8282
- closeSync3(fd);
8479
+ closeSync4(fd);
8283
8480
  } catch {
8284
8481
  }
8285
8482
  }
@@ -8564,8 +8761,11 @@ var init_generic_engine = __esm({
8564
8761
  });
8565
8762
 
8566
8763
  // ../engine/dist/core/backends/claude.js
8567
- 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";
8568
8765
  import { join as join10, basename as basename8 } from "path";
8766
+ function stripReminders(text) {
8767
+ return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
8768
+ }
8569
8769
  function parseExitCode(text) {
8570
8770
  const m = text.match(/(?:^|\n)\s*(?:exit\s+code|returncode|exit\s+status|exit)\s*[:=]\s*(\d+)\b/i);
8571
8771
  return m ? Number(m[1]) : void 0;
@@ -8580,6 +8780,7 @@ var init_claude2 = __esm({
8580
8780
  init_session_git();
8581
8781
  init_first_prompt();
8582
8782
  init_tail_read();
8783
+ init_project_dir_name();
8583
8784
  init_generic_engine();
8584
8785
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8585
8786
  ClaudeBackend = class {
@@ -8656,13 +8857,13 @@ var init_claude2 = __esm({
8656
8857
  return null;
8657
8858
  let head;
8658
8859
  try {
8659
- const fd = openSync4(located.path, "r");
8860
+ const fd = openSync5(located.path, "r");
8660
8861
  try {
8661
8862
  const buf = Buffer.alloc(65536);
8662
- const n = readSync4(fd, buf, 0, buf.length, 0);
8863
+ const n = readSync5(fd, buf, 0, buf.length, 0);
8663
8864
  head = buf.toString("utf-8", 0, n);
8664
8865
  } finally {
8665
- closeSync4(fd);
8866
+ closeSync5(fd);
8666
8867
  }
8667
8868
  } catch {
8668
8869
  return null;
@@ -8721,11 +8922,11 @@ var init_claude2 = __esm({
8721
8922
  if (filter && !proj.name.toLowerCase().includes(filter))
8722
8923
  continue;
8723
8924
  const projPath = join10(root, proj.name);
8724
- const projectPath = proj.name.replace(/-/g, "/").replace(/^\//, "/");
8925
+ const projectPath = decodeProjectDirName(proj.name);
8725
8926
  const indexPath = join10(projPath, "sessions-index.json");
8726
8927
  if (existsSync10(indexPath)) {
8727
8928
  try {
8728
- const indexData = JSON.parse(readFileSync6(indexPath, "utf-8"));
8929
+ const indexData = JSON.parse(readFileSync5(indexPath, "utf-8"));
8729
8930
  const entries = indexData.entries || [];
8730
8931
  for (const entry of entries) {
8731
8932
  if (!entry.fullPath || !existsSync10(entry.fullPath))
@@ -8874,6 +9075,15 @@ var init_claude2 = __esm({
8874
9075
  }
8875
9076
  const tsIso = typeof obj.timestamp === "string" ? obj.timestamp : void 0;
8876
9077
  const ts2 = tsIso ? Date.parse(tsIso) || mtime : mtime;
9078
+ if (obj.type === "queue-operation") {
9079
+ if (obj.operation === "enqueue" && typeof obj.content === "string") {
9080
+ const queued = stripReminders(obj.content);
9081
+ if (queued && !queued.startsWith("<task-notification")) {
9082
+ events.push({ kind: "user", ts: ts2, tsIso, line: lineNum, text: queued });
9083
+ }
9084
+ }
9085
+ continue;
9086
+ }
8877
9087
  if (obj.type === "user") {
8878
9088
  const msg = obj.message;
8879
9089
  const content = msg?.content;
@@ -8907,9 +9117,9 @@ var init_claude2 = __esm({
8907
9117
  }
8908
9118
  }
8909
9119
  }
8910
- if (text && !text.includes("<system-reminder>")) {
8911
- events.push({ kind: "user", ts: ts2, tsIso, line: lineNum, text });
8912
- }
9120
+ const spoken = stripReminders(text);
9121
+ if (spoken)
9122
+ events.push({ kind: "user", ts: ts2, tsIso, line: lineNum, text: spoken });
8913
9123
  for (const tr of toolResults) {
8914
9124
  events.push({
8915
9125
  kind: "tool_result",
@@ -9008,7 +9218,7 @@ var init_claude2 = __esm({
9008
9218
  * with quiet main + active subagents still surfaces.
9009
9219
  */
9010
9220
  collectRecentEdits(opts) {
9011
- const candidates = [];
9221
+ const candidates2 = [];
9012
9222
  for (const root of claudeProjectDirs()) {
9013
9223
  if (!existsSync10(root))
9014
9224
  continue;
@@ -9044,14 +9254,14 @@ var init_claude2 = __esm({
9044
9254
  }
9045
9255
  if (mtime < opts.sinceMs)
9046
9256
  continue;
9047
- candidates.push({ rawId: basename8(f, ".jsonl"), mtime });
9257
+ candidates2.push({ rawId: basename8(f, ".jsonl"), mtime });
9048
9258
  } catch {
9049
9259
  }
9050
9260
  }
9051
9261
  }
9052
9262
  }
9053
- candidates.sort((a, b) => b.mtime - a.mtime);
9054
- 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;
9055
9265
  const edits = [];
9056
9266
  for (const c of limited) {
9057
9267
  const scan = this.liveScanEdits(c.rawId);
@@ -9138,12 +9348,12 @@ var init_claude2 = __esm({
9138
9348
 
9139
9349
  // ../engine/dist/core/backends/gemini.js
9140
9350
  import { createHash as createHash4 } from "crypto";
9141
- 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";
9142
9352
  import { basename as basename9, join as join11 } from "path";
9143
9353
  function readSessionId(path, format) {
9144
9354
  try {
9145
9355
  if (format === "json") {
9146
- const json = JSON.parse(readFileSync7(path, "utf-8"));
9356
+ const json = JSON.parse(readFileSync6(path, "utf-8"));
9147
9357
  return typeof json?.sessionId === "string" ? json.sessionId : null;
9148
9358
  }
9149
9359
  const head = readHeadSync(path).split("\n", 1)[0] || "";
@@ -9177,6 +9387,7 @@ var init_gemini2 = __esm({
9177
9387
  init_session_git();
9178
9388
  init_generic_engine();
9179
9389
  init_tail_read();
9390
+ init_flat_string();
9180
9391
  PREFIX = "gemini_";
9181
9392
  FULL_PARSE_MAX = 4 * 1024 * 1024;
9182
9393
  GeminiBackend = class {
@@ -9286,7 +9497,7 @@ var init_gemini2 = __esm({
9286
9497
  sessionId = readSessionId(fullPath, format);
9287
9498
  const messages = readGeminiMessages(fullPath, format);
9288
9499
  const firstUser = messages.find((m) => m.type === "user");
9289
- 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));
9290
9501
  messageCount = messages.length;
9291
9502
  } else if (format === "jsonl") {
9292
9503
  const lines = readHeadSync(fullPath, 1048576).split("\n");
@@ -9308,7 +9519,7 @@ var init_gemini2 = __esm({
9308
9519
  }
9309
9520
  if (m?.type !== "user")
9310
9521
  continue;
9311
- 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));
9312
9523
  break;
9313
9524
  }
9314
9525
  }
@@ -9484,7 +9695,7 @@ var init_gemini2 = __esm({
9484
9695
  if (!existsSync11(tmpRoot))
9485
9696
  return [];
9486
9697
  const projMap = this.loadProjectMap();
9487
- const candidates = [];
9698
+ const candidates2 = [];
9488
9699
  for (const proj of readdirSync7(tmpRoot, { withFileTypes: true })) {
9489
9700
  if (!proj.isDirectory())
9490
9701
  continue;
@@ -9515,13 +9726,13 @@ var init_gemini2 = __esm({
9515
9726
  if (st.mtimeMs < opts.sinceMs)
9516
9727
  continue;
9517
9728
  const trimLen = isJsonl ? 6 : 5;
9518
- candidates.push({ rawIdHint: f.slice(0, -trimLen), mtime: st.mtimeMs });
9729
+ candidates2.push({ rawIdHint: f.slice(0, -trimLen), mtime: st.mtimeMs });
9519
9730
  } catch {
9520
9731
  }
9521
9732
  }
9522
9733
  }
9523
- candidates.sort((a, b) => b.mtime - a.mtime);
9524
- 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;
9525
9736
  const edits = [];
9526
9737
  for (const c of limited) {
9527
9738
  const scan = this.liveScanEdits(c.rawIdHint);
@@ -9540,7 +9751,7 @@ var init_gemini2 = __esm({
9540
9751
  if (!existsSync11(path))
9541
9752
  return map;
9542
9753
  try {
9543
- const json = JSON.parse(readFileSync7(path, "utf-8"));
9754
+ const json = JSON.parse(readFileSync6(path, "utf-8"));
9544
9755
  const projects = json?.projects ?? {};
9545
9756
  for (const projectPath of Object.keys(projects)) {
9546
9757
  const hash = createHash4("sha256").update(projectPath).digest("hex");
@@ -9558,7 +9769,7 @@ var init_gemini2 = __esm({
9558
9769
  return {
9559
9770
  tool: "gemini",
9560
9771
  mtime: statSync9(loc.path).mtimeMs,
9561
- files: [{ name: basename9(loc.path), bytes: readFileSync7(loc.path) }]
9772
+ files: [{ name: basename9(loc.path), bytes: readFileSync6(loc.path) }]
9562
9773
  };
9563
9774
  } catch {
9564
9775
  return null;
@@ -9653,6 +9864,7 @@ var init_opencode = __esm({
9653
9864
  init_session_outcome();
9654
9865
  init_session_git();
9655
9866
  init_generic_engine();
9867
+ init_flat_string();
9656
9868
  PREFIX2 = "opencode_";
9657
9869
  OpencodeBackend = class {
9658
9870
  id = "opencode";
@@ -9820,7 +10032,7 @@ var init_opencode = __esm({
9820
10032
  if (firstUser) {
9821
10033
  try {
9822
10034
  const parsed = JSON.parse(firstUser.data);
9823
- firstPrompt = String(parsed?.text || "").slice(0, 200);
10035
+ firstPrompt = flatString(String(parsed?.text || "").slice(0, 200));
9824
10036
  } catch {
9825
10037
  }
9826
10038
  }
@@ -10164,7 +10376,7 @@ var init_opencode = __esm({
10164
10376
  });
10165
10377
 
10166
10378
  // ../engine/dist/core/backends/codex.js
10167
- 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";
10168
10380
  import { basename as basename10, dirname as dirname10, join as join13 } from "path";
10169
10381
  function parseShellWrites(cmd) {
10170
10382
  const out = [];
@@ -10293,7 +10505,7 @@ function flattenContent(content) {
10293
10505
  }
10294
10506
  function readFirstLine(path) {
10295
10507
  try {
10296
- const head = readFileSync8(path, "utf-8").split("\n", 1)[0] || "";
10508
+ const head = readFileSync7(path, "utf-8").split("\n", 1)[0] || "";
10297
10509
  if (!head)
10298
10510
  return null;
10299
10511
  return JSON.parse(head);
@@ -10311,6 +10523,7 @@ var init_codex2 = __esm({
10311
10523
  init_session_git();
10312
10524
  init_generic_engine();
10313
10525
  init_tail_read();
10526
+ init_flat_string();
10314
10527
  PREFIX3 = "codex_";
10315
10528
  CodexBackend = class {
10316
10529
  id = "codex";
@@ -10410,7 +10623,7 @@ var init_codex2 = __esm({
10410
10623
  let firstPrompt = "";
10411
10624
  let messageCount = 0;
10412
10625
  try {
10413
- const lines = readFileSync8(filePath, "utf-8").split("\n");
10626
+ const lines = readFileSync7(filePath, "utf-8").split("\n");
10414
10627
  for (const line of lines) {
10415
10628
  if (!line.trim())
10416
10629
  continue;
@@ -10423,7 +10636,7 @@ var init_codex2 = __esm({
10423
10636
  if (ev.type === "event_msg" && ev.payload?.type === "user_message") {
10424
10637
  messageCount++;
10425
10638
  if (!firstPrompt && ev.payload.message) {
10426
- firstPrompt = String(ev.payload.message).slice(0, 200);
10639
+ firstPrompt = flatString(String(ev.payload.message).slice(0, 200));
10427
10640
  }
10428
10641
  } else if (ev.type === "response_item" && ev.payload?.type === "message") {
10429
10642
  messageCount++;
@@ -10524,23 +10737,24 @@ var init_codex2 = __esm({
10524
10737
  return null;
10525
10738
  }
10526
10739
  /**
10527
- * Stream a Codex JSONL rollout into canonical events. Codex separates:
10528
- * - event_msg + payload.type='user_message' → user
10529
- * - response_item + payload.type='message' → assistant_text
10530
- * - response_item + payload.type='function_call' → tool_use
10531
- * - response_item + payload.type='function_call_output' → tool_result
10532
- *
10533
- * Filters Codex's wrapper-bearing user messages (environment_context etc.).
10534
- *
10535
- * **Subagent fan-out:** Codex orchestrates work via `spawn_agent` calls
10536
- * that produce sibling rollout files in the same YYYY/MM/DD directory.
10537
- * Sub-agent rollouts carry `payload.source.subagent.thread_spawn` linking
10538
- * back to the parent. The actual file edits often happen inside those
10539
- * subagent rollouts — without folding their events into the parent's
10540
- * stream, replay/scan would see the parent as edit-free. We append every
10541
- * subagent rollout's events here so the generic engine sees one unified
10542
- * stream per user-facing session.
10543
- */
10740
+ * Stream a Codex JSONL rollout into canonical events. Codex separates:
10741
+ * - event_msg + payload.type='user_message' → user
10742
+ * - response_item + payload.type='message' → user OR assistant_text,
10743
+ * by payload.role
10744
+ * - response_item + payload.type='function_call' → tool_use
10745
+ * - response_item + payload.type='function_call_output' → tool_result
10746
+ *
10747
+ * Filters Codex's wrapper-bearing user messages (environment_context etc.).
10748
+ *
10749
+ * **Subagent fan-out:** Codex orchestrates work via `spawn_agent` calls
10750
+ * that produce sibling rollout files in the same YYYY/MM/DD directory.
10751
+ * Sub-agent rollouts carry `payload.source.subagent.thread_spawn` linking
10752
+ * back to the parent. The actual file edits often happen inside those
10753
+ * subagent rollouts — without folding their events into the parent's
10754
+ * stream, replay/scan would see the parent as edit-free. We append every
10755
+ * subagent rollout's events here so the generic engine sees one unified
10756
+ * stream per user-facing session.
10757
+ */
10544
10758
  readEvents(rawId) {
10545
10759
  const located = findCodexSessionFile(this.toRawId(rawId));
10546
10760
  if (!located)
@@ -10561,7 +10775,7 @@ var init_codex2 = __esm({
10561
10775
  continue;
10562
10776
  let firstLine = "";
10563
10777
  try {
10564
- firstLine = readFileSync8(path, "utf-8").split("\n", 1)[0] || "";
10778
+ firstLine = readFileSync7(path, "utf-8").split("\n", 1)[0] || "";
10565
10779
  } catch {
10566
10780
  continue;
10567
10781
  }
@@ -10585,7 +10799,7 @@ var init_codex2 = __esm({
10585
10799
  let raw = "";
10586
10800
  let mtime = 0;
10587
10801
  try {
10588
- raw = readFileSync8(path, "utf-8");
10802
+ raw = readFileSync7(path, "utf-8");
10589
10803
  mtime = statSync10(path).mtimeMs;
10590
10804
  } catch {
10591
10805
  return [];
@@ -10610,6 +10824,11 @@ var init_codex2 = __esm({
10610
10824
  if (text && !isCodexInjectedWrapper2(text)) {
10611
10825
  events.push({ kind: "user", ts: ts2, tsIso, line: lineNum, text });
10612
10826
  }
10827
+ } else if (obj.type === "response_item" && payload.type === "message" && payload.role === "user") {
10828
+ const text = flattenContent(payload.content).trim();
10829
+ if (text && !isCodexInjectedWrapper2(text)) {
10830
+ events.push({ kind: "user", ts: ts2, tsIso, line: lineNum, text });
10831
+ }
10613
10832
  } else if (obj.type === "response_item" && payload.type === "message" && payload.role === "assistant") {
10614
10833
  const text = flattenContent(payload.content).trim();
10615
10834
  if (text)
@@ -10757,7 +10976,7 @@ var init_codex2 = __esm({
10757
10976
  let parentId = null;
10758
10977
  let isSubagent = false;
10759
10978
  try {
10760
- const first = readFileSync8(p, "utf-8").split("\n")[0];
10979
+ const first = readFileSync7(p, "utf-8").split("\n")[0];
10761
10980
  if (first) {
10762
10981
  const meta = JSON.parse(first);
10763
10982
  const payload = meta?.payload || {};
@@ -10813,7 +11032,7 @@ var init_codex2 = __esm({
10813
11032
  const push = (path, name) => {
10814
11033
  try {
10815
11034
  const st = statSync10(path);
10816
- files.push({ name, bytes: readFileSync8(path) });
11035
+ files.push({ name, bytes: readFileSync7(path) });
10817
11036
  if (st.mtimeMs > mtime)
10818
11037
  mtime = st.mtimeMs;
10819
11038
  } catch {
@@ -10831,10 +11050,10 @@ var init_codex2 = __esm({
10831
11050
  if (p === loc.path)
10832
11051
  continue;
10833
11052
  try {
10834
- const fd = openSync5(p, "r");
11053
+ const fd = openSync6(p, "r");
10835
11054
  const buf = Buffer.alloc(4096);
10836
- const n = readSync5(fd, buf, 0, 4096, 0);
10837
- closeSync5(fd);
11055
+ const n = readSync6(fd, buf, 0, 4096, 0);
11056
+ closeSync6(fd);
10838
11057
  const head = buf.toString("utf-8", 0, n);
10839
11058
  if (head.includes(parentId) && head.includes("parent_thread_id"))
10840
11059
  push(p, `subagents/${f}`);
@@ -10871,13 +11090,13 @@ var init_codex2 = __esm({
10871
11090
  });
10872
11091
 
10873
11092
  // ../engine/dist/core/backends/agy.js
10874
- 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";
10875
11094
  import { basename as basename11, dirname as dirname11, join as join14 } from "path";
10876
11095
  function getFallbackProjectPath(homeDir) {
10877
11096
  try {
10878
11097
  const settingsPath2 = join14(homeDir, "settings.json");
10879
11098
  if (existsSync13(settingsPath2)) {
10880
- const json = JSON.parse(readFileSync9(settingsPath2, "utf-8"));
11099
+ const json = JSON.parse(readFileSync8(settingsPath2, "utf-8"));
10881
11100
  if (Array.isArray(json?.trustedWorkspaces) && json.trustedWorkspaces[0]) {
10882
11101
  return json.trustedWorkspaces[0];
10883
11102
  }
@@ -10904,7 +11123,7 @@ function resolveAgyProject(logsDir, homeDir) {
10904
11123
  function extractProjectPathFromTranscript(path, homeDir) {
10905
11124
  let content;
10906
11125
  try {
10907
- content = readFileSync9(path, "utf-8");
11126
+ content = readFileSync8(path, "utf-8");
10908
11127
  } catch {
10909
11128
  return "";
10910
11129
  }
@@ -10990,6 +11209,7 @@ var init_agy = __esm({
10990
11209
  init_session_outcome();
10991
11210
  init_session_git();
10992
11211
  init_generic_engine();
11212
+ init_flat_string();
10993
11213
  PREFIX4 = "agy_";
10994
11214
  AgyBackend = class {
10995
11215
  id = "agy";
@@ -11082,7 +11302,7 @@ var init_agy = __esm({
11082
11302
  let firstPrompt = "";
11083
11303
  let messageCount = 0;
11084
11304
  try {
11085
- const lines = readFileSync9(filePath, "utf-8").split("\n").filter(Boolean);
11305
+ const lines = readFileSync8(filePath, "utf-8").split("\n").filter(Boolean);
11086
11306
  messageCount = lines.length;
11087
11307
  for (const line of lines) {
11088
11308
  const obj = JSON.parse(line);
@@ -11090,7 +11310,7 @@ var init_agy = __esm({
11090
11310
  const content = obj.content;
11091
11311
  const match = content.match(/<USER_REQUEST>([\s\S]*?)<\/USER_REQUEST>/);
11092
11312
  const rawText = match ? match[1].trim() : content.trim();
11093
- firstPrompt = rawText.slice(0, 200);
11313
+ firstPrompt = flatString(rawText.slice(0, 200));
11094
11314
  break;
11095
11315
  }
11096
11316
  }
@@ -11172,7 +11392,7 @@ var init_agy = __esm({
11172
11392
  return [];
11173
11393
  let rawText;
11174
11394
  try {
11175
- rawText = readFileSync9(located.path, "utf-8");
11395
+ rawText = readFileSync8(located.path, "utf-8");
11176
11396
  } catch {
11177
11397
  return [];
11178
11398
  }
@@ -11324,7 +11544,7 @@ var init_agy = __esm({
11324
11544
  if (brainRoots.length === 0)
11325
11545
  return [];
11326
11546
  const brainDir = brainRoots[0];
11327
- const candidates = [];
11547
+ const candidates2 = [];
11328
11548
  let sessionDirs;
11329
11549
  try {
11330
11550
  sessionDirs = readdirSync9(brainDir);
@@ -11346,12 +11566,12 @@ var init_agy = __esm({
11346
11566
  const st = statSync11(filePath);
11347
11567
  if (st.mtimeMs < opts.sinceMs)
11348
11568
  continue;
11349
- candidates.push({ rawId, mtime: st.mtimeMs });
11569
+ candidates2.push({ rawId, mtime: st.mtimeMs });
11350
11570
  } catch {
11351
11571
  }
11352
11572
  }
11353
- candidates.sort((a, b) => b.mtime - a.mtime);
11354
- 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;
11355
11575
  const edits = [];
11356
11576
  for (const c of limited) {
11357
11577
  const scan = this.liveScanEdits(c.rawId);
@@ -11372,7 +11592,7 @@ var init_agy = __esm({
11372
11592
  return {
11373
11593
  tool: "agy",
11374
11594
  mtime: statSync11(loc.path).mtimeMs,
11375
- files: [{ name: basename11(loc.path), bytes: readFileSync9(loc.path) }]
11595
+ files: [{ name: basename11(loc.path), bytes: readFileSync8(loc.path) }]
11376
11596
  };
11377
11597
  } catch {
11378
11598
  return null;
@@ -11444,6 +11664,24 @@ var init_backends = __esm({
11444
11664
  }
11445
11665
  });
11446
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
+
11447
11685
  // ../engine/dist/core/source-discovery.js
11448
11686
  import { createHash as createHash5 } from "crypto";
11449
11687
  import { existsSync as existsSync14, readdirSync as readdirSync10, statSync as statSync12 } from "fs";
@@ -11550,7 +11788,7 @@ var init_source_discovery = __esm({
11550
11788
 
11551
11789
  // ../engine/dist/core/home-discovery.js
11552
11790
  import { execFileSync as execFileSync2 } from "child_process";
11553
- 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";
11554
11792
  import { homedir as homedir5, platform as osPlatform } from "os";
11555
11793
  import { join as join16 } from "path";
11556
11794
  function candidateRoots(plat = osPlatform(), env = process.env) {
@@ -11672,7 +11910,7 @@ function declaredHomes(env = process.env, plat = osPlatform()) {
11672
11910
  for (const rc of rcs) {
11673
11911
  let text;
11674
11912
  try {
11675
- text = readFileSync10(rc, "utf-8");
11913
+ text = readFileSync9(rc, "utf-8");
11676
11914
  } catch {
11677
11915
  continue;
11678
11916
  }
@@ -11697,7 +11935,7 @@ function runningToolHomes(plat = osPlatform()) {
11697
11935
  for (const pid of pids) {
11698
11936
  let env;
11699
11937
  try {
11700
- env = readFileSync10(`/proc/${pid}/environ`, "utf-8");
11938
+ env = readFileSync9(`/proc/${pid}/environ`, "utf-8");
11701
11939
  } catch {
11702
11940
  continue;
11703
11941
  }
@@ -11818,7 +12056,7 @@ var init_home_discovery = __esm({
11818
12056
 
11819
12057
  // ../engine/dist/core/detector-install.js
11820
12058
  import { createHash as createHash6 } from "crypto";
11821
- 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";
11822
12060
  import { join as join17 } from "path";
11823
12061
  function managedDetectorPath(name) {
11824
12062
  return join17(getDetectorBinDir(), name + exeSuffix());
@@ -11827,11 +12065,11 @@ function receiptPath(name) {
11827
12065
  return join17(getDetectorBinDir(), `${name}.receipt.json`);
11828
12066
  }
11829
12067
  function sha256File(path) {
11830
- return createHash6("sha256").update(readFileSync11(path)).digest("hex");
12068
+ return createHash6("sha256").update(readFileSync10(path)).digest("hex");
11831
12069
  }
11832
12070
  function readReceipt(name) {
11833
12071
  try {
11834
- return JSON.parse(readFileSync11(receiptPath(name), "utf-8"));
12072
+ return JSON.parse(readFileSync10(receiptPath(name), "utf-8"));
11835
12073
  } catch {
11836
12074
  return null;
11837
12075
  }
@@ -11922,7 +12160,7 @@ var init_detector_install = __esm({
11922
12160
 
11923
12161
  // ../engine/dist/core/secret-scanner.js
11924
12162
  import { spawnSync } from "child_process";
11925
- 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";
11926
12164
  import { tmpdir } from "os";
11927
12165
  import { join as join18 } from "path";
11928
12166
  function mask(s) {
@@ -11983,7 +12221,7 @@ function runGitleaks(binary, filePath) {
11983
12221
  return [];
11984
12222
  let parsed = [];
11985
12223
  try {
11986
- parsed = JSON.parse(readFileSync12(reportPath, "utf-8")) || [];
12224
+ parsed = JSON.parse(readFileSync11(reportPath, "utf-8")) || [];
11987
12225
  } catch {
11988
12226
  }
11989
12227
  try {
@@ -12047,7 +12285,7 @@ function runGitleaksDir(binary, dir) {
12047
12285
  }
12048
12286
  let parsed = [];
12049
12287
  try {
12050
- parsed = JSON.parse(readFileSync12(reportPath, "utf-8")) || [];
12288
+ parsed = JSON.parse(readFileSync11(reportPath, "utf-8")) || [];
12051
12289
  } catch {
12052
12290
  }
12053
12291
  try {
@@ -12138,7 +12376,7 @@ function scanFileForSecrets(filePath, opts = {}) {
12138
12376
  out.push({ detector: "trufflehog", ...f });
12139
12377
  if (opts.tenantRules && opts.tenantRules.length > 0) {
12140
12378
  try {
12141
- const text = readFileSync12(filePath, "utf-8");
12379
+ const text = readFileSync11(filePath, "utf-8");
12142
12380
  out.push(...scanTenantRules(text, opts.tenantRules));
12143
12381
  } catch {
12144
12382
  }
@@ -12263,10 +12501,10 @@ var init_types = __esm({
12263
12501
 
12264
12502
  // ../engine/dist/transcript/opencode.js
12265
12503
  async function parseOpenCodeSubagents(parentSessionId) {
12266
- const { existsSync: existsSync45 } = await import("fs");
12504
+ const { existsSync: existsSync46 } = await import("fs");
12267
12505
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
12268
12506
  const dbPath = opencodeBackend2.dbPath();
12269
- if (!existsSync45(dbPath))
12507
+ if (!existsSync46(dbPath))
12270
12508
  return [];
12271
12509
  const realParent = parentSessionId.replace("opencode_", "");
12272
12510
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -12307,10 +12545,10 @@ async function parseOpenCodeSubagents(parentSessionId) {
12307
12545
  }
12308
12546
  }
12309
12547
  async function parseOpenCodeTranscript(sessionId) {
12310
- const { existsSync: existsSync45 } = await import("fs");
12548
+ const { existsSync: existsSync46 } = await import("fs");
12311
12549
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
12312
12550
  const dbPath = opencodeBackend2.dbPath();
12313
- if (!existsSync45(dbPath))
12551
+ if (!existsSync46(dbPath))
12314
12552
  return [];
12315
12553
  const realId = sessionId.replace("opencode_", "");
12316
12554
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -12544,7 +12782,7 @@ var init_transcript = __esm({
12544
12782
  // ../engine/dist/core/project-resolver.js
12545
12783
  import { execSync } from "child_process";
12546
12784
  import { createHash as createHash7 } from "crypto";
12547
- 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";
12548
12786
  import { basename as basename12, dirname as dirname12, isAbsolute, join as join19, resolve, sep } from "path";
12549
12787
  function getProjectsConfigPath() {
12550
12788
  return join19(getDataDir(), "projects.json");
@@ -12562,7 +12800,7 @@ function loadProjectsConfig() {
12562
12800
  if (cachedConfig && cachedConfig.mtime === mtime)
12563
12801
  return cachedConfig.cfg;
12564
12802
  try {
12565
- const raw = readFileSync13(path, "utf-8");
12803
+ const raw = readFileSync12(path, "utf-8");
12566
12804
  const cfg = JSON.parse(raw);
12567
12805
  cachedConfig = { mtime, cfg };
12568
12806
  return cfg;
@@ -12630,8 +12868,8 @@ function resolveBotWorktree(realPath) {
12630
12868
  };
12631
12869
  }
12632
12870
  function matchDeclaredProject(realPath, projects) {
12633
- const candidates = [...projects].sort((a, b) => b.root.length - a.root.length);
12634
- for (const proj of candidates) {
12871
+ const candidates2 = [...projects].sort((a, b) => b.root.length - a.root.length);
12872
+ for (const proj of candidates2) {
12635
12873
  const root = safeRealpath(proj.root);
12636
12874
  if (!isInside(realPath, root))
12637
12875
  continue;
@@ -13665,7 +13903,7 @@ var init_utils = __esm({
13665
13903
  });
13666
13904
 
13667
13905
  // ../engine/dist/parsers/plan-source.js
13668
- 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";
13669
13907
  import { homedir as homedir7 } from "os";
13670
13908
  import { join as join21, basename as basename13 } from "path";
13671
13909
  import { createHash as createHash8 } from "crypto";
@@ -13707,6 +13945,7 @@ var init_plan_source = __esm({
13707
13945
  init_claude2();
13708
13946
  init_agy();
13709
13947
  init_settings();
13948
+ init_project_dir_name();
13710
13949
  MAX_CHUNK_CHARS = 2e3;
13711
13950
  UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13712
13951
  PlanSource = class {
@@ -13731,7 +13970,7 @@ var init_plan_source = __esm({
13731
13970
  for (const entry of entries) {
13732
13971
  if (!entry.isDirectory() || entry.name.startsWith("."))
13733
13972
  continue;
13734
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
13973
+ const projectPath = decodeProjectDirName(entry.name);
13735
13974
  if (existsSync20(projectPath)) {
13736
13975
  this._knownProjectDirs.push(projectPath);
13737
13976
  }
@@ -13769,7 +14008,7 @@ var init_plan_source = __esm({
13769
14008
  const stat3 = statSync18(filePath);
13770
14009
  const planName = basename13(file, ".md");
13771
14010
  const isAgentPlan = planName.includes("-agent-");
13772
- const content = readFileSync14(filePath, "utf-8");
14011
+ const content = readFileSync13(filePath, "utf-8");
13773
14012
  const fm = parseFrontmatter(content);
13774
14013
  const body = fm.body;
13775
14014
  const firstLine = body.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || planName;
@@ -13811,7 +14050,7 @@ var init_plan_source = __esm({
13811
14050
  const filePath = join21(plansDir, file);
13812
14051
  try {
13813
14052
  const stat3 = statSync18(filePath);
13814
- const content = readFileSync14(filePath, "utf-8");
14053
+ const content = readFileSync13(filePath, "utf-8");
13815
14054
  const firstLine = content.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || file;
13816
14055
  const planName = `${opencodeBackend.idPrefix}plan_${basename13(file, ".md")}`;
13817
14056
  const projectPath = this.extractProjectPath(content);
@@ -13841,7 +14080,7 @@ var init_plan_source = __esm({
13841
14080
  const projectsPath = geminiBackend.projectsJson();
13842
14081
  if (existsSync20(projectsPath)) {
13843
14082
  try {
13844
- const data = JSON.parse(readFileSync14(projectsPath, "utf-8"));
14083
+ const data = JSON.parse(readFileSync13(projectsPath, "utf-8"));
13845
14084
  for (const path of Object.keys(data.projects || {})) {
13846
14085
  projMap.set(createHash8("sha256").update(path).digest("hex"), path);
13847
14086
  }
@@ -13877,7 +14116,7 @@ var init_plan_source = __esm({
13877
14116
  const filePath = join21(plansDir, file);
13878
14117
  try {
13879
14118
  const stat3 = statSync18(filePath);
13880
- const content = readFileSync14(filePath, "utf-8");
14119
+ const content = readFileSync13(filePath, "utf-8");
13881
14120
  const firstLine = content.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || file;
13882
14121
  const planName = `${geminiBackend.idPrefix}plan_${sess}_${basename13(file, ".md")}`;
13883
14122
  yield {
@@ -13956,7 +14195,7 @@ var init_plan_source = __esm({
13956
14195
  async parse(item) {
13957
14196
  if (!existsSync20(item.filePath))
13958
14197
  return [];
13959
- const content = readFileSync14(item.filePath, "utf-8");
14198
+ const content = readFileSync13(item.filePath, "utf-8");
13960
14199
  const chunks = [];
13961
14200
  const { body } = parseFrontmatter(content);
13962
14201
  const sections = splitByHeaders(body);
@@ -14100,7 +14339,7 @@ var init_plan_source = __esm({
14100
14339
  const filePath = join21(sessionPath, file);
14101
14340
  try {
14102
14341
  const stat3 = statSync18(filePath);
14103
- const content = readFileSync14(filePath, "utf-8");
14342
+ const content = readFileSync13(filePath, "utf-8");
14104
14343
  const firstLine = content.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || file;
14105
14344
  const sessionLoc = agyBackend.findSession(rawId);
14106
14345
  const projectPath = sessionLoc?.projectPath || this.extractProjectPath(content);
@@ -14129,7 +14368,7 @@ var init_plan_source = __esm({
14129
14368
  });
14130
14369
 
14131
14370
  // ../engine/dist/parsers/task-source.js
14132
- 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";
14133
14372
  import { join as join22 } from "path";
14134
14373
  var MAX_CHUNK_CHARS2, TaskSource;
14135
14374
  var init_task_source = __esm({
@@ -14179,7 +14418,7 @@ var init_task_source = __esm({
14179
14418
  continue;
14180
14419
  let parsed;
14181
14420
  try {
14182
- const raw = readFileSync15(filePath, "utf-8");
14421
+ const raw = readFileSync14(filePath, "utf-8");
14183
14422
  parsed = JSON.parse(raw);
14184
14423
  if (!Array.isArray(parsed) || parsed.length === 0)
14185
14424
  continue;
@@ -14317,7 +14556,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
14317
14556
  return [];
14318
14557
  let parsed;
14319
14558
  try {
14320
- parsed = JSON.parse(readFileSync15(item.filePath, "utf-8"));
14559
+ parsed = JSON.parse(readFileSync14(item.filePath, "utf-8"));
14321
14560
  } catch {
14322
14561
  return [];
14323
14562
  }
@@ -14349,7 +14588,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
14349
14588
  });
14350
14589
  for (const file of sorted) {
14351
14590
  try {
14352
- const content = readFileSync15(join22(taskDir, file), "utf-8");
14591
+ const content = readFileSync14(join22(taskDir, file), "utf-8");
14353
14592
  const task = JSON.parse(content);
14354
14593
  if (task.subject) {
14355
14594
  tasks.push(task);
@@ -14365,7 +14604,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
14365
14604
  });
14366
14605
 
14367
14606
  // ../engine/dist/parsers/claude-md-source.js
14368
- 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";
14369
14608
  import { homedir as homedir8 } from "os";
14370
14609
  import { join as join23, basename as basename14, dirname as dirname13 } from "path";
14371
14610
  import { createHash as createHash9 } from "crypto";
@@ -14386,7 +14625,7 @@ function discoverProjectDirs(claudeDir) {
14386
14625
  for (const entry of entries) {
14387
14626
  if (!entry.isDirectory() || entry.name.startsWith("."))
14388
14627
  continue;
14389
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
14628
+ const projectPath = decodeProjectDirName(entry.name);
14390
14629
  addProject(projectPath);
14391
14630
  }
14392
14631
  } catch {
@@ -14409,7 +14648,7 @@ function discoverProjectDirs(claudeDir) {
14409
14648
  const gemProjects = geminiBackend.projectsJson();
14410
14649
  if (existsSync22(gemProjects)) {
14411
14650
  try {
14412
- const data = JSON.parse(readFileSync16(gemProjects, "utf-8"));
14651
+ const data = JSON.parse(readFileSync15(gemProjects, "utf-8"));
14413
14652
  for (const path of Object.keys(data.projects || {}))
14414
14653
  addProject(path);
14415
14654
  } catch {
@@ -14443,6 +14682,7 @@ var init_claude_md_source = __esm({
14443
14682
  init_gemini2();
14444
14683
  init_settings();
14445
14684
  init_opencode();
14685
+ init_project_dir_name();
14446
14686
  MAX_CHUNK_CHARS3 = 2e3;
14447
14687
  NOTE_FILES = [
14448
14688
  { filename: "CLAUDE.md", tool: "claude" },
@@ -14523,7 +14763,7 @@ var init_claude_md_source = __esm({
14523
14763
  async parse(item) {
14524
14764
  if (!existsSync22(item.filePath))
14525
14765
  return [];
14526
- const content = readFileSync16(item.filePath, "utf-8");
14766
+ const content = readFileSync15(item.filePath, "utf-8");
14527
14767
  const chunks = [];
14528
14768
  const sections = splitByHeaders(content);
14529
14769
  for (let i = 0; i < sections.length; i++) {
@@ -14565,7 +14805,7 @@ var init_claude_md_source = __esm({
14565
14805
  const projectDir = dirname13(filePath);
14566
14806
  const projectName = basename14(projectDir);
14567
14807
  const projectPath = projectDir;
14568
- const content = readFileSync16(filePath, "utf-8");
14808
+ const content = readFileSync15(filePath, "utf-8");
14569
14809
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"))?.trim()?.slice(0, 200) || `${filename} for ${projectName}`;
14570
14810
  const id = tool === "claude" ? projectName : `${projectName}_${tool}`;
14571
14811
  return {
@@ -14590,7 +14830,7 @@ var init_claude_md_source = __esm({
14590
14830
  });
14591
14831
 
14592
14832
  // ../engine/dist/parsers/agent-memory-source.js
14593
- 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";
14594
14834
  import { homedir as homedir9 } from "os";
14595
14835
  import { join as join24, basename as basename15 } from "path";
14596
14836
  function parseFrontmatter2(content) {
@@ -14618,7 +14858,7 @@ function parseFrontmatter2(content) {
14618
14858
  return { fm, body };
14619
14859
  }
14620
14860
  function projectHashToPath(hashDir) {
14621
- return hashDir.replace(/^-/, "/").replace(/-/g, "/");
14861
+ return decodeProjectDirName(hashDir);
14622
14862
  }
14623
14863
  function discoverMemoryDirs() {
14624
14864
  const out = [];
@@ -14660,6 +14900,7 @@ var init_agent_memory_source = __esm({
14660
14900
  init_utils();
14661
14901
  init_claude2();
14662
14902
  init_settings();
14903
+ init_project_dir_name();
14663
14904
  MAX_CHUNK_CHARS4 = 2e3;
14664
14905
  MAX_CHUNKS = 12;
14665
14906
  AgentMemorySource = class {
@@ -14690,7 +14931,7 @@ var init_agent_memory_source = __esm({
14690
14931
  async parse(item) {
14691
14932
  if (!existsSync23(item.filePath))
14692
14933
  return [];
14693
- const content = readFileSync17(item.filePath, "utf-8");
14934
+ const content = readFileSync16(item.filePath, "utf-8");
14694
14935
  const { body } = parseFrontmatter2(content);
14695
14936
  const chunks = [];
14696
14937
  const sections = splitByHeaders(body);
@@ -14743,7 +14984,7 @@ var init_agent_memory_source = __esm({
14743
14984
  fileToItem(filePath, filename, projectPath, _projectHash) {
14744
14985
  try {
14745
14986
  const stat3 = statSync21(filePath);
14746
- const content = readFileSync17(filePath, "utf-8");
14987
+ const content = readFileSync16(filePath, "utf-8");
14747
14988
  const { fm } = parseFrontmatter2(content);
14748
14989
  const stem = filename.replace(/\.md$/, "");
14749
14990
  const idBase = fm.name || stem;
@@ -14958,7 +15199,7 @@ var init_history_source = __esm({
14958
15199
  });
14959
15200
 
14960
15201
  // ../engine/dist/parsers/paste-source.js
14961
- 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";
14962
15203
  import { homedir as homedir11 } from "os";
14963
15204
  import { join as join26, basename as basename16 } from "path";
14964
15205
  var MAX_CHUNK_CHARS6, MAX_FILE_SIZE, PasteSource;
@@ -15014,7 +15255,7 @@ var init_paste_source = __esm({
15014
15255
  if (stat3.size < 10)
15015
15256
  continue;
15016
15257
  const hash = basename16(file, ".txt");
15017
- const content = readFileSync18(filePath, "utf-8");
15258
+ const content = readFileSync17(filePath, "utf-8");
15018
15259
  const firstLine = content.split("\n").find((l) => l.trim().length > 5)?.trim()?.slice(0, 100) || `Paste ${hash.slice(0, 8)}`;
15019
15260
  yield {
15020
15261
  id: hash,
@@ -15035,7 +15276,7 @@ var init_paste_source = __esm({
15035
15276
  async parse(item) {
15036
15277
  if (!existsSync25(item.filePath))
15037
15278
  return [];
15038
- const content = readFileSync18(item.filePath, "utf-8");
15279
+ const content = readFileSync17(item.filePath, "utf-8");
15039
15280
  if (content.trim().length < 20)
15040
15281
  return [];
15041
15282
  return [{
@@ -15058,7 +15299,7 @@ var init_paste_source = __esm({
15058
15299
  });
15059
15300
 
15060
15301
  // ../engine/dist/parsers/gemini-source.js
15061
- 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";
15062
15303
  import { join as join27, basename as basename17 } from "path";
15063
15304
  import { createHash as createHash10 } from "crypto";
15064
15305
  function loadGeminiProjectMap2() {
@@ -15067,7 +15308,7 @@ function loadGeminiProjectMap2() {
15067
15308
  if (!existsSync26(projectsPath))
15068
15309
  return map;
15069
15310
  try {
15070
- const data = JSON.parse(readFileSync19(projectsPath, "utf-8"));
15311
+ const data = JSON.parse(readFileSync18(projectsPath, "utf-8"));
15071
15312
  const projects = data.projects || {};
15072
15313
  for (const [path] of Object.entries(projects)) {
15073
15314
  const hash = createHash10("sha256").update(path).digest("hex");
@@ -15112,7 +15353,7 @@ var init_gemini_source = __esm({
15112
15353
  const filePath = join27(chatsDir, file);
15113
15354
  try {
15114
15355
  const stat3 = statSync24(filePath);
15115
- const content = JSON.parse(readFileSync19(filePath, "utf-8"));
15356
+ const content = JSON.parse(readFileSync18(filePath, "utf-8"));
15116
15357
  const sessionId = content.sessionId || basename17(file, ".json");
15117
15358
  if (seen.has(sessionId))
15118
15359
  continue;
@@ -15193,7 +15434,7 @@ var init_gemini_source = __esm({
15193
15434
  async parse(item) {
15194
15435
  if (!existsSync26(item.filePath))
15195
15436
  return [];
15196
- const content = JSON.parse(readFileSync19(item.filePath, "utf-8"));
15437
+ const content = JSON.parse(readFileSync18(item.filePath, "utf-8"));
15197
15438
  const messages = content.messages || [];
15198
15439
  const chunks = [];
15199
15440
  const getText = (m) => {
@@ -15241,7 +15482,7 @@ var init_gemini_source = __esm({
15241
15482
  });
15242
15483
 
15243
15484
  // ../engine/dist/parsers/gemini-brain-source.js
15244
- 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";
15245
15486
  import { join as join28 } from "path";
15246
15487
  var MAX_CHUNK_CHARS8, GeminiBrainSource;
15247
15488
  var init_gemini_brain_source = __esm({
@@ -15274,14 +15515,14 @@ var init_gemini_brain_source = __esm({
15274
15515
  const filePath = join28(sessionDir, file);
15275
15516
  try {
15276
15517
  const stat3 = statSync25(filePath);
15277
- const content = readFileSync20(filePath, "utf-8");
15518
+ const content = readFileSync19(filePath, "utf-8");
15278
15519
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"))?.trim() || "";
15279
15520
  const heading = content.split("\n").find((l) => l.startsWith("#"))?.replace(/^#+\s*/, "").trim() || file;
15280
15521
  const metaPath = `${filePath}.metadata.json`;
15281
15522
  let metaSummary = "";
15282
15523
  if (existsSync27(metaPath)) {
15283
15524
  try {
15284
- const meta = JSON.parse(readFileSync20(metaPath, "utf-8"));
15525
+ const meta = JSON.parse(readFileSync19(metaPath, "utf-8"));
15285
15526
  metaSummary = meta.summary || "";
15286
15527
  } catch {
15287
15528
  }
@@ -15312,7 +15553,7 @@ var init_gemini_brain_source = __esm({
15312
15553
  async parse(item) {
15313
15554
  if (!existsSync27(item.filePath))
15314
15555
  return [];
15315
- const content = readFileSync20(item.filePath, "utf-8");
15556
+ const content = readFileSync19(item.filePath, "utf-8");
15316
15557
  const chunks = [];
15317
15558
  const sections = splitByHeaders(content);
15318
15559
  for (let i = 0; i < sections.length; i++) {
@@ -15655,7 +15896,7 @@ var init_opencode_todo_source = __esm({
15655
15896
  });
15656
15897
 
15657
15898
  // ../engine/dist/parsers/codex-session-source.js
15658
- 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";
15659
15900
  import { join as join29, basename as basename18 } from "path";
15660
15901
  function getCodexSessionsDir() {
15661
15902
  return codexBackend.sessionsDir();
@@ -15683,7 +15924,7 @@ function extractReasoningText(event) {
15683
15924
  return "";
15684
15925
  }
15685
15926
  function readJsonlLines(filePath) {
15686
- const text = readFileSync21(filePath, "utf-8");
15927
+ const text = readFileSync20(filePath, "utf-8");
15687
15928
  const lines = text.split("\n");
15688
15929
  const events = [];
15689
15930
  for (const line of lines) {
@@ -15718,7 +15959,7 @@ var init_codex_session_source = __esm({
15718
15959
  for (const filePath of jsonlFiles) {
15719
15960
  try {
15720
15961
  const stat3 = statSync26(filePath);
15721
- const lines = readFileSync21(filePath, "utf-8").split("\n");
15962
+ const lines = readFileSync20(filePath, "utf-8").split("\n");
15722
15963
  const firstLine = lines.find((l) => l.trim());
15723
15964
  if (!firstLine)
15724
15965
  continue;
@@ -15891,7 +16132,7 @@ var init_codex_session_source = __esm({
15891
16132
  });
15892
16133
 
15893
16134
  // ../engine/dist/parsers/diary-source.js
15894
- 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";
15895
16136
  import { join as join30, basename as basename19 } from "path";
15896
16137
  var MAX_CHUNK_CHARS11, DiarySource;
15897
16138
  var init_diary_source = __esm({
@@ -15917,7 +16158,7 @@ var init_diary_source = __esm({
15917
16158
  const filePath = join30(agentPath2, file);
15918
16159
  try {
15919
16160
  const stat3 = statSync27(filePath);
15920
- const raw = readFileSync22(filePath, "utf-8");
16161
+ const raw = readFileSync21(filePath, "utf-8");
15921
16162
  const entry = JSON.parse(raw);
15922
16163
  const entryId = basename19(file, ".json");
15923
16164
  const title = `[${entry.agent}] ${entry.topic}: ${entry.content.slice(0, 80)}`;
@@ -15944,7 +16185,7 @@ var init_diary_source = __esm({
15944
16185
  async parse(item) {
15945
16186
  if (!existsSync31(item.filePath))
15946
16187
  return [];
15947
- const raw = readFileSync22(item.filePath, "utf-8");
16188
+ const raw = readFileSync21(item.filePath, "utf-8");
15948
16189
  const entry = JSON.parse(raw);
15949
16190
  if (!entry.content || entry.content.trim().length < 10)
15950
16191
  return [];
@@ -16007,7 +16248,7 @@ var init_diary_source = __esm({
16007
16248
  const entries = [];
16008
16249
  for (const file of files) {
16009
16250
  try {
16010
- const raw = readFileSync22(join30(agentDir, file), "utf-8");
16251
+ const raw = readFileSync21(join30(agentDir, file), "utf-8");
16011
16252
  entries.push(JSON.parse(raw));
16012
16253
  } catch {
16013
16254
  continue;
@@ -16020,7 +16261,7 @@ var init_diary_source = __esm({
16020
16261
  });
16021
16262
 
16022
16263
  // ../engine/dist/parsers/skills-source.js
16023
- 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";
16024
16265
  import { homedir as homedir12 } from "os";
16025
16266
  import { join as join31 } from "path";
16026
16267
  function resolveRoots() {
@@ -16113,7 +16354,7 @@ var init_skills_source = __esm({
16113
16354
  continue;
16114
16355
  let content;
16115
16356
  try {
16116
- content = readFileSync23(skillMd, "utf-8");
16357
+ content = readFileSync22(skillMd, "utf-8");
16117
16358
  } catch {
16118
16359
  continue;
16119
16360
  }
@@ -16173,7 +16414,7 @@ var init_skills_source = __esm({
16173
16414
  async parse(item) {
16174
16415
  if (!existsSync32(item.filePath))
16175
16416
  return [];
16176
- const content = readFileSync23(item.filePath, "utf-8");
16417
+ const content = readFileSync22(item.filePath, "utf-8");
16177
16418
  if (!content.trim())
16178
16419
  return [];
16179
16420
  return [{
@@ -16196,14 +16437,14 @@ var init_skills_source = __esm({
16196
16437
  });
16197
16438
 
16198
16439
  // ../engine/dist/parsers/mcps-source.js
16199
- 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";
16200
16441
  import { homedir as homedir13 } from "os";
16201
16442
  import { join as join32 } from "path";
16202
16443
  function readJson(path) {
16203
16444
  if (!existsSync33(path))
16204
16445
  return null;
16205
16446
  try {
16206
- const raw = readFileSync24(path, "utf-8");
16447
+ const raw = readFileSync23(path, "utf-8");
16207
16448
  const stripped = path.endsWith(".jsonc") ? raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/,(\s*[}\]])/g, "$1") : raw;
16208
16449
  return JSON.parse(stripped);
16209
16450
  } catch {
@@ -16316,7 +16557,7 @@ var init_mcps_source = __esm({
16316
16557
  return;
16317
16558
  let content;
16318
16559
  try {
16319
- content = readFileSync24(path, "utf-8");
16560
+ content = readFileSync23(path, "utf-8");
16320
16561
  } catch {
16321
16562
  return;
16322
16563
  }
@@ -16485,7 +16726,7 @@ var init_toolkit_format = __esm({
16485
16726
  });
16486
16727
 
16487
16728
  // ../engine/dist/parsers/slash-commands-source.js
16488
- 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";
16489
16730
  import { join as join33, basename as basename20 } from "path";
16490
16731
  function parseCommand(content, fallbackName, format) {
16491
16732
  if (format === "toml") {
@@ -16513,6 +16754,7 @@ var init_slash_commands_source = __esm({
16513
16754
  init_codex2();
16514
16755
  init_settings();
16515
16756
  init_toolkit_format();
16757
+ init_project_dir_name();
16516
16758
  MAX_CHUNK_CHARS13 = 2e3;
16517
16759
  SlashCommandsSource = class {
16518
16760
  sourceType = "command";
@@ -16526,7 +16768,7 @@ var init_slash_commands_source = __esm({
16526
16768
  for (const entry of readdirSync26(projectsRoot, { withFileTypes: true })) {
16527
16769
  if (!entry.isDirectory() || entry.name.startsWith("."))
16528
16770
  continue;
16529
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
16771
+ const projectPath = decodeProjectDirName(entry.name);
16530
16772
  const cmdDir = join33(projectPath, ".claude", "commands");
16531
16773
  if (existsSync34(cmdDir))
16532
16774
  roots.push({ path: cmdDir, tool: "claude", scope: "project", projectPath, format: "md" });
@@ -16559,7 +16801,7 @@ var init_slash_commands_source = __esm({
16559
16801
  const filePath = join33(root.path, file);
16560
16802
  try {
16561
16803
  const stat3 = statSync30(filePath);
16562
- const content = readFileSync25(filePath, "utf-8");
16804
+ const content = readFileSync24(filePath, "utf-8");
16563
16805
  const parsed = parseCommand(content, basename20(file, ext), root.format);
16564
16806
  yield {
16565
16807
  id: `${root.tool}_cmd_${root.scope}_${parsed.name}`,
@@ -16585,7 +16827,7 @@ var init_slash_commands_source = __esm({
16585
16827
  async parse(item) {
16586
16828
  if (!existsSync34(item.filePath))
16587
16829
  return [];
16588
- const content = readFileSync25(item.filePath, "utf-8");
16830
+ const content = readFileSync24(item.filePath, "utf-8");
16589
16831
  if (!content.trim())
16590
16832
  return [];
16591
16833
  return [{
@@ -16608,7 +16850,7 @@ var init_slash_commands_source = __esm({
16608
16850
  });
16609
16851
 
16610
16852
  // ../engine/dist/parsers/subagents-source.js
16611
- 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";
16612
16854
  import { join as join34, basename as basename21 } from "path";
16613
16855
  function parseAgent(content, fallbackName, format) {
16614
16856
  if (format === "toml") {
@@ -16638,6 +16880,7 @@ var init_subagents_source = __esm({
16638
16880
  init_codex2();
16639
16881
  init_settings();
16640
16882
  init_toolkit_format();
16883
+ init_project_dir_name();
16641
16884
  MAX_CHUNK_CHARS14 = 2e3;
16642
16885
  SubagentsSource = class {
16643
16886
  sourceType = "agent";
@@ -16651,7 +16894,7 @@ var init_subagents_source = __esm({
16651
16894
  for (const entry of readdirSync27(projectsRoot, { withFileTypes: true })) {
16652
16895
  if (!entry.isDirectory() || entry.name.startsWith("."))
16653
16896
  continue;
16654
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
16897
+ const projectPath = decodeProjectDirName(entry.name);
16655
16898
  const agentDir = join34(projectPath, ".claude", "agents");
16656
16899
  if (existsSync35(agentDir))
16657
16900
  roots.push({ path: agentDir, tool: "claude", scope: "project", projectPath, format: "md" });
@@ -16684,7 +16927,7 @@ var init_subagents_source = __esm({
16684
16927
  const filePath = join34(root.path, file);
16685
16928
  try {
16686
16929
  const stat3 = statSync31(filePath);
16687
- const content = readFileSync26(filePath, "utf-8");
16930
+ const content = readFileSync25(filePath, "utf-8");
16688
16931
  const parsed = parseAgent(content, basename21(file, ext), root.format);
16689
16932
  yield {
16690
16933
  id: `${root.tool}_agent_${root.scope}_${parsed.name}`,
@@ -16711,7 +16954,7 @@ var init_subagents_source = __esm({
16711
16954
  async parse(item) {
16712
16955
  if (!existsSync35(item.filePath))
16713
16956
  return [];
16714
- const content = readFileSync26(item.filePath, "utf-8");
16957
+ const content = readFileSync25(item.filePath, "utf-8");
16715
16958
  if (!content.trim())
16716
16959
  return [];
16717
16960
  return [{
@@ -16734,13 +16977,13 @@ var init_subagents_source = __esm({
16734
16977
  });
16735
16978
 
16736
16979
  // ../engine/dist/parsers/hooks-source.js
16737
- 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";
16738
16981
  import { join as join35 } from "path";
16739
16982
  function readJson2(path) {
16740
16983
  if (!existsSync36(path))
16741
16984
  return null;
16742
16985
  try {
16743
- return JSON.parse(readFileSync27(path, "utf-8"));
16986
+ return JSON.parse(readFileSync26(path, "utf-8"));
16744
16987
  } catch {
16745
16988
  return null;
16746
16989
  }
@@ -16751,6 +16994,7 @@ var init_hooks_source = __esm({
16751
16994
  "use strict";
16752
16995
  init_claude2();
16753
16996
  init_settings();
16997
+ init_project_dir_name();
16754
16998
  HooksSource = class {
16755
16999
  sourceType = "hook";
16756
17000
  async *discover() {
@@ -16763,7 +17007,7 @@ var init_hooks_source = __esm({
16763
17007
  for (const entry of readdirSync28(projectsRoot, { withFileTypes: true })) {
16764
17008
  if (!entry.isDirectory() || entry.name.startsWith("."))
16765
17009
  continue;
16766
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
17010
+ const projectPath = decodeProjectDirName(entry.name);
16767
17011
  const settingsPath2 = join35(projectPath, ".claude", "settings.json");
16768
17012
  if (existsSync36(settingsPath2)) {
16769
17013
  yield* this.fromSettings(settingsPath2, "project", projectPath);
@@ -16856,14 +17100,14 @@ var init_hooks_source = __esm({
16856
17100
  });
16857
17101
 
16858
17102
  // ../engine/dist/parsers/plugins-source.js
16859
- 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";
16860
17104
  import { homedir as homedir14 } from "os";
16861
17105
  import { join as join36 } from "path";
16862
17106
  function readJson3(path) {
16863
17107
  if (!existsSync37(path))
16864
17108
  return null;
16865
17109
  try {
16866
- return JSON.parse(readFileSync28(path, "utf-8"));
17110
+ return JSON.parse(readFileSync27(path, "utf-8"));
16867
17111
  } catch {
16868
17112
  return null;
16869
17113
  }
@@ -17111,14 +17355,16 @@ var init_extractor_version = __esm({
17111
17355
  "use strict";
17112
17356
  BASE_EXTRACTOR_VERSION = 2;
17113
17357
  TOOL_EXTRACTOR_BUMP = {
17114
- agy: 2
17358
+ agy: 2,
17359
+ claude: 1,
17360
+ codex: 1
17115
17361
  };
17116
17362
  EXTRACTOR_VERSION = BASE_EXTRACTOR_VERSION;
17117
17363
  }
17118
17364
  });
17119
17365
 
17120
17366
  // src/sync-ledger.ts
17121
- 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";
17122
17368
  import { join as join37, dirname as dirname14 } from "node:path";
17123
17369
  function toRow(entry) {
17124
17370
  if (entry === void 0) return void 0;
@@ -17127,24 +17373,77 @@ function toRow(entry) {
17127
17373
  function load() {
17128
17374
  if (cache3) return cache3;
17129
17375
  try {
17130
- const parsed = JSON.parse(readFileSync29(ledgerPath(), "utf-8"));
17376
+ const parsed = JSON.parse(readFileSync28(ledgerPath(), "utf-8"));
17131
17377
  cache3 = parsed && typeof parsed === "object" ? parsed : {};
17132
17378
  } catch {
17133
17379
  cache3 = {};
17134
17380
  }
17135
17381
  return cache3;
17136
17382
  }
17137
- function persist(data) {
17383
+ function persistNow(data) {
17138
17384
  const path = ledgerPath();
17139
17385
  mkdirSync6(dirname14(path), { recursive: true });
17140
17386
  const tmp = `${path}.tmp`;
17141
17387
  writeFileSync5(tmp, JSON.stringify(data));
17142
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);
17143
17442
  }
17144
17443
  function loadItemVersionsAll() {
17145
17444
  if (itemVersionsCache) return itemVersionsCache;
17146
17445
  try {
17147
- const parsed = JSON.parse(readFileSync29(itemVersionsPath(), "utf-8"));
17446
+ const parsed = JSON.parse(readFileSync28(itemVersionsPath(), "utf-8"));
17148
17447
  itemVersionsCache = parsed && typeof parsed === "object" ? parsed : {};
17149
17448
  } catch {
17150
17449
  itemVersionsCache = {};
@@ -17226,7 +17525,7 @@ function markFieldCoverage(server, field, rows) {
17226
17525
  function loadReconcile() {
17227
17526
  if (reconcileCache) return reconcileCache;
17228
17527
  try {
17229
- const parsed = JSON.parse(readFileSync29(reconcilePath(), "utf-8"));
17528
+ const parsed = JSON.parse(readFileSync28(reconcilePath(), "utf-8"));
17230
17529
  reconcileCache = parsed && typeof parsed === "object" ? parsed : {};
17231
17530
  } catch {
17232
17531
  reconcileCache = {};
@@ -17274,7 +17573,7 @@ function syncMode(row, mtime, fileSize, extractorVersion, isAppendOnly) {
17274
17573
  if (row.m >= Math.floor(mtime)) return "skip";
17275
17574
  return "full";
17276
17575
  }
17277
- var ledgerPath, cache3, itemVersionsPath, itemVersionsCache, unackedCursorAttempts, reconcilePath, reconcileCache;
17576
+ var ledgerPath, cache3, LEDGER_FLUSH_MS, pendingWrite, flushTimer, itemVersionsPath, itemVersionsCache, unackedCursorAttempts, reconcilePath, reconcileCache;
17278
17577
  var init_sync_ledger = __esm({
17279
17578
  "src/sync-ledger.ts"() {
17280
17579
  "use strict";
@@ -17282,6 +17581,9 @@ var init_sync_ledger = __esm({
17282
17581
  init_extractor_version();
17283
17582
  ledgerPath = () => join37(getDataDir(), "sync-ledger.json");
17284
17583
  cache3 = null;
17584
+ LEDGER_FLUSH_MS = 2e3;
17585
+ pendingWrite = false;
17586
+ flushTimer = null;
17285
17587
  itemVersionsPath = () => join37(getDataDir(), "item-versions.json");
17286
17588
  itemVersionsCache = null;
17287
17589
  unackedCursorAttempts = 0;
@@ -17316,7 +17618,7 @@ var init_sync_fields = __esm({
17316
17618
  });
17317
17619
 
17318
17620
  // ../engine/dist/core/index-lock.js
17319
- 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";
17320
17622
  import { dirname as dirname15, join as join38 } from "path";
17321
17623
  function acquireIndexLock(opts) {
17322
17624
  const lockPath = lockFilePath();
@@ -17328,7 +17630,7 @@ function acquireIndexLock(opts) {
17328
17630
  }
17329
17631
  let fd;
17330
17632
  try {
17331
- fd = openSync6(lockPath, "wx", 384);
17633
+ fd = openSync7(lockPath, "wx", 384);
17332
17634
  } catch (err) {
17333
17635
  if (err.code === "EEXIST")
17334
17636
  return null;
@@ -17338,7 +17640,7 @@ function acquireIndexLock(opts) {
17338
17640
  try {
17339
17641
  writeFileSync6(fd, JSON.stringify(holder));
17340
17642
  } finally {
17341
- closeSync6(fd);
17643
+ closeSync7(fd);
17342
17644
  }
17343
17645
  let released = false;
17344
17646
  return {
@@ -17348,7 +17650,7 @@ function acquireIndexLock(opts) {
17348
17650
  return;
17349
17651
  released = true;
17350
17652
  try {
17351
- const current = JSON.parse(readFileSync30(lockPath, "utf-8"));
17653
+ const current = JSON.parse(readFileSync29(lockPath, "utf-8"));
17352
17654
  if (current.pid !== holder.pid || current.startedAt !== holder.startedAt)
17353
17655
  return;
17354
17656
  } catch {
@@ -17370,7 +17672,7 @@ function stealStaleLock(lockPath, staleAfterMs) {
17370
17672
  const age = Date.now() - s.mtimeMs;
17371
17673
  let payload;
17372
17674
  try {
17373
- payload = JSON.parse(readFileSync30(lockPath, "utf-8"));
17675
+ payload = JSON.parse(readFileSync29(lockPath, "utf-8"));
17374
17676
  } catch {
17375
17677
  rmSync4(lockPath, { force: true });
17376
17678
  return true;
@@ -17408,7 +17710,7 @@ var init_index_lock = __esm({
17408
17710
  });
17409
17711
 
17410
17712
  // src/http.ts
17411
- import { readFileSync as readFileSync31 } from "node:fs";
17713
+ import { readFileSync as readFileSync30 } from "node:fs";
17412
17714
  function clientHeaders() {
17413
17715
  return {
17414
17716
  "x-chat-recall-cli": CLI_VERSION,
@@ -17426,10 +17728,10 @@ var init_http = __esm({
17426
17728
  "use strict";
17427
17729
  CLI_VERSION = "0.0.0";
17428
17730
  try {
17429
- 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;
17430
17732
  } catch {
17431
17733
  try {
17432
- if (true) CLI_VERSION = "0.5.4";
17734
+ if (true) CLI_VERSION = "0.5.6";
17433
17735
  } catch {
17434
17736
  }
17435
17737
  }
@@ -17442,7 +17744,7 @@ __export(client_events_exports, {
17442
17744
  isTelemetryEnabled: () => isTelemetryEnabled,
17443
17745
  reportClientEvent: () => reportClientEvent
17444
17746
  });
17445
- import { readFileSync as readFileSync32 } from "node:fs";
17747
+ import { readFileSync as readFileSync31 } from "node:fs";
17446
17748
  function scrub(msg) {
17447
17749
  if (!msg) return "";
17448
17750
  const noHome = msg.replace(/\/(?:home|Users)\/[^/\s]+/g, "~");
@@ -17495,7 +17797,7 @@ var init_client_events = __esm({
17495
17797
  init_secret_redactor();
17496
17798
  CLI_VERSION2 = "0.0.0";
17497
17799
  try {
17498
- 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;
17499
17801
  } catch {
17500
17802
  }
17501
17803
  }
@@ -17615,13 +17917,15 @@ var init_auto_update = __esm({
17615
17917
 
17616
17918
  // src/sync-client.ts
17617
17919
  import { join as join40, dirname as dirname16, basename as basename22 } from "node:path";
17618
- 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";
17619
17921
  import { tmpdir as tmpdir3, hostname } from "node:os";
17620
17922
  import { createHash as createHash12 } from "node:crypto";
17621
17923
  import { fileURLToPath } from "node:url";
17622
17924
  function loadAllCredentials() {
17925
+ const fromEnv = envCredentials();
17926
+ if (fromEnv) return [{ serverUrl: fromEnv.serverUrl, token: fromEnv.token }];
17623
17927
  try {
17624
- const parsed = JSON.parse(readFileSync33(credPath(), "utf-8"));
17928
+ const parsed = JSON.parse(readFileSync32(credPath(), "utf-8"));
17625
17929
  if (Array.isArray(parsed?.targets)) return parsed.targets.filter((t) => t?.serverUrl).map((t) => ({ serverUrl: t.serverUrl, token: t.token || "" }));
17626
17930
  if (parsed?.serverUrl) return [{ serverUrl: parsed.serverUrl, token: parsed.token || "" }];
17627
17931
  } catch {
@@ -17767,6 +18071,10 @@ function isLocalHost(serverUrl) {
17767
18071
  async function syncSessions(opts = {}) {
17768
18072
  const targets = loadAllCredentials();
17769
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
+ }
17770
18078
  let agg = null;
17771
18079
  const errors = [];
17772
18080
  for (const cred of targets) {
@@ -17789,6 +18097,7 @@ async function syncSessions(opts = {}) {
17789
18097
  errors.push(`${cred.serverUrl}: ${e instanceof Error ? e.message : e}`);
17790
18098
  }
17791
18099
  }
18100
+ flushLedger();
17792
18101
  if (!agg) throw new Error(`sync failed for all targets:
17793
18102
  ${errors.join("\n ")}`);
17794
18103
  if (errors.length > 0) console.error(`[sync] ${errors.length} target(s) failed (others succeeded):
@@ -17814,11 +18123,11 @@ async function reconcileFieldsForTarget(cred, fullRefs, opts = {}) {
17814
18123
  let pushed = 0, absent = 0, scanned = 0;
17815
18124
  for (const field of SYNC_FIELDS) {
17816
18125
  const needFull = !!opts.force || fieldNeedsFullPass(base2, field);
17817
- const candidates = needFull ? fullRefs() : opts.convRefs ?? fullRefs();
18126
+ const candidates2 = needFull ? fullRefs() : opts.convRefs ?? fullRefs();
17818
18127
  const send = [];
17819
18128
  const mtimeById = /* @__PURE__ */ new Map();
17820
18129
  const absentIds = [];
17821
- for (const ref of candidates) {
18130
+ for (const ref of candidates2) {
17822
18131
  if (!fieldNeedsScan(ledger.get(ref.prefixedId), field, ref.mtime, opts.force)) continue;
17823
18132
  scanned++;
17824
18133
  mtimeById.set(ref.prefixedId, ref.mtime);
@@ -17917,6 +18226,17 @@ function batchScanExternal(items, opts = {}) {
17917
18226
  }
17918
18227
  return { findings, scanned, slices, scanMs };
17919
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
+ }
17920
18240
  async function syncToTarget(cred, opts = {}) {
17921
18241
  const sync = loadSettings().sync;
17922
18242
  const serverCfg = await fetchTenantSyncConfig(cred);
@@ -17937,14 +18257,9 @@ async function syncToTarget(cred, opts = {}) {
17937
18257
  return isProjectSyncable(r.source === "ignored" ? "" : r.id, projectPath, { syncMode: "only", syncOnly });
17938
18258
  };
17939
18259
  const MIN_SERVER_API = 2;
17940
- try {
17941
- const caps = await fetchWithTimeout(`${cred.serverUrl.replace(/\/$/, "")}/api/capabilities`).then((r) => r.json());
17942
- if ((caps.apiVersion ?? 0) < MIN_SERVER_API) {
17943
- throw new Error(`server too old: apiVersion ${caps.apiVersion ?? "none"} < required ${MIN_SERVER_API} \u2014 update the server image before syncing`);
17944
- }
17945
- } catch (e) {
17946
- if (e instanceof Error && e.message.startsWith("server too old")) throw e;
17947
- 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`);
17948
18263
  }
17949
18264
  const { tenantRules, verifySecrets } = await fetchTenantSecurityConfig(cred);
17950
18265
  const CHANGED_WALK_OVERLAP_MS = 15 * 6e4;
@@ -17996,6 +18311,20 @@ async function syncToTarget(cred, opts = {}) {
17996
18311
  if (res.status === 401 || res.status === 403) {
17997
18312
  throw new Error(`server rejected this machine's device token (HTTP ${res.status} \u2014 revoked?). Reconnect with: chat-recall login ${base2}`);
17998
18313
  }
18314
+ if (res.status === 402) {
18315
+ let detail = text;
18316
+ try {
18317
+ const body2 = JSON.parse(text);
18318
+ detail = body2.error ?? text;
18319
+ if (body2.detail) detail += `
18320
+ ${body2.detail}`;
18321
+ if (body2.resetsAt) detail += ` (resets ${new Date(body2.resetsAt).toISOString().slice(0, 10)})`;
18322
+ if (body2.upgradeUrl) detail += `
18323
+ ${body2.upgradeUrl}`;
18324
+ } catch {
18325
+ }
18326
+ throw new Error(`sync paused by the server: ${detail}`);
18327
+ }
17999
18328
  if (!retryable) throw new Error(`sync failed: HTTP ${res.status} ${text}`);
18000
18329
  if (attempt >= RETRY_DELAYS_MS.length) throw new Error(`sync failed after ${attempt + 1} attempts: HTTP ${res.status} ${text}`);
18001
18330
  const retryAfterS = Number(res.headers.get("retry-after"));
@@ -18138,16 +18467,21 @@ async function syncToTarget(cred, opts = {}) {
18138
18467
  const findingsBatch = makeBatcher("findings", 200, true);
18139
18468
  const scanSecrets = isSecretScannerAvailable() && upload.findings && process.env.CHAT_RECALL_SCAN_SECRETS !== "0";
18140
18469
  const verifySecretsScan = verifySecrets;
18470
+ let skipped = 0, redactions = 0, walked = 0;
18141
18471
  const kgSeen = /* @__PURE__ */ new Set();
18142
18472
  const kgEntityType = /* @__PURE__ */ new Map();
18143
- const kgRows = [];
18144
- 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) => {
18145
18476
  for (const t of triples) {
18146
- const key = `${t.subject}|${t.predicate}|${t.object}|${t.validFrom ?? ""}`;
18477
+ const key = kgKey(t.subject, t.predicate, t.object, t.validFrom ?? "");
18147
18478
  if (kgSeen.has(key)) continue;
18148
18479
  kgSeen.add(key);
18149
18480
  if (t.predicate === "is_a" && !kgEntityType.has(t.subject)) kgEntityType.set(t.subject, t.object);
18150
- kgRows.push({
18481
+ kgEntityNames.add(t.subject);
18482
+ kgEntityNames.add(t.object);
18483
+ const count = { redactions: 0 };
18484
+ await kgTripleBatch.add(redactDeep({
18151
18485
  subject: t.subject,
18152
18486
  predicate: t.predicate,
18153
18487
  object: t.object,
@@ -18155,10 +18489,10 @@ async function syncToTarget(cred, opts = {}) {
18155
18489
  valid_to: null,
18156
18490
  confidence: t.confidence ?? 1,
18157
18491
  source_session: sourceSession
18158
- });
18492
+ }, count));
18493
+ redactions += count.redactions;
18159
18494
  }
18160
18495
  };
18161
- let skipped = 0, redactions = 0, walked = 0;
18162
18496
  let scanMs = 0, scanned = 0;
18163
18497
  const slice = refs.slice(0, opts.limit ?? refs.length);
18164
18498
  const trace = process.env.CHAT_RECALL_SYNC_TRACE === "1" ? (msg) => {
@@ -18291,7 +18625,7 @@ async function syncToTarget(cred, opts = {}) {
18291
18625
  await convBatch.add(built.conv);
18292
18626
  if (built.findings.length > 0) await findingsBatch.add(built.findings);
18293
18627
  if (upload.sessionMeta) {
18294
- 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);
18295
18629
  }
18296
18630
  if (upload.sessionMeta && sessionFileBytes(ref) <= FULL_BUILD_MAX_BYTES) {
18297
18631
  try {
@@ -18370,7 +18704,7 @@ async function syncToTarget(cred, opts = {}) {
18370
18704
  chunks: redactedChunks
18371
18705
  });
18372
18706
  if (upload.sessionMeta && redactedChunks.length > 0) {
18373
- accumulateKg(
18707
+ await accumulateKg(
18374
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 }),
18375
18709
  null
18376
18710
  );
@@ -18395,16 +18729,8 @@ async function syncToTarget(cred, opts = {}) {
18395
18729
  }
18396
18730
  }
18397
18731
  }
18398
- if (upload.sessionMeta && kgRows.length > 0) {
18399
- const entityNames = /* @__PURE__ */ new Set();
18400
- for (const t of kgRows) {
18401
- const count = { redactions: 0 };
18402
- await kgTripleBatch.add(redactDeep(t, count));
18403
- redactions += count.redactions;
18404
- entityNames.add(t.subject);
18405
- entityNames.add(t.object);
18406
- }
18407
- for (const name of entityNames) {
18732
+ if (upload.sessionMeta && kgEntityNames.size > 0) {
18733
+ for (const name of kgEntityNames) {
18408
18734
  const count = { redactions: 0 };
18409
18735
  await kgEntityBatch.add(redactDeep({ name, type: kgEntityType.get(name) ?? "unknown", properties: {} }, count));
18410
18736
  redactions += count.redactions;
@@ -18764,56 +19090,58 @@ function collectDerived(ref, mtime, count) {
18764
19090
  const id = ref.prefixedId;
18765
19091
  const compute = [];
18766
19092
  let fullOutcome = null;
18767
- for (const kind of COMPUTE_KINDS) {
18768
- let data = null;
18769
- try {
18770
- if (kind === "diff") {
18771
- const replay = replaySessionAny(id);
18772
- if (replay.found) data = replay;
18773
- } else if (kind === "outcome") {
18774
- fullOutcome = computeOutcome(id);
18775
- if (fullOutcome.found) data = fullOutcome;
18776
- } else if (kind === "commits") {
18777
- if (!fullOutcome) fullOutcome = computeOutcome(id);
18778
- if (fullOutcome.found) data = fullOutcome.commits;
18779
- } else if (kind === "markers") {
18780
- const turns = extractTurnsAny(id, { maxTurns: 5e4 });
18781
- 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) }));
18782
- data = { sessionId: id, prompts, summary: summarizeMarkers(prompts) };
18783
- }
18784
- } catch {
18785
- 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
+ };
18786
19137
  }
18787
- if (data === null) continue;
18788
- const redacted = redactDeep(data, count);
18789
- if (JSON.stringify(redacted).length > MAX_DERIVED_ROW_BYTES) continue;
18790
- compute.push({ kind, mtime, data: redacted });
18791
- }
18792
- let outcomeRow = null;
18793
- if (fullOutcome?.found) {
18794
- outcomeRow = {
18795
- sessionId: id,
18796
- tool: ref.toolId,
18797
- status: fullOutcome.status,
18798
- reason: fullOutcome.reason,
18799
- fileMtime: mtime,
18800
- fileSize: 0,
18801
- contentHash: "",
18802
- fileCount: fullOutcome.fileCount,
18803
- linesAdded: fullOutcome.totalLinesAdded,
18804
- linesRemoved: fullOutcome.totalLinesRemoved,
18805
- commits: fullOutcome.commits?.totalCommits ?? 0,
18806
- isFull: true,
18807
- classifiedAt: Date.now(),
18808
- lastScannedOffset: 0
19138
+ return {
19139
+ session_id: id,
19140
+ mtime,
19141
+ compute,
19142
+ outcome_row: outcomeRow ? redactDeep({ ...outcomeRow, sessionId: void 0 }, count) : null
18809
19143
  };
18810
- }
18811
- return {
18812
- session_id: id,
18813
- mtime,
18814
- compute,
18815
- outcome_row: outcomeRow ? redactDeep({ ...outcomeRow, sessionId: void 0 }, count) : null
18816
- };
19144
+ });
18817
19145
  }
18818
19146
  function isSyncSkip(r) {
18819
19147
  return typeof r.skipped === "string";
@@ -18842,10 +19170,11 @@ async function syncIncremental(opts = {}) {
18842
19170
  lock.release();
18843
19171
  }
18844
19172
  }
18845
- 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;
18846
19174
  var init_sync_client = __esm({
18847
19175
  "src/sync-client.ts"() {
18848
19176
  "use strict";
19177
+ init_credentials_env();
18849
19178
  init_secret_redactor();
18850
19179
  init_source_discovery();
18851
19180
  init_home_discovery();
@@ -18873,14 +19202,15 @@ var init_sync_client = __esm({
18873
19202
  init_sync_fields();
18874
19203
  init_index_lock();
18875
19204
  init_http();
19205
+ init_live_session_scan();
18876
19206
  init_backends();
18877
19207
  ownVersion = "0.0.0";
18878
19208
  try {
18879
19209
  const dir = dirname16(fileURLToPath(import.meta.url));
18880
- 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;
18881
19211
  } catch {
18882
19212
  try {
18883
- if (true) ownVersion = "0.5.4";
19213
+ if (true) ownVersion = "0.5.6";
18884
19214
  } catch {
18885
19215
  }
18886
19216
  }
@@ -18891,6 +19221,8 @@ var init_sync_client = __esm({
18891
19221
  hashPath = (p) => p ? "p_" + createHash12("sha256").update(p).digest("hex").slice(0, 12) : "";
18892
19222
  COMPUTE_KINDS = ["diff", "outcome", "commits", "markers"];
18893
19223
  MAX_DERIVED_ROW_BYTES = 2 * 1024 * 1024;
19224
+ CAPS_TTL_MS = 60 * 6e4;
19225
+ capsOk = /* @__PURE__ */ new Map();
18894
19226
  FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "64", 10)) * 1024 * 1024;
18895
19227
  FULL_BUILD_TAIL_BYTES = 16 * 1024 * 1024;
18896
19228
  BATCH_SCAN_MAX_BYTES = Math.max(1, parseInt(process.env.CHAT_RECALL_BATCHSCAN_MAX_MB || "64", 10)) * 1024 * 1024;
@@ -18947,11 +19279,11 @@ async function repairSession(id, opts = {}) {
18947
19279
  const log6 = (m) => {
18948
19280
  if (opts.verbose) console.error(` [repair ${id}] ${m}`);
18949
19281
  };
18950
- const candidates = [];
19282
+ const candidates2 = [];
18951
19283
  const localShadow = readShadowContainer(tool, rawId);
18952
19284
  if (localShadow) {
18953
19285
  try {
18954
- 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" });
18955
19287
  } catch {
18956
19288
  }
18957
19289
  }
@@ -18963,15 +19295,15 @@ async function repairSession(id, opts = {}) {
18963
19295
  if (arch?.gzB64) {
18964
19296
  try {
18965
19297
  const container = gunzipContainer(Buffer.from(arch.gzB64, "base64"));
18966
- 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 });
18967
19299
  } catch {
18968
19300
  }
18969
19301
  }
18970
19302
  }
18971
- if (candidates.length === 0) {
19303
+ if (candidates2.length === 0) {
18972
19304
  return { sessionId: id, status: "no-archive", fullestMessages: 0, fullestSource: "", pushed: [], note: "no shadow or server archive found" };
18973
19305
  }
18974
- 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);
18975
19307
  log6(`fullest archive = ${fullest.messages} msg(s) from ${fullest.source}`);
18976
19308
  if (opts.dryRun) {
18977
19309
  const needy2 = endpoints.filter((ep) => (currentOnServer.get(ep.url) ?? 0) < fullest.messages || opts.force);
@@ -19073,7 +19405,7 @@ async function repairAll(opts = {}) {
19073
19405
  if (!page?.hasMore || sessions.length === 0) break;
19074
19406
  }
19075
19407
  log6(`window=${sinceHours}h \u2192 ${ids.length} session(s) to check on ${primary.url}`);
19076
- const candidates = [];
19408
+ const candidates2 = [];
19077
19409
  let scanned = 0;
19078
19410
  for (const id of ids) {
19079
19411
  scanned++;
@@ -19086,9 +19418,9 @@ async function repairAll(opts = {}) {
19086
19418
  if (archiveMsgs <= current) continue;
19087
19419
  const r = await repairSession(id, { dryRun: !opts.apply, verbose: opts.verbose, server: opts.server });
19088
19420
  if (r.status === "already-full") continue;
19089
- candidates.push(r);
19421
+ candidates2.push(r);
19090
19422
  }
19091
- return { scanned, candidates, discoveryServer: primary.url };
19423
+ return { scanned, candidates: candidates2, discoveryServer: primary.url };
19092
19424
  }
19093
19425
  var hashPath2;
19094
19426
  var init_repair = __esm({
@@ -19104,23 +19436,93 @@ var init_repair = __esm({
19104
19436
  }
19105
19437
  });
19106
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
+
19107
19498
  // ../engine/dist/core/companions.js
19108
- 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";
19109
19500
  import { homedir as homedir19 } from "node:os";
19110
- import { join as join45 } from "node:path";
19501
+ import { join as join47 } from "node:path";
19111
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
+ }
19112
19507
  function detectPlatform() {
19113
19508
  const arch = process.arch;
19114
19509
  const platform = process.platform;
19115
19510
  const archMap = { x64: "x86_64", arm64: "aarch64" };
19116
- const platformMap = { linux: "linux", darwin: "darwin", win32: "windows" };
19511
+ const platformMap = { linux: "linux", darwin: "macos", win32: "windows" };
19117
19512
  const a = archMap[arch];
19118
19513
  const p = platformMap[platform];
19119
19514
  if (!a || !p) {
19120
19515
  return { reason: `Unsupported platform: ${arch}-${platform}` };
19121
19516
  }
19122
- const prebuiltCombos = /* @__PURE__ */ new Set(["x86_64-linux"]);
19123
- 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" : ""}`;
19124
19526
  if (!prebuiltCombos.has(`${a}-${p}`)) {
19125
19527
  return {
19126
19528
  artifact,
@@ -19137,20 +19539,15 @@ function checkCodeindexStatus() {
19137
19539
  artifactName: platform.artifact,
19138
19540
  unsupportedReason: platform.reason
19139
19541
  };
19140
- let foundPath = null;
19141
- try {
19142
- const out = execSync3("command -v codeindex", { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
19143
- if (out)
19144
- foundPath = out;
19145
- } catch {
19542
+ let foundPath = resolveOnPath(CODEINDEX_BIN_NAME);
19543
+ if (!foundPath) {
19544
+ foundPath = resolveOnPath(CODEINDEX_BIN_PATH);
19146
19545
  }
19147
- if (!foundPath && existsSync43(CODEINDEX_BIN_PATH))
19148
- foundPath = CODEINDEX_BIN_PATH;
19149
19546
  if (foundPath) {
19150
19547
  status.installed = true;
19151
19548
  status.path = foundPath;
19152
19549
  try {
19153
- status.size = statSync38(foundPath).size;
19550
+ status.size = statSync39(foundPath).size;
19154
19551
  } catch {
19155
19552
  }
19156
19553
  try {
@@ -19162,16 +19559,9 @@ function checkCodeindexStatus() {
19162
19559
  return status;
19163
19560
  }
19164
19561
  async function downloadArtifact(artifact, dest) {
19165
- const hasGh = (() => {
19166
- try {
19167
- execSync3("command -v gh", { stdio: "ignore" });
19168
- return true;
19169
- } catch {
19170
- return false;
19171
- }
19172
- })();
19562
+ const hasGh = isOnPath("gh");
19173
19563
  if (hasGh) {
19174
- 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}"`, {
19175
19565
  stdio: ["ignore", "pipe", "pipe"],
19176
19566
  timeout: 6e4
19177
19567
  });
@@ -19184,7 +19574,7 @@ async function downloadArtifact(artifact, dest) {
19184
19574
  if (!res.body)
19185
19575
  throw new Error("Download returned empty body");
19186
19576
  const file = createWriteStream(dest);
19187
- await new Promise((resolve2, reject) => {
19577
+ await new Promise((resolve3, reject) => {
19188
19578
  const reader = res.body.getReader();
19189
19579
  const pump = async () => {
19190
19580
  try {
@@ -19195,7 +19585,7 @@ async function downloadArtifact(artifact, dest) {
19195
19585
  file.write(value);
19196
19586
  }
19197
19587
  file.end();
19198
- file.on("finish", () => resolve2());
19588
+ file.on("finish", () => resolve3());
19199
19589
  file.on("error", reject);
19200
19590
  } catch (e) {
19201
19591
  reject(e);
@@ -19210,15 +19600,26 @@ async function installCodeindex(opts = {}) {
19210
19600
  return status;
19211
19601
  if (!status.prebuiltAvailable)
19212
19602
  return status;
19213
- mkdirSync12(CODEINDEX_INSTALL_DIR, { recursive: true });
19603
+ mkdirSync13(CODEINDEX_INSTALL_DIR, { recursive: true });
19214
19604
  const tmpPath = `${CODEINDEX_BIN_PATH}.partial`;
19215
19605
  try {
19216
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
+ }
19217
19618
  chmodSync4(tmpPath, 493);
19218
19619
  renameSync5(tmpPath, CODEINDEX_BIN_PATH);
19219
19620
  } catch (err) {
19220
19621
  try {
19221
- if (existsSync43(tmpPath))
19622
+ if (existsSync44(tmpPath))
19222
19623
  unlinkSync3(tmpPath);
19223
19624
  } catch {
19224
19625
  }
@@ -19226,15 +19627,24 @@ async function installCodeindex(opts = {}) {
19226
19627
  }
19227
19628
  return checkCodeindexStatus();
19228
19629
  }
19229
- 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;
19230
19631
  var init_companions = __esm({
19231
19632
  "../engine/dist/core/companions.js"() {
19232
19633
  "use strict";
19634
+ init_which();
19233
19635
  CODEINDEX_REPO = "munhq/codeindex";
19234
- CODEINDEX_VERSION = "v0.1.0";
19235
- CODEINDEX_INSTALL_DIR = join45(homedir19(), ".local", "bin");
19236
- CODEINDEX_BIN_NAME = "codeindex";
19237
- 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);
19238
19648
  }
19239
19649
  });
19240
19650
 
@@ -19340,7 +19750,9 @@ var init_prompts = __esm({
19340
19750
  var collector_exports = {};
19341
19751
  __export(collector_exports, {
19342
19752
  COLLECTOR_VERSION: () => COLLECTOR_VERSION,
19753
+ GENERATED_PATTERNS: () => GENERATED_PATTERNS,
19343
19754
  collectCode: () => collectCode,
19755
+ isGenerated: () => isGenerated,
19344
19756
  isJunkWorkspacePath: () => isJunkWorkspacePath,
19345
19757
  isSecretRule: () => isSecretRule,
19346
19758
  resolveCodeindexBin: () => resolveCodeindexBin
@@ -19348,7 +19760,7 @@ __export(collector_exports, {
19348
19760
  import { spawn as spawn2 } from "node:child_process";
19349
19761
  import { execSync as execSync4 } from "node:child_process";
19350
19762
  import { readFileSync as readFileSync39 } from "node:fs";
19351
- import { join as join46, basename as basename24 } from "node:path";
19763
+ import { join as join48, basename as basename24 } from "node:path";
19352
19764
  async function resolveCodeindexBin(autoInstall = true) {
19353
19765
  let st = checkCodeindexStatus();
19354
19766
  if (!st.installed && autoInstall)
@@ -19359,7 +19771,7 @@ async function resolveCodeindexBin(autoInstall = true) {
19359
19771
  return st.path;
19360
19772
  }
19361
19773
  function runMcp(binPath, workspace, calls, timeoutMs = 18e4) {
19362
- return new Promise((resolve2, reject) => {
19774
+ return new Promise((resolve3, reject) => {
19363
19775
  const p = spawn2(binPath, ["--mcp", "--workspace", workspace], { stdio: ["pipe", "pipe", "ignore"] });
19364
19776
  let out = "";
19365
19777
  const timer = setTimeout(() => {
@@ -19402,7 +19814,7 @@ function runMcp(binPath, workspace, calls, timeoutMs = 18e4) {
19402
19814
  if (m?.id != null && typeof text === "string")
19403
19815
  res.set(Number(m.id), text);
19404
19816
  }
19405
- resolve2(res);
19817
+ resolve3(res);
19406
19818
  });
19407
19819
  p.stdin.write(calls.map((c) => JSON.stringify(c) + "\n").join(""));
19408
19820
  p.stdin.end();
@@ -19435,10 +19847,11 @@ function isSecretRule(rule) {
19435
19847
  return /secret|password|credential|key|token|auth/i.test(rule);
19436
19848
  }
19437
19849
  function isJunkWorkspacePath(ws) {
19438
- 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);
19439
19852
  }
19440
19853
  async function collectCode(opts) {
19441
- const ws = opts.workspace.replace(/\/+$/, "");
19854
+ const ws = opts.workspace.replace(/[/\\]+$/, "");
19442
19855
  const log6 = opts.log ?? (() => {
19443
19856
  });
19444
19857
  if (isJunkWorkspacePath(ws)) {
@@ -19446,9 +19859,11 @@ async function collectCode(opts) {
19446
19859
  }
19447
19860
  const bin = opts.binPath ?? await resolveCodeindexBin(opts.autoInstall ?? true);
19448
19861
  const projectId = resolveProjectId(ws).id || `path:${ws}`;
19449
- const wsPrefix = ws + "/";
19862
+ const wsNorm = ws.replace(/\\/g, "/");
19863
+ const wsPrefix = wsNorm + "/";
19450
19864
  const rel = (p) => {
19451
- 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;
19452
19867
  if (r.startsWith("./"))
19453
19868
  r = r.slice(2);
19454
19869
  return r;
@@ -19509,7 +19924,7 @@ async function collectCode(opts) {
19509
19924
  }
19510
19925
  const complexity = (r) => {
19511
19926
  try {
19512
- const txt = readFileSync39(join46(ws, r), "utf8");
19927
+ const txt = readFileSync39(join48(ws, r), "utf8");
19513
19928
  if (txt.length > 2e6)
19514
19929
  return 1;
19515
19930
  const m = txt.match(CF_RE);
@@ -19863,6 +20278,14 @@ async function collectCode(opts) {
19863
20278
  agentPrompt: manifestPrompt(v.violation_type ?? "", file, v.line ?? null, v.description ?? "")
19864
20279
  });
19865
20280
  }
20281
+ {
20282
+ const before = findings.length;
20283
+ const own = findings.filter((f) => !f.file || !isGenerated(f.file));
20284
+ if (own.length !== before) {
20285
+ findings.splice(0, before, ...own);
20286
+ log6(`dropped ${before - own.length} finding(s) in vendored/generated files`);
20287
+ }
20288
+ }
19866
20289
  const godSet = new Set((coup?.god_modules ?? []).map((m) => rel(m.file)));
19867
20290
  const cloneSet = /* @__PURE__ */ new Set();
19868
20291
  for (const g of clones?.groups ?? [])
@@ -19948,7 +20371,9 @@ async function collectCode(opts) {
19948
20371
  }
19949
20372
  for (const g of clones?.groups ?? []) {
19950
20373
  if ((g.count ?? 0) >= 3 || (g.lines ?? 0) >= 12) {
19951
- const fns = (g.functions ?? []).filter((x) => x && typeof x === "object");
20374
+ const fns = (g.functions ?? []).filter((x) => x && typeof x === "object").filter((x) => !isGenerated(rel(x.file)));
20375
+ if (fns.length < 2)
20376
+ continue;
19952
20377
  const names = [...new Set(fns.map((x) => x.name))];
19953
20378
  const locs = fns.map((x) => rel(x.file)).slice(0, 5);
19954
20379
  pushAction((g.lines ?? 0) * (g.count ?? 0) >= 150 ? 1 : 2, "duplication", `${names.join(", ")} copy-pasted ${g.count}\xD7 (${g.lines} lines each)`, "Extract one shared implementation and replace the copies.", locs.map((f) => ({ file: f })), clonePrompt(names, g.lines ?? 0, g.count ?? 0, locs));
@@ -19967,10 +20392,14 @@ async function collectCode(opts) {
19967
20392
  }
19968
20393
  for (const m of (coup?.god_modules ?? []).slice(0, 5)) {
19969
20394
  const file = rel(m.file);
20395
+ if (isGenerated(file))
20396
+ continue;
19970
20397
  pushAction(2, "modularity", `God module ${file} (fan-in ${m.fan_in}, fan-out ${m.fan_out})`, "Split into cohesive sub-modules; introduce interfaces to cut fan-out.", [{ file }], couplingPrompt(file, m.fan_in ?? 0, m.fan_out ?? 0, Math.round((m.instability ?? 0) * 100) / 100));
19971
20398
  }
19972
20399
  for (const c of (cyc?.cycles ?? []).slice(0, 5)) {
19973
- const chain = (c.files ?? []).map(rel);
20400
+ const chain = (c.files ?? []).map(rel).filter((f) => !isGenerated(f));
20401
+ if (chain.length < 2)
20402
+ continue;
19974
20403
  pushAction(2, "architecture", `Circular dependency (${chain.length} files)`, "Break the cycle: extract the shared type/interface into a separate module.", chain.slice(0, 4).map((f) => ({ file: f })), cyclePrompt(chain.slice(0, 6)));
19975
20404
  }
19976
20405
  for (const h of hotspotsAll.slice(0, 5)) {
@@ -20079,7 +20508,7 @@ async function collectCode(opts) {
20079
20508
  void health;
20080
20509
  return { project, findings, hotspots, actions };
20081
20510
  }
20082
- 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;
20083
20512
  var init_collector = __esm({
20084
20513
  "../engine/dist/core/code/collector.js"() {
20085
20514
  "use strict";
@@ -20109,11 +20538,42 @@ var init_collector = __esm({
20109
20538
  "migration_parity",
20110
20539
  "manifest_compliance"
20111
20540
  ];
20112
- GENERATED = ["generated/", ".pb.go", ".gen.", "_pb2.py", "/gen/", ".g.dart", "/node_modules/", "/vendor/", ".lock"];
20541
+ GENERATED = [
20542
+ "generated/",
20543
+ "__generated__/",
20544
+ ".pb.go",
20545
+ ".gen.",
20546
+ "_pb2.py",
20547
+ "/gen/",
20548
+ ".g.dart",
20549
+ "/node_modules/",
20550
+ "/vendor/",
20551
+ "/third_party/",
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.
20557
+ ".lock",
20558
+ "-lock.json",
20559
+ "-lock.yaml",
20560
+ "/dist/",
20561
+ "/build/",
20562
+ "/out/",
20563
+ "/target/",
20564
+ "/.next/",
20565
+ "/coverage/",
20566
+ ".min.js",
20567
+ ".min.css",
20568
+ ".bundle.js",
20569
+ "-bundle.js",
20570
+ ".map"
20571
+ ];
20113
20572
  CF_RE = /\b(if|for|while|switch|case|catch|elif|when|loop|&&|\|\||\?)\b|\?/g;
20114
20573
  DEPTH = 2;
20115
20574
  MCP_OUT_MAX_BYTES = 64 * 1024 * 1024;
20116
20575
  call = (id, name, args = {}) => ({ jsonrpc: "2.0", id, method: "tools/call", params: { name, arguments: args } });
20576
+ GENERATED_PATTERNS = GENERATED;
20117
20577
  COMMON_SYMBOL_NAMES = /* @__PURE__ */ new Set([
20118
20578
  "error",
20119
20579
  "err",
@@ -20185,12 +20645,13 @@ init_settings();
20185
20645
  init_sync_client();
20186
20646
  init_http();
20187
20647
  import chokidar from "chokidar";
20188
- 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";
20189
20650
 
20190
20651
  // src/intent-drain.ts
20191
20652
  init_http();
20192
20653
  init_sync_client();
20193
- 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";
20194
20655
  import { join as join44 } from "node:path";
20195
20656
  import { homedir as homedir18 } from "node:os";
20196
20657
 
@@ -20200,7 +20661,7 @@ init_gemini2();
20200
20661
  init_opencode();
20201
20662
  init_codex2();
20202
20663
  init_agy();
20203
- 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";
20204
20665
  import { dirname as dirname17, join as join42, basename as basename23 } from "path";
20205
20666
  import { homedir as homedir16 } from "os";
20206
20667
 
@@ -20211,11 +20672,11 @@ init_opencode();
20211
20672
  init_codex2();
20212
20673
  init_agy();
20213
20674
  init_toolkit_format();
20214
- import { readFileSync as readFileSync34 } from "fs";
20675
+ import { readFileSync as readFileSync33 } from "fs";
20215
20676
  import { join as join41 } from "path";
20216
20677
  import { homedir as homedir15 } from "os";
20217
20678
  function readCommand(filePath, format) {
20218
- const content = readFileSync34(filePath, "utf-8");
20679
+ const content = readFileSync33(filePath, "utf-8");
20219
20680
  if (format === "toml") {
20220
20681
  const t = parseScalarToml(content);
20221
20682
  return { name: t.name || base(filePath), description: t.description || "", body: t.prompt || "" };
@@ -20224,7 +20685,7 @@ function readCommand(filePath, format) {
20224
20685
  return { name: fm.name || base(filePath), description: fm.description || "", body: body.trim() };
20225
20686
  }
20226
20687
  function readAgent(filePath, format) {
20227
- const content = readFileSync34(filePath, "utf-8");
20688
+ const content = readFileSync33(filePath, "utf-8");
20228
20689
  if (format === "toml") {
20229
20690
  const t = parseScalarToml(content);
20230
20691
  return {
@@ -20237,7 +20698,7 @@ function readAgent(filePath, format) {
20237
20698
  return { name: fm.name || base(filePath), description: fm.description || "", tools: fm.tools, body: body.trim() };
20238
20699
  }
20239
20700
  function readInstructions(filePath, name) {
20240
- const content = readFileSync34(filePath, "utf-8");
20701
+ const content = readFileSync33(filePath, "utf-8");
20241
20702
  return { name, description: "", body: content };
20242
20703
  }
20243
20704
  function commandPath(tool, name) {
@@ -20424,7 +20885,7 @@ function readMcpEntry(tool, name) {
20424
20885
  if (!existsSync39(t.path))
20425
20886
  continue;
20426
20887
  try {
20427
- const cfg = JSON.parse(readFileSync35(t.path, "utf-8"));
20888
+ const cfg = JSON.parse(readFileSync34(t.path, "utf-8"));
20428
20889
  const block = cfg[t.key] || {};
20429
20890
  if (block[name])
20430
20891
  return block[name];
@@ -20438,7 +20899,7 @@ function readCodexMcpEntry(path, target) {
20438
20899
  return null;
20439
20900
  let content;
20440
20901
  try {
20441
- content = readFileSync35(path, "utf-8");
20902
+ content = readFileSync34(path, "utf-8");
20442
20903
  } catch {
20443
20904
  return null;
20444
20905
  }
@@ -20489,7 +20950,7 @@ function readCodexMcpEntry(path, target) {
20489
20950
  function writeCodexMcpEntry(path, name, entry) {
20490
20951
  let content = "";
20491
20952
  if (existsSync39(path))
20492
- content = readFileSync35(path, "utf-8");
20953
+ content = readFileSync34(path, "utf-8");
20493
20954
  const header = `[mcp_servers.${name}]`;
20494
20955
  if (content.includes(header))
20495
20956
  return false;
@@ -20548,7 +21009,7 @@ function writeMcpEntry(toTool, name, entry) {
20548
21009
  let cfg = {};
20549
21010
  if (existsSync39(path)) {
20550
21011
  try {
20551
- cfg = JSON.parse(readFileSync35(path, "utf-8"));
21012
+ cfg = JSON.parse(readFileSync34(path, "utf-8"));
20552
21013
  } catch {
20553
21014
  return { ok: false, status: 500, error: `Target config is malformed: ${path}` };
20554
21015
  }
@@ -20735,7 +21196,7 @@ async function executeCopy(type, name, fromTool, toTool) {
20735
21196
 
20736
21197
  // src/project-tasks.ts
20737
21198
  init_http();
20738
- 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";
20739
21200
  import { join as join43, dirname as dirname18 } from "node:path";
20740
21201
  import { homedir as homedir17 } from "node:os";
20741
21202
 
@@ -20752,7 +21213,7 @@ function isCodeTaskStatus(v) {
20752
21213
  }
20753
21214
 
20754
21215
  // src/project-tasks.ts
20755
- var TEAM_STATUSES = /* @__PURE__ */ new Set(["todo", "in_progress", "blocked", "done"]);
21216
+ var TEAM_STATUSES = /* @__PURE__ */ new Set(["todo", "in_progress", "done", "rejected"]);
20756
21217
  function isTeamTaskStatus(v) {
20757
21218
  return !!v && TEAM_STATUSES.has(v);
20758
21219
  }
@@ -20770,7 +21231,7 @@ function reconcile(kind, was, token, checked) {
20770
21231
  const done = kind === "secret" ? "rotated" : "done";
20771
21232
  if (token && token !== was) return token;
20772
21233
  if (kind === "team") {
20773
- if (checked && was !== "done") return "done";
21234
+ if (checked && was === "todo") return "in_progress";
20774
21235
  if (!checked && was === "done") return "todo";
20775
21236
  return token ?? was;
20776
21237
  }
@@ -20809,7 +21270,7 @@ function ledgerPath2() {
20809
21270
  }
20810
21271
  function readLedger() {
20811
21272
  try {
20812
- return JSON.parse(readFileSync36(ledgerPath2(), "utf8"));
21273
+ return JSON.parse(readFileSync35(ledgerPath2(), "utf8"));
20813
21274
  } catch {
20814
21275
  return {};
20815
21276
  }
@@ -20853,7 +21314,7 @@ async function pushProjectTaskStatuses(base2, authHeaders, opts = {}) {
20853
21314
  if (ledger[file] === mtime) continue;
20854
21315
  let changed;
20855
21316
  try {
20856
- changed = parseTaskFile(readFileSync36(file, "utf8")).filter((t) => t.kind === fk.kind);
21317
+ changed = parseTaskFile(readFileSync35(file, "utf8")).filter((t) => t.kind === fk.kind);
20857
21318
  } catch {
20858
21319
  continue;
20859
21320
  }
@@ -20910,7 +21371,7 @@ function applyCodeRecommendation(intent) {
20910
21371
  if (!meta.rootPath) return { status: "error", result: JSON.stringify({ error: "missing rootPath" }) };
20911
21372
  file = join44(meta.rootPath, "CLAUDE.md");
20912
21373
  }
20913
- const existing = existsSync41(file) ? readFileSync37(file, "utf8") : "";
21374
+ const existing = existsSync41(file) ? readFileSync36(file, "utf8") : "";
20914
21375
  if (existing.includes(text)) return { status: "done", result: JSON.stringify({ skipped: "rule already present", file }) };
20915
21376
  const block = `${existing && !existing.endsWith("\n") ? "\n" : ""}
20916
21377
  ## Rule (added by chat-recall recommendation)
@@ -20956,10 +21417,10 @@ async function runIntent(intent, ctx) {
20956
21417
  var ownVersion2 = "0.0.0";
20957
21418
  try {
20958
21419
  const dir = dirname19(fileURLToPath2(import.meta.url));
20959
- 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;
20960
21421
  } catch {
20961
21422
  try {
20962
- if (true) ownVersion2 = "0.5.4";
21423
+ if (true) ownVersion2 = "0.5.6";
20963
21424
  } catch {
20964
21425
  }
20965
21426
  }
@@ -21133,8 +21594,37 @@ async function runCollectorMigration(opts) {
21133
21594
  }
21134
21595
 
21135
21596
  // auto-indexer/indexer.ts
21136
- import { existsSync as existsSync44, readFileSync as readFileSync40 } from "fs";
21137
- var CLI_VERSION3 = true ? "0.5.4" : "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";
21138
21628
  var DEBOUNCE_MS = 5e3;
21139
21629
  function ts() {
21140
21630
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -21147,9 +21637,9 @@ var DIARY_DIR = getDiaryDir();
21147
21637
  var CODEX_SESSIONS_DIR = codexBackend.sessionsDir();
21148
21638
  var GEMINI_TMP_DIR = geminiBackend.tmpDir();
21149
21639
  var OPENCODE_DB_PATH = opencodeBackend.dbPath();
21150
- var OPENCODE_DB_DIR = dirname20(OPENCODE_DB_PATH);
21151
- var CURRENT_RESUME_PATH = join47(claudeBackend.homeDir(), "current-resume");
21152
- 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");
21153
21643
  var debounceTimer = null;
21154
21644
  var syncInFlight = false;
21155
21645
  var pendingFileCount = 0;
@@ -21171,9 +21661,11 @@ async function shipToServer(trigger) {
21171
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`
21172
21662
  );
21173
21663
  }
21664
+ for (const server of syncTargetUrls()) noteSyncOutcome(server, true);
21174
21665
  } catch (err) {
21175
21666
  const msg = err instanceof Error ? err.message : String(err);
21176
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);
21177
21669
  } finally {
21178
21670
  syncInFlight = false;
21179
21671
  }
@@ -21201,39 +21693,58 @@ async function onResumeSignal(path) {
21201
21693
  lastResumeAt.set(id, now);
21202
21694
  const { snapshotShadow: snapshotShadow2 } = await Promise.resolve().then(() => (init_transcript(), transcript_exports));
21203
21695
  const r = await snapshotShadow2(id);
21696
+ if (r.status === "skipped") return;
21204
21697
  console.log(`[${ts()}] [resume-guard] shadow ${r.status} for ${id.slice(0, 8)}\u2026` + (r.recovered > 0 ? ` (recovered ${r.recovered} record(s))` : ""));
21205
21698
  } catch (e) {
21206
21699
  console.error(`[${ts()}] [resume-guard] ${e instanceof Error ? e.message : e}`);
21207
21700
  }
21208
21701
  }
21209
- var sessionWatcher = chokidar.watch(`${CLAUDE_DIR}/**/*.jsonl`, {
21210
- 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) => ({
21211
21705
  persistent: true,
21212
- ignoreInitial: true,
21213
- usePolling: true,
21214
- interval: 5e3,
21215
- 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
21216
21730
  });
21217
21731
  var plansWatcher = chokidar.watch(`${PLANS_DIR}/*.md`, {
21218
21732
  persistent: true,
21219
21733
  ignoreInitial: true,
21220
21734
  awaitWriteFinish: { stabilityThreshold: 1e3, pollInterval: 100 },
21221
- usePolling: true,
21222
- interval: 1e4
21735
+ ...POLL(1e4)
21223
21736
  });
21224
21737
  var tasksWatcher = chokidar.watch(`${TASKS_DIR}/**/*.json`, {
21225
21738
  persistent: true,
21226
21739
  ignoreInitial: true,
21227
21740
  awaitWriteFinish: { stabilityThreshold: 1e3, pollInterval: 100 },
21228
- usePolling: true,
21229
- interval: 1e4
21741
+ ...POLL(1e4)
21230
21742
  });
21231
21743
  var historyWatcher = chokidar.watch(HISTORY_PATH, {
21232
21744
  persistent: true,
21233
21745
  ignoreInitial: true,
21234
21746
  awaitWriteFinish: { stabilityThreshold: 2e3, pollInterval: 500 },
21235
- usePolling: true,
21236
- interval: 3e4
21747
+ ...POLL(3e4)
21237
21748
  // check less frequently
21238
21749
  });
21239
21750
  var diaryWatcher = chokidar.watch(`${DIARY_DIR}/**/*.json`, {
@@ -21247,15 +21758,13 @@ var codexWatcher = chokidar.watch(`${CODEX_SESSIONS_DIR}/**/rollout-*.jsonl`, {
21247
21758
  // Codex rolls every event into its own JSONL line — wait for ~2s of
21248
21759
  // write quiescence so we don't ship mid-write.
21249
21760
  awaitWriteFinish: { stabilityThreshold: 2e3, pollInterval: 200 },
21250
- usePolling: true,
21251
- interval: 5e3
21761
+ ...POLL(5e3)
21252
21762
  });
21253
21763
  var geminiWatcher = chokidar.watch(`${GEMINI_TMP_DIR}/**/session-*.json`, {
21254
21764
  persistent: true,
21255
21765
  ignoreInitial: true,
21256
21766
  awaitWriteFinish: { stabilityThreshold: 1500, pollInterval: 200 },
21257
- usePolling: true,
21258
- interval: 5e3
21767
+ ...POLL(5e3)
21259
21768
  });
21260
21769
  var opencodeWatcher = chokidar.watch(
21261
21770
  [`${OPENCODE_DB_PATH}`, `${OPENCODE_DB_PATH}-wal`],
@@ -21263,23 +21772,20 @@ var opencodeWatcher = chokidar.watch(
21263
21772
  persistent: true,
21264
21773
  ignoreInitial: true,
21265
21774
  awaitWriteFinish: { stabilityThreshold: 2500, pollInterval: 500 },
21266
- usePolling: true,
21267
- interval: 1e4
21775
+ ...POLL(1e4)
21268
21776
  }
21269
21777
  );
21270
21778
  var agentMemoryWatcher = chokidar.watch(`${CLAUDE_DIR}/*/memory/*.md`, {
21271
21779
  persistent: true,
21272
21780
  ignoreInitial: true,
21273
21781
  awaitWriteFinish: { stabilityThreshold: 1e3, pollInterval: 100 },
21274
- usePolling: true,
21275
- interval: 1e4
21782
+ ...POLL(1e4)
21276
21783
  });
21277
21784
  var cleanupWatcher = chokidar.watch(LAST_CLEANUP_PATH, {
21278
21785
  persistent: true,
21279
21786
  ignoreInitial: true,
21280
21787
  awaitWriteFinish: { stabilityThreshold: 1e3, pollInterval: 200 },
21281
- usePolling: true,
21282
- interval: 3e4
21788
+ ...POLL(3e4)
21283
21789
  });
21284
21790
  var watchers = {
21285
21791
  sessions: sessionWatcher,
@@ -21293,12 +21799,10 @@ var watchers = {
21293
21799
  agentMemory: agentMemoryWatcher,
21294
21800
  cleanup: cleanupWatcher
21295
21801
  };
21296
- var resumeWatcher = chokidar.watch(CURRENT_RESUME_PATH, {
21297
- persistent: true,
21298
- ignoreInitial: false,
21299
- usePolling: true,
21300
- interval: 1e3
21301
- });
21802
+ var resumeWatcher = chokidar.watch(CURRENT_RESUME_PATH, watchOpts({
21803
+ interval: 1e3,
21804
+ ignoreInitial: false
21805
+ }));
21302
21806
  resumeWatcher.on("add", (p) => {
21303
21807
  void onResumeSignal(p);
21304
21808
  }).on("change", (p) => {
@@ -21342,9 +21846,71 @@ var BOOT_CODE = codeFingerprint();
21342
21846
  if (BOOT_CODE) {
21343
21847
  console.log(`[${ts()}] running bundle ${BOOT_CODE.path} (${BOOT_CODE.size} bytes)`);
21344
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
+ }
21345
21910
  setInterval(() => {
21346
21911
  if (checkSelfRestart({ boot: BOOT_CODE, log: (m) => console.log(`[${ts()}] ${m}`) })) return;
21347
21912
  console.log(`[${ts()}] Heartbeat - ${pendingFileCount} pending file event(s), sync ${syncInFlight ? "in-flight" : "idle"}`);
21913
+ publishHealth();
21348
21914
  }, 6e4);
21349
21915
  var intentDrainInFlight = false;
21350
21916
  var intentDrainStartedAt = 0;
@@ -21353,7 +21919,7 @@ var DRAIN_STALL_MS = 5 * 6e4;
21353
21919
  async function drainIntentsTick() {
21354
21920
  if (intentDrainInFlight) {
21355
21921
  const stuckMs = Date.now() - intentDrainStartedAt;
21356
- if (stuckMs < DRAIN_STALL_MS) return;
21922
+ if (stuckMs < DRAIN_STALL_MS) return false;
21357
21923
  console.error(`[${ts()}] sync-intents: previous drain has been running ${Math.round(stuckMs / 1e3)}s \u2014 abandoning it and starting a fresh tick`);
21358
21924
  }
21359
21925
  intentDrainInFlight = true;
@@ -21365,18 +21931,28 @@ async function drainIntentsTick() {
21365
21931
  console.log(`[${ts()}] sync-intents: ${r.done} done, ${r.errored} errored`);
21366
21932
  if (r.done > 0) await shipToServer("intent-applied");
21367
21933
  }
21934
+ return r.processed > 0;
21368
21935
  } catch (err) {
21369
21936
  console.error(`[${ts()}] sync-intents drain failed: ${err instanceof Error ? err.message : err}`);
21937
+ return false;
21370
21938
  } finally {
21371
21939
  if (gen === intentDrainGeneration) intentDrainInFlight = false;
21372
21940
  }
21373
21941
  }
21374
- setInterval(() => {
21375
- void drainIntentsTick();
21376
- }, 15e3).unref();
21377
- setTimeout(() => {
21378
- void drainIntentsTick();
21379
- }, 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);
21380
21956
  var CODE_INDEX_ON = process.env.CHAT_RECALL_CODE_INDEX !== "0";
21381
21957
  var CODE_INDEX_MS = Math.max(15, parseInt(process.env.CHAT_RECALL_CODE_INDEX_MINUTES || "180", 10)) * 60 * 1e3;
21382
21958
  var CODE_INDEX_MAX = Math.max(1, parseInt(process.env.CHAT_RECALL_CODE_INDEX_MAX || "50", 10));
@@ -21412,13 +21988,45 @@ function discoverWorkspaces() {
21412
21988
  } catch {
21413
21989
  }
21414
21990
  const isExcluded = (p) => excluded.some((x) => p.includes(x));
21415
- 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]) => {
21992
+ try {
21993
+ return existsSync45(p);
21994
+ } catch {
21995
+ return false;
21996
+ }
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) => {
21416
22024
  try {
21417
- return existsSync44(p);
22025
+ return existsSync45(join49(p, m));
21418
22026
  } catch {
21419
22027
  return false;
21420
22028
  }
21421
- }).sort((a, b) => b[1] - a[1]).slice(0, CODE_INDEX_MAX).map(([p]) => p);
22029
+ });
21422
22030
  }
21423
22031
  async function codeIndexTick() {
21424
22032
  if (codeIndexInFlight) return;
@@ -21523,6 +22131,11 @@ if (CODE_INDEX_ON) setTimeout(() => {
21523
22131
  }, 15e4);
21524
22132
  function shutdown(signal) {
21525
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
+ }
21526
22139
  for (const watcher of Object.values(watchers)) {
21527
22140
  void watcher.close();
21528
22141
  }
@@ -21531,3 +22144,13 @@ function shutdown(signal) {
21531
22144
  }
21532
22145
  process.on("SIGTERM", () => shutdown("SIGTERM"));
21533
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
+ });