chat-recall 0.5.5 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp.js CHANGED
@@ -2110,7 +2110,7 @@ var require_thread_stream = __commonJS({
2110
2110
  var { version: version2 } = require_package();
2111
2111
  var { EventEmitter } = __require("events");
2112
2112
  var { Worker } = __require("worker_threads");
2113
- var { join: join47 } = __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"] || join47(__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: join47, isAbsolute: isAbsolute2, sep: sep2 } = __require("node:path");
2559
+ var { join: join50, isAbsolute: isAbsolute3, sep: sep3 } = __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"] || join47(__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"] || join47(__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 join47(__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() + sep3 : 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 join47 = ",";
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
- join47 = `,
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 += join47;
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 += `${join47}"... ${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
- join47 = `,
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 = join47;
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 = join47;
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 join47 = ",";
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
- join47 = `,
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 += join47;
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 += `${join47}"... ${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
- join47 = `,
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 = join47;
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 join48 = `,
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 += join48;
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 += `${join48}"... ${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 join47 = `,
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, join47, maximumBreadth);
3903
+ res += stringifyTypedArray(value, join50, maximumBreadth);
3904
3904
  keys = keys.slice(value.length);
3905
3905
  maximumPropertiesToStringify -= value.length;
3906
- separator = join47;
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 = join47;
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 = join47;
3923
+ separator = join50;
3924
3924
  }
3925
3925
  if (separator !== "") {
3926
3926
  res = `
@@ -4848,6 +4848,16 @@ function loadSecrets() {
4848
4848
  const p = secretsPath();
4849
4849
  if (!existsSync2(p))
4850
4850
  return out;
4851
+ let mtimeMs = -1, size = -1;
4852
+ try {
4853
+ const st = statSync2(p);
4854
+ mtimeMs = st.mtimeMs;
4855
+ size = st.size;
4856
+ } catch {
4857
+ }
4858
+ if (_secretsCache && _secretsCache.mtimeMs === mtimeMs && _secretsCache.size === size) {
4859
+ return { ..._secretsCache.value };
4860
+ }
4851
4861
  try {
4852
4862
  for (const line of readFileSync(p, "utf-8").split("\n")) {
4853
4863
  const t = line.trim();
@@ -4865,9 +4875,12 @@ function loadSecrets() {
4865
4875
  }
4866
4876
  } catch {
4867
4877
  }
4878
+ if (mtimeMs >= 0)
4879
+ _secretsCache = { mtimeMs, size, value: { ...out } };
4868
4880
  return out;
4869
4881
  }
4870
4882
  function writeSecrets(secrets) {
4883
+ _secretsCache = null;
4871
4884
  const p = secretsPath();
4872
4885
  mkdirSync2(dirname2(p), { recursive: true });
4873
4886
  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";
@@ -4877,10 +4890,31 @@ function writeSecrets(secrets) {
4877
4890
  } catch {
4878
4891
  }
4879
4892
  }
4893
+ function applySecretOverlay(result) {
4894
+ const secrets = loadSecrets();
4895
+ for (const { block, field, env } of SECRET_FIELDS) {
4896
+ const blk = result[block];
4897
+ const value = process.env[env] || secrets[env] || blk[field];
4898
+ if (value)
4899
+ blk[field] = value;
4900
+ }
4901
+ return result;
4902
+ }
4880
4903
  function loadSettings() {
4881
4904
  const path = settingsPath();
4882
4905
  if (!existsSync2(path))
4883
4906
  return freshDefaults();
4907
+ let mtimeMs = -1, size = -1;
4908
+ try {
4909
+ const st = statSync2(path);
4910
+ mtimeMs = st.mtimeMs;
4911
+ size = st.size;
4912
+ } catch {
4913
+ }
4914
+ if (_settingsCache && _settingsCache.mtimeMs === mtimeMs && _settingsCache.size === size) {
4915
+ const cached = structuredClone(_settingsCache.value);
4916
+ return applySecretOverlay(cached);
4917
+ }
4884
4918
  try {
4885
4919
  const parsed = JSON.parse(readFileSync(path, "utf-8"));
4886
4920
  const base = freshDefaults();
@@ -4893,19 +4927,15 @@ function loadSettings() {
4893
4927
  sync: mergeSync(base.sync, parsed.sync),
4894
4928
  team: mergeTeam(base.team, parsed.team)
4895
4929
  };
4896
- const secrets = loadSecrets();
4897
- for (const { block, field, env } of SECRET_FIELDS) {
4898
- const blk = result[block];
4899
- const value = process.env[env] || secrets[env] || blk[field];
4900
- if (value)
4901
- blk[field] = value;
4902
- }
4903
- return result;
4930
+ if (mtimeMs >= 0)
4931
+ _settingsCache = { mtimeMs, size, value: structuredClone(result) };
4932
+ return applySecretOverlay(result);
4904
4933
  } catch {
4905
4934
  return freshDefaults();
4906
4935
  }
4907
4936
  }
4908
4937
  function saveSettings(settings2) {
4938
+ _settingsCache = null;
4909
4939
  const secrets = loadSecrets();
4910
4940
  const stripped = {
4911
4941
  ...settings2,
@@ -4929,7 +4959,7 @@ function saveSettings(settings2) {
4929
4959
  } catch {
4930
4960
  }
4931
4961
  }
4932
- var CLI_DETECTION_ORDER, SCHEMA_VERSION, _sourceSettingsCache, SECRET_FIELDS;
4962
+ var CLI_DETECTION_ORDER, SCHEMA_VERSION, PERSONAL_DIR_NAMES, PERSONAL_DIR_PATTERNS, _sourceSettingsCache, SECRET_FIELDS, _secretsCache, _settingsCache;
4933
4963
  var init_settings = __esm({
4934
4964
  "../engine/dist/core/settings.js"() {
4935
4965
  "use strict";
@@ -4943,6 +4973,18 @@ var init_settings = __esm({
4943
4973
  { preset: "aichat", bin: "aichat", cmd: "aichat --no-stream" }
4944
4974
  ];
4945
4975
  SCHEMA_VERSION = 3;
4976
+ PERSONAL_DIR_NAMES = [
4977
+ "Pictures",
4978
+ "Music",
4979
+ "Movies",
4980
+ "Videos",
4981
+ "Photos Library",
4982
+ "Documents",
4983
+ "Desktop",
4984
+ "Downloads",
4985
+ "Library"
4986
+ ];
4987
+ PERSONAL_DIR_PATTERNS = PERSONAL_DIR_NAMES.map((n) => `/${n}/`);
4946
4988
  _sourceSettingsCache = null;
4947
4989
  SECRET_FIELDS = [
4948
4990
  { block: "summary", field: "apiKey", env: "SUMMARY_API_KEY" },
@@ -4953,6 +4995,8 @@ var init_settings = __esm({
4953
4995
  { block: "embedding", field: "ollamaCloudApiKey", env: "OLLAMA_API_KEY" },
4954
4996
  { block: "embedding", field: "openaiCompatApiKey", env: "OPENAI_COMPAT_API_KEY" }
4955
4997
  ];
4998
+ _secretsCache = null;
4999
+ _settingsCache = null;
4956
5000
  }
4957
5001
  });
4958
5002
 
@@ -5042,14 +5086,14 @@ function primaryHomes() {
5042
5086
  s = void 0;
5043
5087
  }
5044
5088
  const opencodeDb = env.CHAT_RECALL_OPENCODE_DB || s?.opencodeDbPath || join3(home, ".local", "share", "opencode", "opencode.db");
5045
- const candidates = [
5089
+ const candidates2 = [
5046
5090
  env.CHAT_RECALL_CLAUDE_HOME || s?.claudeHome || join3(home, ".claude"),
5047
5091
  env.CHAT_RECALL_GEMINI_HOME || s?.geminiHome || join3(home, ".gemini"),
5048
5092
  env.CHAT_RECALL_CODEX_HOME || s?.codexHome || join3(home, ".codex"),
5049
5093
  env.CHAT_RECALL_AGY_HOME || s?.agyHome || join3(home, ".gemini", "antigravity-cli"),
5050
5094
  dirOf(opencodeDb)
5051
5095
  ];
5052
- return candidates.map(normalizeHomePath);
5096
+ return candidates2.map(normalizeHomePath);
5053
5097
  }
5054
5098
  function homeDecision(path) {
5055
5099
  const p = normalizeHomePath(path);
@@ -5416,28 +5460,55 @@ function maskSecret(s) {
5416
5460
  return "*".repeat(s.length);
5417
5461
  return "*".repeat(Math.min(s.length - 4, 60)) + s.slice(-4);
5418
5462
  }
5463
+ function globalRegexFor(pattern) {
5464
+ const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
5465
+ const key = pattern.source + "\0" + flags;
5466
+ let re = globalRegexCache.get(key);
5467
+ if (!re) {
5468
+ re = new RegExp(pattern.source, flags);
5469
+ globalRegexCache.set(key, re);
5470
+ }
5471
+ return re;
5472
+ }
5473
+ function makeLineResolver(text) {
5474
+ const starts = [];
5475
+ for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1))
5476
+ starts.push(i);
5477
+ return (index) => {
5478
+ let lo = 0, hi = starts.length;
5479
+ while (lo < hi) {
5480
+ const mid = lo + hi >> 1;
5481
+ if (starts[mid] < index)
5482
+ lo = mid + 1;
5483
+ else
5484
+ hi = mid;
5485
+ }
5486
+ return lo + 1;
5487
+ };
5488
+ }
5419
5489
  function scanTextForFindings(text, opts = {}) {
5420
5490
  if (!text)
5421
5491
  return [];
5422
5492
  const rules = opts.rules || activeRules();
5423
5493
  const findings = [];
5424
5494
  const seenSpans = /* @__PURE__ */ new Set();
5495
+ const lineAt = makeLineResolver(text);
5425
5496
  for (const r of rules) {
5426
- const re = new RegExp(r.pattern.source, r.pattern.flags.includes("g") ? r.pattern.flags : r.pattern.flags + "g");
5497
+ const re = globalRegexFor(r.pattern);
5498
+ re.lastIndex = 0;
5427
5499
  let m;
5428
5500
  while ((m = re.exec(text)) !== null) {
5429
5501
  const span = `${m.index}:${m[0].length}`;
5430
5502
  if (!seenSpans.has(span)) {
5431
5503
  seenSpans.add(span);
5432
- const line = text.slice(0, m.index).split("\n").length;
5433
- findings.push({ rule: r.label, line, preview: maskSecret(m[0]) });
5504
+ findings.push({ rule: r.label, line: lineAt(m.index), preview: maskSecret(m[0]) });
5434
5505
  }
5435
5506
  if (re.lastIndex === m.index)
5436
5507
  re.lastIndex++;
5437
5508
  }
5438
5509
  }
5439
5510
  for (const { start, end } of findContextualSecrets(text)) {
5440
- const line = text.slice(0, start).split("\n").length;
5511
+ const line = lineAt(start);
5441
5512
  findings.push({ rule: "secret-context", line, preview: maskSecret(text.slice(start, end)) });
5442
5513
  }
5443
5514
  return findings;
@@ -5468,7 +5539,7 @@ function redactToolResultBody(toolName, body) {
5468
5539
  return body;
5469
5540
  return `[redacted: ${toolName} output, ${body.length} chars]`;
5470
5541
  }
5471
- var DEFAULT_REDACTION_RULES, SECRET_CONTEXT_RE, HIGH_ENTROPY_TOKEN_RE, cache, CANARY, MAX_PACK_RULES, MAX_PATTERN_LEN, serverPack, privacyCache;
5542
+ var DEFAULT_REDACTION_RULES, SECRET_CONTEXT_RE, HIGH_ENTROPY_TOKEN_RE, cache, CANARY, MAX_PACK_RULES, MAX_PATTERN_LEN, serverPack, globalRegexCache, privacyCache;
5472
5543
  var init_secret_redactor = __esm({
5473
5544
  "../engine/dist/core/secret-redactor.js"() {
5474
5545
  "use strict";
@@ -5606,6 +5677,7 @@ var init_secret_redactor = __esm({
5606
5677
  MAX_PACK_RULES = 200;
5607
5678
  MAX_PATTERN_LEN = 512;
5608
5679
  serverPack = null;
5680
+ globalRegexCache = /* @__PURE__ */ new Map();
5609
5681
  privacyCache = null;
5610
5682
  }
5611
5683
  });
@@ -6365,8 +6437,8 @@ function decodeDirName(dirName) {
6365
6437
  }
6366
6438
  for (let len = 2; len <= segments.length - i; len++) {
6367
6439
  const joinedSegments = segments.slice(i, i + len);
6368
- for (const sep2 of ["-", "_", "."]) {
6369
- const candidate = join5(currentPath, joinedSegments.join(sep2));
6440
+ for (const sep3 of ["-", "_", "."]) {
6441
+ const candidate = join5(currentPath, joinedSegments.join(sep3));
6370
6442
  if (existsSync5(candidate)) {
6371
6443
  currentPath = candidate;
6372
6444
  i += len;
@@ -6657,8 +6729,8 @@ var init_claude = __esm({
6657
6729
 
6658
6730
  // ../engine/dist/transcript/gemini.js
6659
6731
  async function parseGeminiTranscript(sessionPath) {
6660
- const { readFileSync: readFileSync38 } = await import("fs");
6661
- return parseGeminiTranscriptText(readFileSync38(sessionPath, "utf-8"));
6732
+ const { readFileSync: readFileSync39 } = await import("fs");
6733
+ return parseGeminiTranscriptText(readFileSync39(sessionPath, "utf-8"));
6662
6734
  }
6663
6735
  function parseGeminiTranscriptText(text) {
6664
6736
  let content;
@@ -7114,11 +7186,11 @@ async function archiveRawSession(store, sessionId) {
7114
7186
  return await store.putRawSession(sessionId, exp.tool, Math.floor(exp.mtime), gz, size);
7115
7187
  }
7116
7188
  function detectForkPredecessor(sessionPath) {
7117
- const { readFileSync: readFileSync38, readdirSync: readdirSync32 } = requireFs();
7118
- const { dirname: dirname17, join: join47, basename: basename25 } = requirePath();
7189
+ const { readFileSync: readFileSync39, readdirSync: readdirSync32 } = requireFs();
7190
+ const { dirname: dirname18, join: join50, basename: basename25 } = requirePath();
7119
7191
  let text;
7120
7192
  try {
7121
- text = readFileSync38(sessionPath, "utf-8");
7193
+ text = readFileSync39(sessionPath, "utf-8");
7122
7194
  } catch {
7123
7195
  return null;
7124
7196
  }
@@ -7140,7 +7212,7 @@ function detectForkPredecessor(sessionPath) {
7140
7212
  return null;
7141
7213
  if (text.includes(`"uuid":"${headParent}"`))
7142
7214
  return null;
7143
- const dir = dirname17(sessionPath);
7215
+ const dir = dirname18(sessionPath);
7144
7216
  const self = basename25(sessionPath);
7145
7217
  let entries;
7146
7218
  try {
@@ -7152,7 +7224,7 @@ function detectForkPredecessor(sessionPath) {
7152
7224
  if (!f.endsWith(".jsonl") || f === self || f === "sessions-index.json")
7153
7225
  continue;
7154
7226
  try {
7155
- if (readFileSync38(join47(dir, f), "utf-8").includes(`"uuid":"${headParent}"`)) {
7227
+ if (readFileSync39(join50(dir, f), "utf-8").includes(`"uuid":"${headParent}"`)) {
7156
7228
  return basename25(f, ".jsonl");
7157
7229
  }
7158
7230
  } catch {
@@ -7367,6 +7439,26 @@ var init_shadow = __esm({
7367
7439
  }
7368
7440
  });
7369
7441
 
7442
+ // ../engine/dist/core/project-dir-name.js
7443
+ function looksWindowsEncoded(dirName) {
7444
+ return /^[A-Za-z]--/.test(dirName);
7445
+ }
7446
+ function decodeProjectDirName(dirName) {
7447
+ if (!dirName)
7448
+ return "";
7449
+ if (looksWindowsEncoded(dirName)) {
7450
+ const drive = dirName[0].toUpperCase();
7451
+ const rest = dirName.slice(3).replace(/-/g, "\\");
7452
+ return `${drive}:\\${rest}`;
7453
+ }
7454
+ return "/" + dirName.replace(/^-/, "").replace(/-/g, "/");
7455
+ }
7456
+ var init_project_dir_name = __esm({
7457
+ "../engine/dist/core/project-dir-name.js"() {
7458
+ "use strict";
7459
+ }
7460
+ });
7461
+
7370
7462
  // ../engine/dist/core/live-session-scan.js
7371
7463
  import { existsSync as existsSync7, readdirSync as readdirSync5, readFileSync as readFileSync4, statSync as statSync6, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
7372
7464
  import { join as join9, basename as basename5 } from "path";
@@ -7390,7 +7482,7 @@ function findSessionFile(sessionId) {
7390
7482
  path: candidate,
7391
7483
  projectDir: entry.name,
7392
7484
  // Project path is encoded by replacing slashes with dashes.
7393
- projectPath: entry.name.replace(/-/g, "/").replace(/^\//, "/")
7485
+ projectPath: decodeProjectDirName(entry.name)
7394
7486
  };
7395
7487
  }
7396
7488
  }
@@ -7419,7 +7511,7 @@ function findSessionFiles(sessionId) {
7419
7511
  out.push({
7420
7512
  path: candidate,
7421
7513
  projectDir: entry.name,
7422
- projectPath: entry.name.replace(/-/g, "/").replace(/^\//, "/")
7514
+ projectPath: decodeProjectDirName(entry.name)
7423
7515
  });
7424
7516
  }
7425
7517
  }
@@ -7443,7 +7535,31 @@ function resolveSessionContentGroups(sessionId) {
7443
7535
  const names = [...groups.keys()].sort((a, b) => a === "main" ? -1 : b === "main" ? 1 : a.localeCompare(b));
7444
7536
  return names.map((name) => ({ name, paths: groups.get(name) }));
7445
7537
  }
7538
+ function withSessionReadCache(fn) {
7539
+ readScopeDepth++;
7540
+ try {
7541
+ return fn();
7542
+ } finally {
7543
+ if (--readScopeDepth === 0)
7544
+ readScope = null;
7545
+ }
7546
+ }
7446
7547
  function readSessionGroupText(group) {
7548
+ let key = "";
7549
+ if (readScopeDepth > 0) {
7550
+ const parts = [];
7551
+ for (const p of group.paths) {
7552
+ try {
7553
+ const st = statSync6(p);
7554
+ parts.push(`${p}:${st.mtimeMs}:${st.size}`);
7555
+ } catch {
7556
+ parts.push(`${p}:missing`);
7557
+ }
7558
+ }
7559
+ key = parts.join("|");
7560
+ if (readScope && readScope.key === key)
7561
+ return readScope.value;
7562
+ }
7447
7563
  let text = "";
7448
7564
  let mtime = 0;
7449
7565
  for (const p of group.paths) {
@@ -7459,7 +7575,10 @@ function readSessionGroupText(group) {
7459
7575
  }
7460
7576
  text = text ? mergeLineText(text, raw).text : raw;
7461
7577
  }
7462
- return { text, mtime };
7578
+ const out = { text, mtime };
7579
+ if (readScopeDepth > 0 && key)
7580
+ readScope = { key, value: out };
7581
+ return out;
7463
7582
  }
7464
7583
  function resolveSessionContentPaths(sessionFile) {
7465
7584
  if (!hasSubagentsDir(sessionFile))
@@ -7682,7 +7801,7 @@ function findCodexSessionFileIn(root, sessionId) {
7682
7801
  }
7683
7802
  return scanDir(root);
7684
7803
  }
7685
- var geminiProjectMapCache, MAX_EVENT_LINE_BYTES;
7804
+ var readScope, readScopeDepth, geminiProjectMapCache, MAX_EVENT_LINE_BYTES;
7686
7805
  var init_live_session_scan = __esm({
7687
7806
  "../engine/dist/core/live-session-scan.js"() {
7688
7807
  "use strict";
@@ -7691,6 +7810,9 @@ var init_live_session_scan = __esm({
7691
7810
  init_tool_backend();
7692
7811
  init_tool_paths();
7693
7812
  init_backends();
7813
+ init_project_dir_name();
7814
+ readScope = null;
7815
+ readScopeDepth = 0;
7694
7816
  geminiProjectMapCache = null;
7695
7817
  MAX_EVENT_LINE_BYTES = 8 * 1024 * 1024;
7696
7818
  }
@@ -8151,74 +8273,123 @@ var init_session_outcome = __esm({
8151
8273
  }
8152
8274
  });
8153
8275
 
8276
+ // ../engine/dist/core/flat-string.js
8277
+ function flatString(s) {
8278
+ if (!s)
8279
+ return "";
8280
+ if (s.length < 13)
8281
+ return s;
8282
+ return Buffer.from(s, "utf8").toString("utf8");
8283
+ }
8284
+ var init_flat_string = __esm({
8285
+ "../engine/dist/core/flat-string.js"() {
8286
+ "use strict";
8287
+ }
8288
+ });
8289
+
8154
8290
  // ../engine/dist/core/first-prompt.js
8155
- import { createReadStream as createReadStream2, existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
8291
+ import { createReadStream as createReadStream2, existsSync as existsSync9, openSync as openSync3, readSync as readSync3, closeSync as closeSync3 } from "fs";
8292
+ import { StringDecoder } from "string_decoder";
8156
8293
  function stripBanners2(s) {
8157
8294
  let out = s;
8158
8295
  for (const re of BANNERS)
8159
8296
  out = out.replace(re, " ");
8160
8297
  return out.replace(/[ \t]{2,}/g, " ").trim();
8161
8298
  }
8299
+ function promptFromLine(raw, maxLength) {
8300
+ if (!raw.trim())
8301
+ return null;
8302
+ let obj;
8303
+ try {
8304
+ obj = JSON.parse(raw);
8305
+ } catch {
8306
+ return null;
8307
+ }
8308
+ if (obj.type !== "user")
8309
+ return null;
8310
+ if (obj.isSidechain === true)
8311
+ return null;
8312
+ const message = obj.message;
8313
+ if (!message)
8314
+ return null;
8315
+ const content = message.content;
8316
+ let text = "";
8317
+ if (typeof content === "string") {
8318
+ text = content;
8319
+ } else if (Array.isArray(content)) {
8320
+ const parts = [];
8321
+ for (const block of content) {
8322
+ if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
8323
+ parts.push(block.text);
8324
+ }
8325
+ }
8326
+ text = parts.join("\n");
8327
+ }
8328
+ if (!text)
8329
+ return null;
8330
+ if (text.includes("<system-reminder>"))
8331
+ return null;
8332
+ if (text.trim() === "[Request interrupted by user]")
8333
+ return null;
8334
+ const cleaned = stripBanners2(text);
8335
+ if (cleaned.length < 10)
8336
+ return null;
8337
+ return flatString(cleaned.slice(0, maxLength));
8338
+ }
8162
8339
  function extractFirstUserPromptSync(filePath, options = {}) {
8163
8340
  const maxLines = options.maxLines ?? 500;
8164
8341
  const maxLength = options.maxLength ?? 1e3;
8165
- if (!existsSync9(filePath))
8166
- return "";
8167
- let raw;
8342
+ const maxBytes = options.maxBytes ?? 4 * 1024 * 1024;
8343
+ let fd;
8168
8344
  try {
8169
- raw = readFileSync5(filePath, "utf-8");
8345
+ fd = openSync3(filePath, "r");
8170
8346
  } catch {
8171
8347
  return "";
8172
8348
  }
8173
- const lines = raw.split("\n");
8174
- const upTo = Math.min(lines.length, maxLines);
8175
- for (let i = 0; i < upTo; i++) {
8176
- const line = lines[i];
8177
- if (!line.trim())
8178
- continue;
8179
- let obj;
8349
+ try {
8350
+ const CHUNK = 64 * 1024;
8351
+ const buf = Buffer.allocUnsafe(CHUNK);
8352
+ const decoder = new StringDecoder("utf-8");
8353
+ let carry = "";
8354
+ let pos = 0;
8355
+ let lineNo = 0;
8356
+ for (; ; ) {
8357
+ const n = readSync3(fd, buf, 0, CHUNK, pos);
8358
+ if (n <= 0)
8359
+ break;
8360
+ pos += n;
8361
+ carry += decoder.write(buf.subarray(0, n));
8362
+ const parts = carry.split("\n");
8363
+ carry = parts.pop() ?? "";
8364
+ for (const line of parts) {
8365
+ if (++lineNo > maxLines)
8366
+ return "";
8367
+ const found = promptFromLine(line, maxLength);
8368
+ if (found !== null)
8369
+ return found;
8370
+ }
8371
+ if (pos >= maxBytes)
8372
+ return "";
8373
+ }
8374
+ carry += decoder.end();
8375
+ if (carry && lineNo < maxLines) {
8376
+ const found = promptFromLine(carry, maxLength);
8377
+ if (found !== null)
8378
+ return found;
8379
+ }
8380
+ return "";
8381
+ } finally {
8180
8382
  try {
8181
- obj = JSON.parse(line);
8383
+ closeSync3(fd);
8182
8384
  } catch {
8183
- continue;
8184
- }
8185
- if (obj.type !== "user")
8186
- continue;
8187
- if (obj.isSidechain === true)
8188
- continue;
8189
- const message = obj.message;
8190
- if (!message)
8191
- continue;
8192
- const content = message.content;
8193
- let text = "";
8194
- if (typeof content === "string") {
8195
- text = content;
8196
- } else if (Array.isArray(content)) {
8197
- const parts = [];
8198
- for (const block of content) {
8199
- if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
8200
- parts.push(block.text);
8201
- }
8202
- }
8203
- text = parts.join("\n");
8204
8385
  }
8205
- if (!text)
8206
- continue;
8207
- if (text.includes("<system-reminder>"))
8208
- continue;
8209
- if (text.trim() === "[Request interrupted by user]")
8210
- continue;
8211
- const cleaned = stripBanners2(text);
8212
- if (cleaned.length < 10)
8213
- continue;
8214
- return cleaned.slice(0, maxLength);
8215
8386
  }
8216
- return "";
8217
8387
  }
8218
8388
  var BANNERS;
8219
8389
  var init_first_prompt = __esm({
8220
8390
  "../engine/dist/core/first-prompt.js"() {
8221
8391
  "use strict";
8392
+ init_flat_string();
8222
8393
  BANNERS = [
8223
8394
  /MCP issues detected\. ?Run \/mcp list for status\.?/g,
8224
8395
  /Context low[^\n]*Run \/compact[^\n]*/g,
@@ -8228,7 +8399,7 @@ var init_first_prompt = __esm({
8228
8399
  });
8229
8400
 
8230
8401
  // ../engine/dist/core/backends/tail-read.js
8231
- import { openSync as openSync3, readSync as readSync3, closeSync as closeSync3, statSync as statSync7 } from "node:fs";
8402
+ import { openSync as openSync4, readSync as readSync4, closeSync as closeSync4, statSync as statSync7 } from "node:fs";
8232
8403
  function readTailFromOffset(path, offset) {
8233
8404
  let size = 0;
8234
8405
  try {
@@ -8242,10 +8413,10 @@ function readTailFromOffset(path, offset) {
8242
8413
  const buf = Buffer.allocUnsafe(len);
8243
8414
  let fd = null;
8244
8415
  try {
8245
- fd = openSync3(path, "r");
8416
+ fd = openSync4(path, "r");
8246
8417
  let got = 0;
8247
8418
  while (got < len) {
8248
- const n = readSync3(fd, buf, got, len - got, offset + got);
8419
+ const n = readSync4(fd, buf, got, len - got, offset + got);
8249
8420
  if (n <= 0)
8250
8421
  break;
8251
8422
  got += n;
@@ -8264,7 +8435,7 @@ function readTailFromOffset(path, offset) {
8264
8435
  } finally {
8265
8436
  if (fd !== null) {
8266
8437
  try {
8267
- closeSync3(fd);
8438
+ closeSync4(fd);
8268
8439
  } catch {
8269
8440
  }
8270
8441
  }
@@ -8549,7 +8720,7 @@ var init_generic_engine = __esm({
8549
8720
  });
8550
8721
 
8551
8722
  // ../engine/dist/core/backends/claude.js
8552
- import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync6, statSync as statSync8, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
8723
+ import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync5, statSync as statSync8, openSync as openSync5, readSync as readSync5, closeSync as closeSync5 } from "fs";
8553
8724
  import { join as join10, basename as basename8 } from "path";
8554
8725
  function stripReminders(text) {
8555
8726
  return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
@@ -8568,6 +8739,7 @@ var init_claude2 = __esm({
8568
8739
  init_session_git();
8569
8740
  init_first_prompt();
8570
8741
  init_tail_read();
8742
+ init_project_dir_name();
8571
8743
  init_generic_engine();
8572
8744
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8573
8745
  ClaudeBackend = class {
@@ -8644,13 +8816,13 @@ var init_claude2 = __esm({
8644
8816
  return null;
8645
8817
  let head;
8646
8818
  try {
8647
- const fd = openSync4(located.path, "r");
8819
+ const fd = openSync5(located.path, "r");
8648
8820
  try {
8649
8821
  const buf = Buffer.alloc(65536);
8650
- const n = readSync4(fd, buf, 0, buf.length, 0);
8822
+ const n = readSync5(fd, buf, 0, buf.length, 0);
8651
8823
  head = buf.toString("utf-8", 0, n);
8652
8824
  } finally {
8653
- closeSync4(fd);
8825
+ closeSync5(fd);
8654
8826
  }
8655
8827
  } catch {
8656
8828
  return null;
@@ -8709,11 +8881,11 @@ var init_claude2 = __esm({
8709
8881
  if (filter && !proj.name.toLowerCase().includes(filter))
8710
8882
  continue;
8711
8883
  const projPath = join10(root, proj.name);
8712
- const projectPath = proj.name.replace(/-/g, "/").replace(/^\//, "/");
8884
+ const projectPath = decodeProjectDirName(proj.name);
8713
8885
  const indexPath = join10(projPath, "sessions-index.json");
8714
8886
  if (existsSync10(indexPath)) {
8715
8887
  try {
8716
- const indexData = JSON.parse(readFileSync6(indexPath, "utf-8"));
8888
+ const indexData = JSON.parse(readFileSync5(indexPath, "utf-8"));
8717
8889
  const entries = indexData.entries || [];
8718
8890
  for (const entry of entries) {
8719
8891
  if (!entry.fullPath || !existsSync10(entry.fullPath))
@@ -9005,7 +9177,7 @@ var init_claude2 = __esm({
9005
9177
  * with quiet main + active subagents still surfaces.
9006
9178
  */
9007
9179
  collectRecentEdits(opts) {
9008
- const candidates = [];
9180
+ const candidates2 = [];
9009
9181
  for (const root of claudeProjectDirs()) {
9010
9182
  if (!existsSync10(root))
9011
9183
  continue;
@@ -9041,14 +9213,14 @@ var init_claude2 = __esm({
9041
9213
  }
9042
9214
  if (mtime < opts.sinceMs)
9043
9215
  continue;
9044
- candidates.push({ rawId: basename8(f, ".jsonl"), mtime });
9216
+ candidates2.push({ rawId: basename8(f, ".jsonl"), mtime });
9045
9217
  } catch {
9046
9218
  }
9047
9219
  }
9048
9220
  }
9049
9221
  }
9050
- candidates.sort((a, b) => b.mtime - a.mtime);
9051
- const limited = opts.limitSessions ? candidates.slice(0, opts.limitSessions) : candidates;
9222
+ candidates2.sort((a, b) => b.mtime - a.mtime);
9223
+ const limited = opts.limitSessions ? candidates2.slice(0, opts.limitSessions) : candidates2;
9052
9224
  const edits = [];
9053
9225
  for (const c of limited) {
9054
9226
  const scan = this.liveScanEdits(c.rawId);
@@ -9135,12 +9307,12 @@ var init_claude2 = __esm({
9135
9307
 
9136
9308
  // ../engine/dist/core/backends/gemini.js
9137
9309
  import { createHash as createHash4 } from "crypto";
9138
- import { existsSync as existsSync11, readdirSync as readdirSync7, readFileSync as readFileSync7, statSync as statSync9 } from "fs";
9310
+ import { existsSync as existsSync11, readdirSync as readdirSync7, readFileSync as readFileSync6, statSync as statSync9 } from "fs";
9139
9311
  import { basename as basename9, join as join11 } from "path";
9140
9312
  function readSessionId(path, format) {
9141
9313
  try {
9142
9314
  if (format === "json") {
9143
- const json = JSON.parse(readFileSync7(path, "utf-8"));
9315
+ const json = JSON.parse(readFileSync6(path, "utf-8"));
9144
9316
  return typeof json?.sessionId === "string" ? json.sessionId : null;
9145
9317
  }
9146
9318
  const head = readHeadSync(path).split("\n", 1)[0] || "";
@@ -9174,6 +9346,7 @@ var init_gemini2 = __esm({
9174
9346
  init_session_git();
9175
9347
  init_generic_engine();
9176
9348
  init_tail_read();
9349
+ init_flat_string();
9177
9350
  PREFIX = "gemini_";
9178
9351
  FULL_PARSE_MAX = 4 * 1024 * 1024;
9179
9352
  GeminiBackend = class {
@@ -9283,7 +9456,7 @@ var init_gemini2 = __esm({
9283
9456
  sessionId = readSessionId(fullPath, format);
9284
9457
  const messages = readGeminiMessages(fullPath, format);
9285
9458
  const firstUser = messages.find((m) => m.type === "user");
9286
- firstPrompt = (typeof firstUser?.text === "string" ? firstUser.text : flatten(firstUser?.content)).slice(0, 200);
9459
+ firstPrompt = flatString((typeof firstUser?.text === "string" ? firstUser.text : flatten(firstUser?.content)).slice(0, 200));
9287
9460
  messageCount = messages.length;
9288
9461
  } else if (format === "jsonl") {
9289
9462
  const lines = readHeadSync(fullPath, 1048576).split("\n");
@@ -9305,7 +9478,7 @@ var init_gemini2 = __esm({
9305
9478
  }
9306
9479
  if (m?.type !== "user")
9307
9480
  continue;
9308
- firstPrompt = (typeof m.text === "string" ? m.text : flatten(m.content)).slice(0, 200);
9481
+ firstPrompt = flatString((typeof m.text === "string" ? m.text : flatten(m.content)).slice(0, 200));
9309
9482
  break;
9310
9483
  }
9311
9484
  }
@@ -9481,7 +9654,7 @@ var init_gemini2 = __esm({
9481
9654
  if (!existsSync11(tmpRoot))
9482
9655
  return [];
9483
9656
  const projMap = this.loadProjectMap();
9484
- const candidates = [];
9657
+ const candidates2 = [];
9485
9658
  for (const proj of readdirSync7(tmpRoot, { withFileTypes: true })) {
9486
9659
  if (!proj.isDirectory())
9487
9660
  continue;
@@ -9512,13 +9685,13 @@ var init_gemini2 = __esm({
9512
9685
  if (st.mtimeMs < opts.sinceMs)
9513
9686
  continue;
9514
9687
  const trimLen = isJsonl ? 6 : 5;
9515
- candidates.push({ rawIdHint: f.slice(0, -trimLen), mtime: st.mtimeMs });
9688
+ candidates2.push({ rawIdHint: f.slice(0, -trimLen), mtime: st.mtimeMs });
9516
9689
  } catch {
9517
9690
  }
9518
9691
  }
9519
9692
  }
9520
- candidates.sort((a, b) => b.mtime - a.mtime);
9521
- const limited = opts.limitSessions ? candidates.slice(0, opts.limitSessions) : candidates;
9693
+ candidates2.sort((a, b) => b.mtime - a.mtime);
9694
+ const limited = opts.limitSessions ? candidates2.slice(0, opts.limitSessions) : candidates2;
9522
9695
  const edits = [];
9523
9696
  for (const c of limited) {
9524
9697
  const scan = this.liveScanEdits(c.rawIdHint);
@@ -9537,7 +9710,7 @@ var init_gemini2 = __esm({
9537
9710
  if (!existsSync11(path))
9538
9711
  return map;
9539
9712
  try {
9540
- const json = JSON.parse(readFileSync7(path, "utf-8"));
9713
+ const json = JSON.parse(readFileSync6(path, "utf-8"));
9541
9714
  const projects = json?.projects ?? {};
9542
9715
  for (const projectPath of Object.keys(projects)) {
9543
9716
  const hash = createHash4("sha256").update(projectPath).digest("hex");
@@ -9555,7 +9728,7 @@ var init_gemini2 = __esm({
9555
9728
  return {
9556
9729
  tool: "gemini",
9557
9730
  mtime: statSync9(loc.path).mtimeMs,
9558
- files: [{ name: basename9(loc.path), bytes: readFileSync7(loc.path) }]
9731
+ files: [{ name: basename9(loc.path), bytes: readFileSync6(loc.path) }]
9559
9732
  };
9560
9733
  } catch {
9561
9734
  return null;
@@ -9650,6 +9823,7 @@ var init_opencode = __esm({
9650
9823
  init_session_outcome();
9651
9824
  init_session_git();
9652
9825
  init_generic_engine();
9826
+ init_flat_string();
9653
9827
  PREFIX2 = "opencode_";
9654
9828
  OpencodeBackend = class {
9655
9829
  id = "opencode";
@@ -9817,7 +9991,7 @@ var init_opencode = __esm({
9817
9991
  if (firstUser) {
9818
9992
  try {
9819
9993
  const parsed = JSON.parse(firstUser.data);
9820
- firstPrompt = String(parsed?.text || "").slice(0, 200);
9994
+ firstPrompt = flatString(String(parsed?.text || "").slice(0, 200));
9821
9995
  } catch {
9822
9996
  }
9823
9997
  }
@@ -10161,7 +10335,7 @@ var init_opencode = __esm({
10161
10335
  });
10162
10336
 
10163
10337
  // ../engine/dist/core/backends/codex.js
10164
- import { readdirSync as readdirSync8, readFileSync as readFileSync8, statSync as statSync10, openSync as openSync5, readSync as readSync5, closeSync as closeSync5 } from "fs";
10338
+ import { readdirSync as readdirSync8, readFileSync as readFileSync7, statSync as statSync10, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
10165
10339
  import { basename as basename10, dirname as dirname10, join as join13 } from "path";
10166
10340
  function parseShellWrites(cmd) {
10167
10341
  const out = [];
@@ -10290,7 +10464,7 @@ function flattenContent(content) {
10290
10464
  }
10291
10465
  function readFirstLine(path) {
10292
10466
  try {
10293
- const head = readFileSync8(path, "utf-8").split("\n", 1)[0] || "";
10467
+ const head = readFileSync7(path, "utf-8").split("\n", 1)[0] || "";
10294
10468
  if (!head)
10295
10469
  return null;
10296
10470
  return JSON.parse(head);
@@ -10308,6 +10482,7 @@ var init_codex2 = __esm({
10308
10482
  init_session_git();
10309
10483
  init_generic_engine();
10310
10484
  init_tail_read();
10485
+ init_flat_string();
10311
10486
  PREFIX3 = "codex_";
10312
10487
  CodexBackend = class {
10313
10488
  id = "codex";
@@ -10407,7 +10582,7 @@ var init_codex2 = __esm({
10407
10582
  let firstPrompt = "";
10408
10583
  let messageCount = 0;
10409
10584
  try {
10410
- const lines = readFileSync8(filePath, "utf-8").split("\n");
10585
+ const lines = readFileSync7(filePath, "utf-8").split("\n");
10411
10586
  for (const line of lines) {
10412
10587
  if (!line.trim())
10413
10588
  continue;
@@ -10420,7 +10595,7 @@ var init_codex2 = __esm({
10420
10595
  if (ev.type === "event_msg" && ev.payload?.type === "user_message") {
10421
10596
  messageCount++;
10422
10597
  if (!firstPrompt && ev.payload.message) {
10423
- firstPrompt = String(ev.payload.message).slice(0, 200);
10598
+ firstPrompt = flatString(String(ev.payload.message).slice(0, 200));
10424
10599
  }
10425
10600
  } else if (ev.type === "response_item" && ev.payload?.type === "message") {
10426
10601
  messageCount++;
@@ -10559,7 +10734,7 @@ var init_codex2 = __esm({
10559
10734
  continue;
10560
10735
  let firstLine = "";
10561
10736
  try {
10562
- firstLine = readFileSync8(path, "utf-8").split("\n", 1)[0] || "";
10737
+ firstLine = readFileSync7(path, "utf-8").split("\n", 1)[0] || "";
10563
10738
  } catch {
10564
10739
  continue;
10565
10740
  }
@@ -10583,7 +10758,7 @@ var init_codex2 = __esm({
10583
10758
  let raw = "";
10584
10759
  let mtime = 0;
10585
10760
  try {
10586
- raw = readFileSync8(path, "utf-8");
10761
+ raw = readFileSync7(path, "utf-8");
10587
10762
  mtime = statSync10(path).mtimeMs;
10588
10763
  } catch {
10589
10764
  return [];
@@ -10760,7 +10935,7 @@ var init_codex2 = __esm({
10760
10935
  let parentId = null;
10761
10936
  let isSubagent = false;
10762
10937
  try {
10763
- const first = readFileSync8(p, "utf-8").split("\n")[0];
10938
+ const first = readFileSync7(p, "utf-8").split("\n")[0];
10764
10939
  if (first) {
10765
10940
  const meta = JSON.parse(first);
10766
10941
  const payload = meta?.payload || {};
@@ -10816,7 +10991,7 @@ var init_codex2 = __esm({
10816
10991
  const push = (path, name) => {
10817
10992
  try {
10818
10993
  const st = statSync10(path);
10819
- files.push({ name, bytes: readFileSync8(path) });
10994
+ files.push({ name, bytes: readFileSync7(path) });
10820
10995
  if (st.mtimeMs > mtime)
10821
10996
  mtime = st.mtimeMs;
10822
10997
  } catch {
@@ -10834,10 +11009,10 @@ var init_codex2 = __esm({
10834
11009
  if (p === loc.path)
10835
11010
  continue;
10836
11011
  try {
10837
- const fd = openSync5(p, "r");
11012
+ const fd = openSync6(p, "r");
10838
11013
  const buf = Buffer.alloc(4096);
10839
- const n = readSync5(fd, buf, 0, 4096, 0);
10840
- closeSync5(fd);
11014
+ const n = readSync6(fd, buf, 0, 4096, 0);
11015
+ closeSync6(fd);
10841
11016
  const head = buf.toString("utf-8", 0, n);
10842
11017
  if (head.includes(parentId) && head.includes("parent_thread_id"))
10843
11018
  push(p, `subagents/${f}`);
@@ -10874,13 +11049,13 @@ var init_codex2 = __esm({
10874
11049
  });
10875
11050
 
10876
11051
  // ../engine/dist/core/backends/agy.js
10877
- import { existsSync as existsSync13, readdirSync as readdirSync9, readFileSync as readFileSync9, statSync as statSync11 } from "fs";
11052
+ import { existsSync as existsSync13, readdirSync as readdirSync9, readFileSync as readFileSync8, statSync as statSync11 } from "fs";
10878
11053
  import { basename as basename11, dirname as dirname11, join as join14 } from "path";
10879
11054
  function getFallbackProjectPath(homeDir) {
10880
11055
  try {
10881
11056
  const settingsPath2 = join14(homeDir, "settings.json");
10882
11057
  if (existsSync13(settingsPath2)) {
10883
- const json = JSON.parse(readFileSync9(settingsPath2, "utf-8"));
11058
+ const json = JSON.parse(readFileSync8(settingsPath2, "utf-8"));
10884
11059
  if (Array.isArray(json?.trustedWorkspaces) && json.trustedWorkspaces[0]) {
10885
11060
  return json.trustedWorkspaces[0];
10886
11061
  }
@@ -10907,7 +11082,7 @@ function resolveAgyProject(logsDir, homeDir) {
10907
11082
  function extractProjectPathFromTranscript(path, homeDir) {
10908
11083
  let content;
10909
11084
  try {
10910
- content = readFileSync9(path, "utf-8");
11085
+ content = readFileSync8(path, "utf-8");
10911
11086
  } catch {
10912
11087
  return "";
10913
11088
  }
@@ -10993,6 +11168,7 @@ var init_agy = __esm({
10993
11168
  init_session_outcome();
10994
11169
  init_session_git();
10995
11170
  init_generic_engine();
11171
+ init_flat_string();
10996
11172
  PREFIX4 = "agy_";
10997
11173
  AgyBackend = class {
10998
11174
  id = "agy";
@@ -11085,7 +11261,7 @@ var init_agy = __esm({
11085
11261
  let firstPrompt = "";
11086
11262
  let messageCount = 0;
11087
11263
  try {
11088
- const lines = readFileSync9(filePath, "utf-8").split("\n").filter(Boolean);
11264
+ const lines = readFileSync8(filePath, "utf-8").split("\n").filter(Boolean);
11089
11265
  messageCount = lines.length;
11090
11266
  for (const line of lines) {
11091
11267
  const obj = JSON.parse(line);
@@ -11093,7 +11269,7 @@ var init_agy = __esm({
11093
11269
  const content = obj.content;
11094
11270
  const match = content.match(/<USER_REQUEST>([\s\S]*?)<\/USER_REQUEST>/);
11095
11271
  const rawText = match ? match[1].trim() : content.trim();
11096
- firstPrompt = rawText.slice(0, 200);
11272
+ firstPrompt = flatString(rawText.slice(0, 200));
11097
11273
  break;
11098
11274
  }
11099
11275
  }
@@ -11175,7 +11351,7 @@ var init_agy = __esm({
11175
11351
  return [];
11176
11352
  let rawText;
11177
11353
  try {
11178
- rawText = readFileSync9(located.path, "utf-8");
11354
+ rawText = readFileSync8(located.path, "utf-8");
11179
11355
  } catch {
11180
11356
  return [];
11181
11357
  }
@@ -11327,7 +11503,7 @@ var init_agy = __esm({
11327
11503
  if (brainRoots.length === 0)
11328
11504
  return [];
11329
11505
  const brainDir = brainRoots[0];
11330
- const candidates = [];
11506
+ const candidates2 = [];
11331
11507
  let sessionDirs;
11332
11508
  try {
11333
11509
  sessionDirs = readdirSync9(brainDir);
@@ -11349,12 +11525,12 @@ var init_agy = __esm({
11349
11525
  const st = statSync11(filePath);
11350
11526
  if (st.mtimeMs < opts.sinceMs)
11351
11527
  continue;
11352
- candidates.push({ rawId, mtime: st.mtimeMs });
11528
+ candidates2.push({ rawId, mtime: st.mtimeMs });
11353
11529
  } catch {
11354
11530
  }
11355
11531
  }
11356
- candidates.sort((a, b) => b.mtime - a.mtime);
11357
- const limited = opts.limitSessions ? candidates.slice(0, opts.limitSessions) : candidates;
11532
+ candidates2.sort((a, b) => b.mtime - a.mtime);
11533
+ const limited = opts.limitSessions ? candidates2.slice(0, opts.limitSessions) : candidates2;
11358
11534
  const edits = [];
11359
11535
  for (const c of limited) {
11360
11536
  const scan = this.liveScanEdits(c.rawId);
@@ -11375,7 +11551,7 @@ var init_agy = __esm({
11375
11551
  return {
11376
11552
  tool: "agy",
11377
11553
  mtime: statSync11(loc.path).mtimeMs,
11378
- files: [{ name: basename11(loc.path), bytes: readFileSync9(loc.path) }]
11554
+ files: [{ name: basename11(loc.path), bytes: readFileSync8(loc.path) }]
11379
11555
  };
11380
11556
  } catch {
11381
11557
  return null;
@@ -11427,6 +11603,24 @@ var init_backends = __esm({
11427
11603
  }
11428
11604
  });
11429
11605
 
11606
+ // ../engine/dist/core/credentials-env.js
11607
+ function envCredentials(env = process.env) {
11608
+ const token = (env.CHAT_RECALL_TOKEN || "").trim();
11609
+ const server2 = (env.CHAT_RECALL_SERVER || "").trim();
11610
+ if (!token || !server2)
11611
+ return null;
11612
+ if (!/^https?:\/\//i.test(server2))
11613
+ return null;
11614
+ return { serverUrl: normalise(server2), token };
11615
+ }
11616
+ var normalise;
11617
+ var init_credentials_env = __esm({
11618
+ "../engine/dist/core/credentials-env.js"() {
11619
+ "use strict";
11620
+ normalise = (u) => u.trim().replace(/\/+$/, "");
11621
+ }
11622
+ });
11623
+
11430
11624
  // ../engine/dist/core/source-discovery.js
11431
11625
  import { createHash as createHash5 } from "crypto";
11432
11626
  import { existsSync as existsSync15, readdirSync as readdirSync10, statSync as statSync12 } from "fs";
@@ -11533,7 +11727,7 @@ var init_source_discovery = __esm({
11533
11727
 
11534
11728
  // ../engine/dist/core/home-discovery.js
11535
11729
  import { execFileSync as execFileSync2 } from "child_process";
11536
- import { existsSync as existsSync16, readdirSync as readdirSync11, readFileSync as readFileSync10, realpathSync as realpathSync2, statSync as statSync13 } from "fs";
11730
+ import { existsSync as existsSync16, readdirSync as readdirSync11, readFileSync as readFileSync9, realpathSync as realpathSync2, statSync as statSync13 } from "fs";
11537
11731
  import { homedir as homedir5, platform as osPlatform } from "os";
11538
11732
  import { join as join17 } from "path";
11539
11733
  function candidateRoots(plat = osPlatform(), env = process.env) {
@@ -11655,7 +11849,7 @@ function declaredHomes(env = process.env, plat = osPlatform()) {
11655
11849
  for (const rc of rcs) {
11656
11850
  let text;
11657
11851
  try {
11658
- text = readFileSync10(rc, "utf-8");
11852
+ text = readFileSync9(rc, "utf-8");
11659
11853
  } catch {
11660
11854
  continue;
11661
11855
  }
@@ -11680,7 +11874,7 @@ function runningToolHomes(plat = osPlatform()) {
11680
11874
  for (const pid of pids) {
11681
11875
  let env;
11682
11876
  try {
11683
- env = readFileSync10(`/proc/${pid}/environ`, "utf-8");
11877
+ env = readFileSync9(`/proc/${pid}/environ`, "utf-8");
11684
11878
  } catch {
11685
11879
  continue;
11686
11880
  }
@@ -11801,7 +11995,7 @@ var init_home_discovery = __esm({
11801
11995
 
11802
11996
  // ../engine/dist/core/detector-install.js
11803
11997
  import { createHash as createHash6 } from "crypto";
11804
- import { existsSync as existsSync17, mkdirSync as mkdirSync5, mkdtempSync, chmodSync as chmodSync3, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync11, statSync as statSync14, copyFileSync as copyFileSync2 } from "fs";
11998
+ import { existsSync as existsSync17, mkdirSync as mkdirSync5, mkdtempSync, chmodSync as chmodSync3, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync10, statSync as statSync14, copyFileSync as copyFileSync2 } from "fs";
11805
11999
  import { join as join18 } from "path";
11806
12000
  function managedDetectorPath(name) {
11807
12001
  return join18(getDetectorBinDir(), name + exeSuffix());
@@ -11810,11 +12004,11 @@ function receiptPath(name) {
11810
12004
  return join18(getDetectorBinDir(), `${name}.receipt.json`);
11811
12005
  }
11812
12006
  function sha256File(path) {
11813
- return createHash6("sha256").update(readFileSync11(path)).digest("hex");
12007
+ return createHash6("sha256").update(readFileSync10(path)).digest("hex");
11814
12008
  }
11815
12009
  function readReceipt(name) {
11816
12010
  try {
11817
- return JSON.parse(readFileSync11(receiptPath(name), "utf-8"));
12011
+ return JSON.parse(readFileSync10(receiptPath(name), "utf-8"));
11818
12012
  } catch {
11819
12013
  return null;
11820
12014
  }
@@ -11905,7 +12099,7 @@ var init_detector_install = __esm({
11905
12099
 
11906
12100
  // ../engine/dist/core/secret-scanner.js
11907
12101
  import { spawnSync } from "child_process";
11908
- import { existsSync as existsSync18, unlinkSync, readFileSync as readFileSync12, mkdtempSync as mkdtempSync2, rmSync as rmSync3 } from "fs";
12102
+ import { existsSync as existsSync18, unlinkSync, readFileSync as readFileSync11, mkdtempSync as mkdtempSync2, rmSync as rmSync3 } from "fs";
11909
12103
  import { tmpdir } from "os";
11910
12104
  import { join as join19 } from "path";
11911
12105
  function mask(s) {
@@ -11966,7 +12160,7 @@ function runGitleaks(binary, filePath) {
11966
12160
  return [];
11967
12161
  let parsed = [];
11968
12162
  try {
11969
- parsed = JSON.parse(readFileSync12(reportPath, "utf-8")) || [];
12163
+ parsed = JSON.parse(readFileSync11(reportPath, "utf-8")) || [];
11970
12164
  } catch {
11971
12165
  }
11972
12166
  try {
@@ -12030,7 +12224,7 @@ function runGitleaksDir(binary, dir) {
12030
12224
  }
12031
12225
  let parsed = [];
12032
12226
  try {
12033
- parsed = JSON.parse(readFileSync12(reportPath, "utf-8")) || [];
12227
+ parsed = JSON.parse(readFileSync11(reportPath, "utf-8")) || [];
12034
12228
  } catch {
12035
12229
  }
12036
12230
  try {
@@ -12121,7 +12315,7 @@ function scanFileForSecrets(filePath, opts = {}) {
12121
12315
  out.push({ detector: "trufflehog", ...f });
12122
12316
  if (opts.tenantRules && opts.tenantRules.length > 0) {
12123
12317
  try {
12124
- const text = readFileSync12(filePath, "utf-8");
12318
+ const text = readFileSync11(filePath, "utf-8");
12125
12319
  out.push(...scanTenantRules(text, opts.tenantRules));
12126
12320
  } catch {
12127
12321
  }
@@ -12246,10 +12440,10 @@ var init_types = __esm({
12246
12440
 
12247
12441
  // ../engine/dist/transcript/opencode.js
12248
12442
  async function parseOpenCodeSubagents(parentSessionId) {
12249
- const { existsSync: existsSync44 } = await import("fs");
12443
+ const { existsSync: existsSync45 } = await import("fs");
12250
12444
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
12251
12445
  const dbPath = opencodeBackend2.dbPath();
12252
- if (!existsSync44(dbPath))
12446
+ if (!existsSync45(dbPath))
12253
12447
  return [];
12254
12448
  const realParent = parentSessionId.replace("opencode_", "");
12255
12449
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -12290,10 +12484,10 @@ async function parseOpenCodeSubagents(parentSessionId) {
12290
12484
  }
12291
12485
  }
12292
12486
  async function parseOpenCodeTranscript(sessionId) {
12293
- const { existsSync: existsSync44 } = await import("fs");
12487
+ const { existsSync: existsSync45 } = await import("fs");
12294
12488
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
12295
12489
  const dbPath = opencodeBackend2.dbPath();
12296
- if (!existsSync44(dbPath))
12490
+ if (!existsSync45(dbPath))
12297
12491
  return [];
12298
12492
  const realId = sessionId.replace("opencode_", "");
12299
12493
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -12486,7 +12680,7 @@ var init_transcript = __esm({
12486
12680
  // ../engine/dist/core/project-resolver.js
12487
12681
  import { execSync } from "child_process";
12488
12682
  import { createHash as createHash7 } from "crypto";
12489
- import { existsSync as existsSync19, readFileSync as readFileSync13, realpathSync as realpathSync3, readdirSync as readdirSync12, statSync as statSync16 } from "fs";
12683
+ import { existsSync as existsSync19, readFileSync as readFileSync12, realpathSync as realpathSync3, readdirSync as readdirSync12, statSync as statSync16 } from "fs";
12490
12684
  import { basename as basename12, dirname as dirname12, isAbsolute, join as join20, resolve, sep } from "path";
12491
12685
  function getProjectsConfigPath() {
12492
12686
  return join20(getDataDir(), "projects.json");
@@ -12504,7 +12698,7 @@ function loadProjectsConfig() {
12504
12698
  if (cachedConfig && cachedConfig.mtime === mtime)
12505
12699
  return cachedConfig.cfg;
12506
12700
  try {
12507
- const raw = readFileSync13(path, "utf-8");
12701
+ const raw = readFileSync12(path, "utf-8");
12508
12702
  const cfg = JSON.parse(raw);
12509
12703
  cachedConfig = { mtime, cfg };
12510
12704
  return cfg;
@@ -12572,8 +12766,8 @@ function resolveBotWorktree(realPath) {
12572
12766
  };
12573
12767
  }
12574
12768
  function matchDeclaredProject(realPath, projects) {
12575
- const candidates = [...projects].sort((a, b) => b.root.length - a.root.length);
12576
- for (const proj of candidates) {
12769
+ const candidates2 = [...projects].sort((a, b) => b.root.length - a.root.length);
12770
+ for (const proj of candidates2) {
12577
12771
  const root = safeRealpath(proj.root);
12578
12772
  if (!isInside(realPath, root))
12579
12773
  continue;
@@ -13607,7 +13801,7 @@ var init_utils = __esm({
13607
13801
  });
13608
13802
 
13609
13803
  // ../engine/dist/parsers/plan-source.js
13610
- import { existsSync as existsSync21, readdirSync as readdirSync14, readFileSync as readFileSync14, statSync as statSync18 } from "fs";
13804
+ import { existsSync as existsSync21, readdirSync as readdirSync14, readFileSync as readFileSync13, statSync as statSync18 } from "fs";
13611
13805
  import { homedir as homedir7 } from "os";
13612
13806
  import { join as join22, basename as basename13 } from "path";
13613
13807
  import { createHash as createHash8 } from "crypto";
@@ -13649,6 +13843,7 @@ var init_plan_source = __esm({
13649
13843
  init_claude2();
13650
13844
  init_agy();
13651
13845
  init_settings();
13846
+ init_project_dir_name();
13652
13847
  MAX_CHUNK_CHARS = 2e3;
13653
13848
  UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13654
13849
  PlanSource = class {
@@ -13673,7 +13868,7 @@ var init_plan_source = __esm({
13673
13868
  for (const entry of entries) {
13674
13869
  if (!entry.isDirectory() || entry.name.startsWith("."))
13675
13870
  continue;
13676
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
13871
+ const projectPath = decodeProjectDirName(entry.name);
13677
13872
  if (existsSync21(projectPath)) {
13678
13873
  this._knownProjectDirs.push(projectPath);
13679
13874
  }
@@ -13711,7 +13906,7 @@ var init_plan_source = __esm({
13711
13906
  const stat3 = statSync18(filePath);
13712
13907
  const planName = basename13(file, ".md");
13713
13908
  const isAgentPlan = planName.includes("-agent-");
13714
- const content = readFileSync14(filePath, "utf-8");
13909
+ const content = readFileSync13(filePath, "utf-8");
13715
13910
  const fm = parseFrontmatter(content);
13716
13911
  const body = fm.body;
13717
13912
  const firstLine = body.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || planName;
@@ -13753,7 +13948,7 @@ var init_plan_source = __esm({
13753
13948
  const filePath = join22(plansDir, file);
13754
13949
  try {
13755
13950
  const stat3 = statSync18(filePath);
13756
- const content = readFileSync14(filePath, "utf-8");
13951
+ const content = readFileSync13(filePath, "utf-8");
13757
13952
  const firstLine = content.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || file;
13758
13953
  const planName = `${opencodeBackend.idPrefix}plan_${basename13(file, ".md")}`;
13759
13954
  const projectPath = this.extractProjectPath(content);
@@ -13783,7 +13978,7 @@ var init_plan_source = __esm({
13783
13978
  const projectsPath = geminiBackend.projectsJson();
13784
13979
  if (existsSync21(projectsPath)) {
13785
13980
  try {
13786
- const data = JSON.parse(readFileSync14(projectsPath, "utf-8"));
13981
+ const data = JSON.parse(readFileSync13(projectsPath, "utf-8"));
13787
13982
  for (const path of Object.keys(data.projects || {})) {
13788
13983
  projMap.set(createHash8("sha256").update(path).digest("hex"), path);
13789
13984
  }
@@ -13819,7 +14014,7 @@ var init_plan_source = __esm({
13819
14014
  const filePath = join22(plansDir, file);
13820
14015
  try {
13821
14016
  const stat3 = statSync18(filePath);
13822
- const content = readFileSync14(filePath, "utf-8");
14017
+ const content = readFileSync13(filePath, "utf-8");
13823
14018
  const firstLine = content.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || file;
13824
14019
  const planName = `${geminiBackend.idPrefix}plan_${sess}_${basename13(file, ".md")}`;
13825
14020
  yield {
@@ -13898,7 +14093,7 @@ var init_plan_source = __esm({
13898
14093
  async parse(item) {
13899
14094
  if (!existsSync21(item.filePath))
13900
14095
  return [];
13901
- const content = readFileSync14(item.filePath, "utf-8");
14096
+ const content = readFileSync13(item.filePath, "utf-8");
13902
14097
  const chunks = [];
13903
14098
  const { body } = parseFrontmatter(content);
13904
14099
  const sections = splitByHeaders(body);
@@ -14042,7 +14237,7 @@ var init_plan_source = __esm({
14042
14237
  const filePath = join22(sessionPath, file);
14043
14238
  try {
14044
14239
  const stat3 = statSync18(filePath);
14045
- const content = readFileSync14(filePath, "utf-8");
14240
+ const content = readFileSync13(filePath, "utf-8");
14046
14241
  const firstLine = content.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || file;
14047
14242
  const sessionLoc = agyBackend.findSession(rawId);
14048
14243
  const projectPath = sessionLoc?.projectPath || this.extractProjectPath(content);
@@ -14071,7 +14266,7 @@ var init_plan_source = __esm({
14071
14266
  });
14072
14267
 
14073
14268
  // ../engine/dist/parsers/task-source.js
14074
- import { existsSync as existsSync22, readdirSync as readdirSync15, readFileSync as readFileSync15, statSync as statSync19 } from "fs";
14269
+ import { existsSync as existsSync22, readdirSync as readdirSync15, readFileSync as readFileSync14, statSync as statSync19 } from "fs";
14075
14270
  import { join as join23 } from "path";
14076
14271
  var MAX_CHUNK_CHARS2, TaskSource;
14077
14272
  var init_task_source = __esm({
@@ -14121,7 +14316,7 @@ var init_task_source = __esm({
14121
14316
  continue;
14122
14317
  let parsed;
14123
14318
  try {
14124
- const raw = readFileSync15(filePath, "utf-8");
14319
+ const raw = readFileSync14(filePath, "utf-8");
14125
14320
  parsed = JSON.parse(raw);
14126
14321
  if (!Array.isArray(parsed) || parsed.length === 0)
14127
14322
  continue;
@@ -14259,7 +14454,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
14259
14454
  return [];
14260
14455
  let parsed;
14261
14456
  try {
14262
- parsed = JSON.parse(readFileSync15(item.filePath, "utf-8"));
14457
+ parsed = JSON.parse(readFileSync14(item.filePath, "utf-8"));
14263
14458
  } catch {
14264
14459
  return [];
14265
14460
  }
@@ -14291,7 +14486,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
14291
14486
  });
14292
14487
  for (const file of sorted) {
14293
14488
  try {
14294
- const content = readFileSync15(join23(taskDir, file), "utf-8");
14489
+ const content = readFileSync14(join23(taskDir, file), "utf-8");
14295
14490
  const task = JSON.parse(content);
14296
14491
  if (task.subject) {
14297
14492
  tasks.push(task);
@@ -14307,7 +14502,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
14307
14502
  });
14308
14503
 
14309
14504
  // ../engine/dist/parsers/claude-md-source.js
14310
- import { existsSync as existsSync23, readFileSync as readFileSync16, readdirSync as readdirSync16, statSync as statSync20 } from "fs";
14505
+ import { existsSync as existsSync23, readFileSync as readFileSync15, readdirSync as readdirSync16, statSync as statSync20 } from "fs";
14311
14506
  import { homedir as homedir8 } from "os";
14312
14507
  import { join as join24, basename as basename14, dirname as dirname13 } from "path";
14313
14508
  import { createHash as createHash9 } from "crypto";
@@ -14328,7 +14523,7 @@ function discoverProjectDirs(claudeDir) {
14328
14523
  for (const entry of entries) {
14329
14524
  if (!entry.isDirectory() || entry.name.startsWith("."))
14330
14525
  continue;
14331
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
14526
+ const projectPath = decodeProjectDirName(entry.name);
14332
14527
  addProject(projectPath);
14333
14528
  }
14334
14529
  } catch {
@@ -14351,7 +14546,7 @@ function discoverProjectDirs(claudeDir) {
14351
14546
  const gemProjects = geminiBackend.projectsJson();
14352
14547
  if (existsSync23(gemProjects)) {
14353
14548
  try {
14354
- const data = JSON.parse(readFileSync16(gemProjects, "utf-8"));
14549
+ const data = JSON.parse(readFileSync15(gemProjects, "utf-8"));
14355
14550
  for (const path of Object.keys(data.projects || {}))
14356
14551
  addProject(path);
14357
14552
  } catch {
@@ -14385,6 +14580,7 @@ var init_claude_md_source = __esm({
14385
14580
  init_gemini2();
14386
14581
  init_settings();
14387
14582
  init_opencode();
14583
+ init_project_dir_name();
14388
14584
  MAX_CHUNK_CHARS3 = 2e3;
14389
14585
  NOTE_FILES = [
14390
14586
  { filename: "CLAUDE.md", tool: "claude" },
@@ -14465,7 +14661,7 @@ var init_claude_md_source = __esm({
14465
14661
  async parse(item) {
14466
14662
  if (!existsSync23(item.filePath))
14467
14663
  return [];
14468
- const content = readFileSync16(item.filePath, "utf-8");
14664
+ const content = readFileSync15(item.filePath, "utf-8");
14469
14665
  const chunks = [];
14470
14666
  const sections = splitByHeaders(content);
14471
14667
  for (let i = 0; i < sections.length; i++) {
@@ -14507,7 +14703,7 @@ var init_claude_md_source = __esm({
14507
14703
  const projectDir = dirname13(filePath);
14508
14704
  const projectName = basename14(projectDir);
14509
14705
  const projectPath = projectDir;
14510
- const content = readFileSync16(filePath, "utf-8");
14706
+ const content = readFileSync15(filePath, "utf-8");
14511
14707
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"))?.trim()?.slice(0, 200) || `${filename} for ${projectName}`;
14512
14708
  const id = tool === "claude" ? projectName : `${projectName}_${tool}`;
14513
14709
  return {
@@ -14532,7 +14728,7 @@ var init_claude_md_source = __esm({
14532
14728
  });
14533
14729
 
14534
14730
  // ../engine/dist/parsers/agent-memory-source.js
14535
- import { existsSync as existsSync24, readFileSync as readFileSync17, readdirSync as readdirSync17, statSync as statSync21 } from "fs";
14731
+ import { existsSync as existsSync24, readFileSync as readFileSync16, readdirSync as readdirSync17, statSync as statSync21 } from "fs";
14536
14732
  import { homedir as homedir9 } from "os";
14537
14733
  import { join as join25, basename as basename15 } from "path";
14538
14734
  function parseFrontmatter2(content) {
@@ -14560,7 +14756,7 @@ function parseFrontmatter2(content) {
14560
14756
  return { fm, body };
14561
14757
  }
14562
14758
  function projectHashToPath(hashDir) {
14563
- return hashDir.replace(/^-/, "/").replace(/-/g, "/");
14759
+ return decodeProjectDirName(hashDir);
14564
14760
  }
14565
14761
  function discoverMemoryDirs() {
14566
14762
  const out = [];
@@ -14602,6 +14798,7 @@ var init_agent_memory_source = __esm({
14602
14798
  init_utils();
14603
14799
  init_claude2();
14604
14800
  init_settings();
14801
+ init_project_dir_name();
14605
14802
  MAX_CHUNK_CHARS4 = 2e3;
14606
14803
  MAX_CHUNKS = 12;
14607
14804
  AgentMemorySource = class {
@@ -14632,7 +14829,7 @@ var init_agent_memory_source = __esm({
14632
14829
  async parse(item) {
14633
14830
  if (!existsSync24(item.filePath))
14634
14831
  return [];
14635
- const content = readFileSync17(item.filePath, "utf-8");
14832
+ const content = readFileSync16(item.filePath, "utf-8");
14636
14833
  const { body } = parseFrontmatter2(content);
14637
14834
  const chunks = [];
14638
14835
  const sections = splitByHeaders(body);
@@ -14685,7 +14882,7 @@ var init_agent_memory_source = __esm({
14685
14882
  fileToItem(filePath, filename, projectPath, _projectHash) {
14686
14883
  try {
14687
14884
  const stat3 = statSync21(filePath);
14688
- const content = readFileSync17(filePath, "utf-8");
14885
+ const content = readFileSync16(filePath, "utf-8");
14689
14886
  const { fm } = parseFrontmatter2(content);
14690
14887
  const stem = filename.replace(/\.md$/, "");
14691
14888
  const idBase = fm.name || stem;
@@ -14900,7 +15097,7 @@ var init_history_source = __esm({
14900
15097
  });
14901
15098
 
14902
15099
  // ../engine/dist/parsers/paste-source.js
14903
- import { existsSync as existsSync26, readdirSync as readdirSync19, readFileSync as readFileSync18, statSync as statSync23 } from "fs";
15100
+ import { existsSync as existsSync26, readdirSync as readdirSync19, readFileSync as readFileSync17, statSync as statSync23 } from "fs";
14904
15101
  import { homedir as homedir11 } from "os";
14905
15102
  import { join as join27, basename as basename16 } from "path";
14906
15103
  var MAX_CHUNK_CHARS6, MAX_FILE_SIZE, PasteSource;
@@ -14956,7 +15153,7 @@ var init_paste_source = __esm({
14956
15153
  if (stat3.size < 10)
14957
15154
  continue;
14958
15155
  const hash = basename16(file, ".txt");
14959
- const content = readFileSync18(filePath, "utf-8");
15156
+ const content = readFileSync17(filePath, "utf-8");
14960
15157
  const firstLine = content.split("\n").find((l) => l.trim().length > 5)?.trim()?.slice(0, 100) || `Paste ${hash.slice(0, 8)}`;
14961
15158
  yield {
14962
15159
  id: hash,
@@ -14977,7 +15174,7 @@ var init_paste_source = __esm({
14977
15174
  async parse(item) {
14978
15175
  if (!existsSync26(item.filePath))
14979
15176
  return [];
14980
- const content = readFileSync18(item.filePath, "utf-8");
15177
+ const content = readFileSync17(item.filePath, "utf-8");
14981
15178
  if (content.trim().length < 20)
14982
15179
  return [];
14983
15180
  return [{
@@ -15000,7 +15197,7 @@ var init_paste_source = __esm({
15000
15197
  });
15001
15198
 
15002
15199
  // ../engine/dist/parsers/gemini-source.js
15003
- import { existsSync as existsSync27, readdirSync as readdirSync20, readFileSync as readFileSync19, statSync as statSync24 } from "fs";
15200
+ import { existsSync as existsSync27, readdirSync as readdirSync20, readFileSync as readFileSync18, statSync as statSync24 } from "fs";
15004
15201
  import { join as join28, basename as basename17 } from "path";
15005
15202
  import { createHash as createHash10 } from "crypto";
15006
15203
  function loadGeminiProjectMap2() {
@@ -15009,7 +15206,7 @@ function loadGeminiProjectMap2() {
15009
15206
  if (!existsSync27(projectsPath))
15010
15207
  return map;
15011
15208
  try {
15012
- const data = JSON.parse(readFileSync19(projectsPath, "utf-8"));
15209
+ const data = JSON.parse(readFileSync18(projectsPath, "utf-8"));
15013
15210
  const projects = data.projects || {};
15014
15211
  for (const [path] of Object.entries(projects)) {
15015
15212
  const hash = createHash10("sha256").update(path).digest("hex");
@@ -15054,7 +15251,7 @@ var init_gemini_source = __esm({
15054
15251
  const filePath = join28(chatsDir, file);
15055
15252
  try {
15056
15253
  const stat3 = statSync24(filePath);
15057
- const content = JSON.parse(readFileSync19(filePath, "utf-8"));
15254
+ const content = JSON.parse(readFileSync18(filePath, "utf-8"));
15058
15255
  const sessionId = content.sessionId || basename17(file, ".json");
15059
15256
  if (seen.has(sessionId))
15060
15257
  continue;
@@ -15135,7 +15332,7 @@ var init_gemini_source = __esm({
15135
15332
  async parse(item) {
15136
15333
  if (!existsSync27(item.filePath))
15137
15334
  return [];
15138
- const content = JSON.parse(readFileSync19(item.filePath, "utf-8"));
15335
+ const content = JSON.parse(readFileSync18(item.filePath, "utf-8"));
15139
15336
  const messages = content.messages || [];
15140
15337
  const chunks = [];
15141
15338
  const getText = (m) => {
@@ -15183,7 +15380,7 @@ var init_gemini_source = __esm({
15183
15380
  });
15184
15381
 
15185
15382
  // ../engine/dist/parsers/gemini-brain-source.js
15186
- import { existsSync as existsSync28, readdirSync as readdirSync21, readFileSync as readFileSync20, statSync as statSync25 } from "fs";
15383
+ import { existsSync as existsSync28, readdirSync as readdirSync21, readFileSync as readFileSync19, statSync as statSync25 } from "fs";
15187
15384
  import { join as join29 } from "path";
15188
15385
  var MAX_CHUNK_CHARS8, GeminiBrainSource;
15189
15386
  var init_gemini_brain_source = __esm({
@@ -15216,14 +15413,14 @@ var init_gemini_brain_source = __esm({
15216
15413
  const filePath = join29(sessionDir, file);
15217
15414
  try {
15218
15415
  const stat3 = statSync25(filePath);
15219
- const content = readFileSync20(filePath, "utf-8");
15416
+ const content = readFileSync19(filePath, "utf-8");
15220
15417
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"))?.trim() || "";
15221
15418
  const heading = content.split("\n").find((l) => l.startsWith("#"))?.replace(/^#+\s*/, "").trim() || file;
15222
15419
  const metaPath = `${filePath}.metadata.json`;
15223
15420
  let metaSummary = "";
15224
15421
  if (existsSync28(metaPath)) {
15225
15422
  try {
15226
- const meta = JSON.parse(readFileSync20(metaPath, "utf-8"));
15423
+ const meta = JSON.parse(readFileSync19(metaPath, "utf-8"));
15227
15424
  metaSummary = meta.summary || "";
15228
15425
  } catch {
15229
15426
  }
@@ -15254,7 +15451,7 @@ var init_gemini_brain_source = __esm({
15254
15451
  async parse(item) {
15255
15452
  if (!existsSync28(item.filePath))
15256
15453
  return [];
15257
- const content = readFileSync20(item.filePath, "utf-8");
15454
+ const content = readFileSync19(item.filePath, "utf-8");
15258
15455
  const chunks = [];
15259
15456
  const sections = splitByHeaders(content);
15260
15457
  for (let i = 0; i < sections.length; i++) {
@@ -15597,7 +15794,7 @@ var init_opencode_todo_source = __esm({
15597
15794
  });
15598
15795
 
15599
15796
  // ../engine/dist/parsers/codex-session-source.js
15600
- import { existsSync as existsSync31, readdirSync as readdirSync22, readFileSync as readFileSync21, statSync as statSync26 } from "fs";
15797
+ import { existsSync as existsSync31, readdirSync as readdirSync22, readFileSync as readFileSync20, statSync as statSync26 } from "fs";
15601
15798
  import { join as join30, basename as basename18 } from "path";
15602
15799
  function getCodexSessionsDir() {
15603
15800
  return codexBackend.sessionsDir();
@@ -15625,7 +15822,7 @@ function extractReasoningText(event) {
15625
15822
  return "";
15626
15823
  }
15627
15824
  function readJsonlLines(filePath) {
15628
- const text = readFileSync21(filePath, "utf-8");
15825
+ const text = readFileSync20(filePath, "utf-8");
15629
15826
  const lines = text.split("\n");
15630
15827
  const events = [];
15631
15828
  for (const line of lines) {
@@ -15660,7 +15857,7 @@ var init_codex_session_source = __esm({
15660
15857
  for (const filePath of jsonlFiles) {
15661
15858
  try {
15662
15859
  const stat3 = statSync26(filePath);
15663
- const lines = readFileSync21(filePath, "utf-8").split("\n");
15860
+ const lines = readFileSync20(filePath, "utf-8").split("\n");
15664
15861
  const firstLine = lines.find((l) => l.trim());
15665
15862
  if (!firstLine)
15666
15863
  continue;
@@ -15833,7 +16030,7 @@ var init_codex_session_source = __esm({
15833
16030
  });
15834
16031
 
15835
16032
  // ../engine/dist/parsers/diary-source.js
15836
- import { existsSync as existsSync32, mkdirSync as mkdirSync6, readdirSync as readdirSync23, readFileSync as readFileSync22, writeFileSync as writeFileSync4, statSync as statSync27 } from "fs";
16033
+ import { existsSync as existsSync32, mkdirSync as mkdirSync6, readdirSync as readdirSync23, readFileSync as readFileSync21, writeFileSync as writeFileSync4, statSync as statSync27 } from "fs";
15837
16034
  import { join as join31, basename as basename19 } from "path";
15838
16035
  var MAX_CHUNK_CHARS11, DiarySource;
15839
16036
  var init_diary_source = __esm({
@@ -15859,7 +16056,7 @@ var init_diary_source = __esm({
15859
16056
  const filePath = join31(agentPath, file);
15860
16057
  try {
15861
16058
  const stat3 = statSync27(filePath);
15862
- const raw = readFileSync22(filePath, "utf-8");
16059
+ const raw = readFileSync21(filePath, "utf-8");
15863
16060
  const entry = JSON.parse(raw);
15864
16061
  const entryId = basename19(file, ".json");
15865
16062
  const title = `[${entry.agent}] ${entry.topic}: ${entry.content.slice(0, 80)}`;
@@ -15886,7 +16083,7 @@ var init_diary_source = __esm({
15886
16083
  async parse(item) {
15887
16084
  if (!existsSync32(item.filePath))
15888
16085
  return [];
15889
- const raw = readFileSync22(item.filePath, "utf-8");
16086
+ const raw = readFileSync21(item.filePath, "utf-8");
15890
16087
  const entry = JSON.parse(raw);
15891
16088
  if (!entry.content || entry.content.trim().length < 10)
15892
16089
  return [];
@@ -15949,7 +16146,7 @@ var init_diary_source = __esm({
15949
16146
  const entries = [];
15950
16147
  for (const file of files) {
15951
16148
  try {
15952
- const raw = readFileSync22(join31(agentDir, file), "utf-8");
16149
+ const raw = readFileSync21(join31(agentDir, file), "utf-8");
15953
16150
  entries.push(JSON.parse(raw));
15954
16151
  } catch {
15955
16152
  continue;
@@ -15962,7 +16159,7 @@ var init_diary_source = __esm({
15962
16159
  });
15963
16160
 
15964
16161
  // ../engine/dist/parsers/skills-source.js
15965
- import { existsSync as existsSync33, readdirSync as readdirSync24, readFileSync as readFileSync23, statSync as statSync28 } from "fs";
16162
+ import { existsSync as existsSync33, readdirSync as readdirSync24, readFileSync as readFileSync22, statSync as statSync28 } from "fs";
15966
16163
  import { homedir as homedir12 } from "os";
15967
16164
  import { join as join32 } from "path";
15968
16165
  function resolveRoots() {
@@ -16055,7 +16252,7 @@ var init_skills_source = __esm({
16055
16252
  continue;
16056
16253
  let content;
16057
16254
  try {
16058
- content = readFileSync23(skillMd, "utf-8");
16255
+ content = readFileSync22(skillMd, "utf-8");
16059
16256
  } catch {
16060
16257
  continue;
16061
16258
  }
@@ -16115,7 +16312,7 @@ var init_skills_source = __esm({
16115
16312
  async parse(item) {
16116
16313
  if (!existsSync33(item.filePath))
16117
16314
  return [];
16118
- const content = readFileSync23(item.filePath, "utf-8");
16315
+ const content = readFileSync22(item.filePath, "utf-8");
16119
16316
  if (!content.trim())
16120
16317
  return [];
16121
16318
  return [{
@@ -16138,14 +16335,14 @@ var init_skills_source = __esm({
16138
16335
  });
16139
16336
 
16140
16337
  // ../engine/dist/parsers/mcps-source.js
16141
- import { existsSync as existsSync34, readdirSync as readdirSync25, readFileSync as readFileSync24, statSync as statSync29 } from "fs";
16338
+ import { existsSync as existsSync34, readdirSync as readdirSync25, readFileSync as readFileSync23, statSync as statSync29 } from "fs";
16142
16339
  import { homedir as homedir13 } from "os";
16143
16340
  import { join as join33 } from "path";
16144
16341
  function readJson(path) {
16145
16342
  if (!existsSync34(path))
16146
16343
  return null;
16147
16344
  try {
16148
- const raw = readFileSync24(path, "utf-8");
16345
+ const raw = readFileSync23(path, "utf-8");
16149
16346
  const stripped = path.endsWith(".jsonc") ? raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/,(\s*[}\]])/g, "$1") : raw;
16150
16347
  return JSON.parse(stripped);
16151
16348
  } catch {
@@ -16258,7 +16455,7 @@ var init_mcps_source = __esm({
16258
16455
  return;
16259
16456
  let content;
16260
16457
  try {
16261
- content = readFileSync24(path, "utf-8");
16458
+ content = readFileSync23(path, "utf-8");
16262
16459
  } catch {
16263
16460
  return;
16264
16461
  }
@@ -16410,7 +16607,7 @@ var init_toolkit_format = __esm({
16410
16607
  });
16411
16608
 
16412
16609
  // ../engine/dist/parsers/slash-commands-source.js
16413
- import { existsSync as existsSync35, readdirSync as readdirSync26, readFileSync as readFileSync25, statSync as statSync30 } from "fs";
16610
+ import { existsSync as existsSync35, readdirSync as readdirSync26, readFileSync as readFileSync24, statSync as statSync30 } from "fs";
16414
16611
  import { join as join34, basename as basename20 } from "path";
16415
16612
  function parseCommand(content, fallbackName, format) {
16416
16613
  if (format === "toml") {
@@ -16438,6 +16635,7 @@ var init_slash_commands_source = __esm({
16438
16635
  init_codex2();
16439
16636
  init_settings();
16440
16637
  init_toolkit_format();
16638
+ init_project_dir_name();
16441
16639
  MAX_CHUNK_CHARS13 = 2e3;
16442
16640
  SlashCommandsSource = class {
16443
16641
  sourceType = "command";
@@ -16451,7 +16649,7 @@ var init_slash_commands_source = __esm({
16451
16649
  for (const entry of readdirSync26(projectsRoot, { withFileTypes: true })) {
16452
16650
  if (!entry.isDirectory() || entry.name.startsWith("."))
16453
16651
  continue;
16454
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
16652
+ const projectPath = decodeProjectDirName(entry.name);
16455
16653
  const cmdDir = join34(projectPath, ".claude", "commands");
16456
16654
  if (existsSync35(cmdDir))
16457
16655
  roots.push({ path: cmdDir, tool: "claude", scope: "project", projectPath, format: "md" });
@@ -16484,7 +16682,7 @@ var init_slash_commands_source = __esm({
16484
16682
  const filePath = join34(root.path, file);
16485
16683
  try {
16486
16684
  const stat3 = statSync30(filePath);
16487
- const content = readFileSync25(filePath, "utf-8");
16685
+ const content = readFileSync24(filePath, "utf-8");
16488
16686
  const parsed = parseCommand(content, basename20(file, ext), root.format);
16489
16687
  yield {
16490
16688
  id: `${root.tool}_cmd_${root.scope}_${parsed.name}`,
@@ -16510,7 +16708,7 @@ var init_slash_commands_source = __esm({
16510
16708
  async parse(item) {
16511
16709
  if (!existsSync35(item.filePath))
16512
16710
  return [];
16513
- const content = readFileSync25(item.filePath, "utf-8");
16711
+ const content = readFileSync24(item.filePath, "utf-8");
16514
16712
  if (!content.trim())
16515
16713
  return [];
16516
16714
  return [{
@@ -16533,7 +16731,7 @@ var init_slash_commands_source = __esm({
16533
16731
  });
16534
16732
 
16535
16733
  // ../engine/dist/parsers/subagents-source.js
16536
- import { existsSync as existsSync36, readdirSync as readdirSync27, readFileSync as readFileSync26, statSync as statSync31 } from "fs";
16734
+ import { existsSync as existsSync36, readdirSync as readdirSync27, readFileSync as readFileSync25, statSync as statSync31 } from "fs";
16537
16735
  import { join as join35, basename as basename21 } from "path";
16538
16736
  function parseAgent(content, fallbackName, format) {
16539
16737
  if (format === "toml") {
@@ -16563,6 +16761,7 @@ var init_subagents_source = __esm({
16563
16761
  init_codex2();
16564
16762
  init_settings();
16565
16763
  init_toolkit_format();
16764
+ init_project_dir_name();
16566
16765
  MAX_CHUNK_CHARS14 = 2e3;
16567
16766
  SubagentsSource = class {
16568
16767
  sourceType = "agent";
@@ -16576,7 +16775,7 @@ var init_subagents_source = __esm({
16576
16775
  for (const entry of readdirSync27(projectsRoot, { withFileTypes: true })) {
16577
16776
  if (!entry.isDirectory() || entry.name.startsWith("."))
16578
16777
  continue;
16579
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
16778
+ const projectPath = decodeProjectDirName(entry.name);
16580
16779
  const agentDir = join35(projectPath, ".claude", "agents");
16581
16780
  if (existsSync36(agentDir))
16582
16781
  roots.push({ path: agentDir, tool: "claude", scope: "project", projectPath, format: "md" });
@@ -16609,7 +16808,7 @@ var init_subagents_source = __esm({
16609
16808
  const filePath = join35(root.path, file);
16610
16809
  try {
16611
16810
  const stat3 = statSync31(filePath);
16612
- const content = readFileSync26(filePath, "utf-8");
16811
+ const content = readFileSync25(filePath, "utf-8");
16613
16812
  const parsed = parseAgent(content, basename21(file, ext), root.format);
16614
16813
  yield {
16615
16814
  id: `${root.tool}_agent_${root.scope}_${parsed.name}`,
@@ -16636,7 +16835,7 @@ var init_subagents_source = __esm({
16636
16835
  async parse(item) {
16637
16836
  if (!existsSync36(item.filePath))
16638
16837
  return [];
16639
- const content = readFileSync26(item.filePath, "utf-8");
16838
+ const content = readFileSync25(item.filePath, "utf-8");
16640
16839
  if (!content.trim())
16641
16840
  return [];
16642
16841
  return [{
@@ -16659,13 +16858,13 @@ var init_subagents_source = __esm({
16659
16858
  });
16660
16859
 
16661
16860
  // ../engine/dist/parsers/hooks-source.js
16662
- import { existsSync as existsSync37, readFileSync as readFileSync27, readdirSync as readdirSync28, statSync as statSync32 } from "fs";
16861
+ import { existsSync as existsSync37, readFileSync as readFileSync26, readdirSync as readdirSync28, statSync as statSync32 } from "fs";
16663
16862
  import { join as join36 } from "path";
16664
16863
  function readJson2(path) {
16665
16864
  if (!existsSync37(path))
16666
16865
  return null;
16667
16866
  try {
16668
- return JSON.parse(readFileSync27(path, "utf-8"));
16867
+ return JSON.parse(readFileSync26(path, "utf-8"));
16669
16868
  } catch {
16670
16869
  return null;
16671
16870
  }
@@ -16676,6 +16875,7 @@ var init_hooks_source = __esm({
16676
16875
  "use strict";
16677
16876
  init_claude2();
16678
16877
  init_settings();
16878
+ init_project_dir_name();
16679
16879
  HooksSource = class {
16680
16880
  sourceType = "hook";
16681
16881
  async *discover() {
@@ -16688,7 +16888,7 @@ var init_hooks_source = __esm({
16688
16888
  for (const entry of readdirSync28(projectsRoot, { withFileTypes: true })) {
16689
16889
  if (!entry.isDirectory() || entry.name.startsWith("."))
16690
16890
  continue;
16691
- const projectPath = entry.name.replace(/^-/, "/").replace(/-/g, "/");
16891
+ const projectPath = decodeProjectDirName(entry.name);
16692
16892
  const settingsPath2 = join36(projectPath, ".claude", "settings.json");
16693
16893
  if (existsSync37(settingsPath2)) {
16694
16894
  yield* this.fromSettings(settingsPath2, "project", projectPath);
@@ -16781,14 +16981,14 @@ var init_hooks_source = __esm({
16781
16981
  });
16782
16982
 
16783
16983
  // ../engine/dist/parsers/plugins-source.js
16784
- import { existsSync as existsSync38, readFileSync as readFileSync28, readdirSync as readdirSync29, statSync as statSync33 } from "fs";
16984
+ import { existsSync as existsSync38, readFileSync as readFileSync27, readdirSync as readdirSync29, statSync as statSync33 } from "fs";
16785
16985
  import { homedir as homedir14 } from "os";
16786
16986
  import { join as join37 } from "path";
16787
16987
  function readJson3(path) {
16788
16988
  if (!existsSync38(path))
16789
16989
  return null;
16790
16990
  try {
16791
- return JSON.parse(readFileSync28(path, "utf-8"));
16991
+ return JSON.parse(readFileSync27(path, "utf-8"));
16792
16992
  } catch {
16793
16993
  return null;
16794
16994
  }
@@ -17045,7 +17245,7 @@ var init_extractor_version = __esm({
17045
17245
  });
17046
17246
 
17047
17247
  // src/sync-ledger.ts
17048
- import { readFileSync as readFileSync29, writeFileSync as writeFileSync5, renameSync as renameSync4, mkdirSync as mkdirSync7 } from "node:fs";
17248
+ import { readFileSync as readFileSync28, writeFileSync as writeFileSync5, renameSync as renameSync4, mkdirSync as mkdirSync7 } from "node:fs";
17049
17249
  import { join as join38, dirname as dirname14 } from "node:path";
17050
17250
  function toRow(entry) {
17051
17251
  if (entry === void 0) return void 0;
@@ -17054,24 +17254,77 @@ function toRow(entry) {
17054
17254
  function load() {
17055
17255
  if (cache3) return cache3;
17056
17256
  try {
17057
- const parsed = JSON.parse(readFileSync29(ledgerPath(), "utf-8"));
17257
+ const parsed = JSON.parse(readFileSync28(ledgerPath(), "utf-8"));
17058
17258
  cache3 = parsed && typeof parsed === "object" ? parsed : {};
17059
17259
  } catch {
17060
17260
  cache3 = {};
17061
17261
  }
17062
17262
  return cache3;
17063
17263
  }
17064
- function persist(data) {
17264
+ function persistNow(data) {
17065
17265
  const path = ledgerPath();
17066
17266
  mkdirSync7(dirname14(path), { recursive: true });
17067
17267
  const tmp = `${path}.tmp`;
17068
17268
  writeFileSync5(tmp, JSON.stringify(data));
17069
17269
  renameSync4(tmp, path);
17270
+ pendingWrite = false;
17271
+ }
17272
+ function persist(data) {
17273
+ pendingWrite = true;
17274
+ if (flushTimer) return;
17275
+ flushTimer = setTimeout(() => {
17276
+ flushTimer = null;
17277
+ if (pendingWrite) {
17278
+ try {
17279
+ persistNow(data);
17280
+ } catch {
17281
+ }
17282
+ }
17283
+ }, LEDGER_FLUSH_MS);
17284
+ flushTimer.unref?.();
17285
+ }
17286
+ function pruneLedgerTargets(configuredServers) {
17287
+ const data = load();
17288
+ const keep = new Set(configuredServers);
17289
+ const orphans = {};
17290
+ for (const server2 of Object.keys(data)) {
17291
+ if (!keep.has(server2)) {
17292
+ orphans[server2] = data[server2];
17293
+ delete data[server2];
17294
+ }
17295
+ }
17296
+ const names = Object.keys(orphans);
17297
+ if (names.length === 0) return 0;
17298
+ const path = ledgerPath();
17299
+ const side = `${path}.orphans.json`;
17300
+ try {
17301
+ let prior = {};
17302
+ try {
17303
+ prior = JSON.parse(readFileSync28(side, "utf-8"));
17304
+ } catch {
17305
+ }
17306
+ writeFileSync5(side, JSON.stringify({ ...prior, ...orphans }));
17307
+ } catch {
17308
+ for (const n of names) data[n] = orphans[n];
17309
+ return 0;
17310
+ }
17311
+ const rows = names.reduce((n, s) => n + Object.keys(orphans[s]).length, 0);
17312
+ console.error(`[sync] ledger: parked ${rows} row(s) for ${names.length} server(s) no longer configured (${names.join(", ")}) in ${side}`);
17313
+ persistNow(data);
17314
+ return names.length;
17315
+ }
17316
+ function flushLedger() {
17317
+ if (flushTimer) {
17318
+ clearTimeout(flushTimer);
17319
+ flushTimer = null;
17320
+ }
17321
+ if (!pendingWrite || !cache3) return;
17322
+ persistNow(cache3);
17070
17323
  }
17071
17324
  function loadItemVersionsAll() {
17072
17325
  if (itemVersionsCache) return itemVersionsCache;
17073
17326
  try {
17074
- const parsed = JSON.parse(readFileSync29(itemVersionsPath(), "utf-8"));
17327
+ const parsed = JSON.parse(readFileSync28(itemVersionsPath(), "utf-8"));
17075
17328
  itemVersionsCache = parsed && typeof parsed === "object" ? parsed : {};
17076
17329
  } catch {
17077
17330
  itemVersionsCache = {};
@@ -17153,7 +17406,7 @@ function markFieldCoverage(server2, field, rows) {
17153
17406
  function loadReconcile() {
17154
17407
  if (reconcileCache) return reconcileCache;
17155
17408
  try {
17156
- const parsed = JSON.parse(readFileSync29(reconcilePath(), "utf-8"));
17409
+ const parsed = JSON.parse(readFileSync28(reconcilePath(), "utf-8"));
17157
17410
  reconcileCache = parsed && typeof parsed === "object" ? parsed : {};
17158
17411
  } catch {
17159
17412
  reconcileCache = {};
@@ -17201,7 +17454,7 @@ function syncMode(row, mtime, fileSize, extractorVersion, isAppendOnly) {
17201
17454
  if (row.m >= Math.floor(mtime)) return "skip";
17202
17455
  return "full";
17203
17456
  }
17204
- var ledgerPath, cache3, itemVersionsPath, itemVersionsCache, unackedCursorAttempts, reconcilePath, reconcileCache;
17457
+ var ledgerPath, cache3, LEDGER_FLUSH_MS, pendingWrite, flushTimer, itemVersionsPath, itemVersionsCache, unackedCursorAttempts, reconcilePath, reconcileCache;
17205
17458
  var init_sync_ledger = __esm({
17206
17459
  "src/sync-ledger.ts"() {
17207
17460
  "use strict";
@@ -17209,6 +17462,9 @@ var init_sync_ledger = __esm({
17209
17462
  init_extractor_version();
17210
17463
  ledgerPath = () => join38(getDataDir(), "sync-ledger.json");
17211
17464
  cache3 = null;
17465
+ LEDGER_FLUSH_MS = 2e3;
17466
+ pendingWrite = false;
17467
+ flushTimer = null;
17212
17468
  itemVersionsPath = () => join38(getDataDir(), "item-versions.json");
17213
17469
  itemVersionsCache = null;
17214
17470
  unackedCursorAttempts = 0;
@@ -17243,7 +17499,7 @@ var init_sync_fields = __esm({
17243
17499
  });
17244
17500
 
17245
17501
  // ../engine/dist/core/index-lock.js
17246
- import { existsSync as existsSync39, mkdirSync as mkdirSync8, openSync as openSync7, writeFileSync as writeFileSync6, readFileSync as readFileSync30, rmSync as rmSync4, statSync as statSync34, closeSync as closeSync7 } from "fs";
17502
+ import { existsSync as existsSync39, mkdirSync as mkdirSync8, openSync as openSync8, writeFileSync as writeFileSync6, readFileSync as readFileSync29, rmSync as rmSync4, statSync as statSync34, closeSync as closeSync8 } from "fs";
17247
17503
  import { dirname as dirname15, join as join39 } from "path";
17248
17504
  function acquireIndexLock(opts) {
17249
17505
  const lockPath = lockFilePath();
@@ -17255,7 +17511,7 @@ function acquireIndexLock(opts) {
17255
17511
  }
17256
17512
  let fd;
17257
17513
  try {
17258
- fd = openSync7(lockPath, "wx", 384);
17514
+ fd = openSync8(lockPath, "wx", 384);
17259
17515
  } catch (err) {
17260
17516
  if (err.code === "EEXIST")
17261
17517
  return null;
@@ -17265,7 +17521,7 @@ function acquireIndexLock(opts) {
17265
17521
  try {
17266
17522
  writeFileSync6(fd, JSON.stringify(holder));
17267
17523
  } finally {
17268
- closeSync7(fd);
17524
+ closeSync8(fd);
17269
17525
  }
17270
17526
  let released = false;
17271
17527
  return {
@@ -17275,7 +17531,7 @@ function acquireIndexLock(opts) {
17275
17531
  return;
17276
17532
  released = true;
17277
17533
  try {
17278
- const current = JSON.parse(readFileSync30(lockPath, "utf-8"));
17534
+ const current = JSON.parse(readFileSync29(lockPath, "utf-8"));
17279
17535
  if (current.pid !== holder.pid || current.startedAt !== holder.startedAt)
17280
17536
  return;
17281
17537
  } catch {
@@ -17297,7 +17553,7 @@ function stealStaleLock(lockPath, staleAfterMs) {
17297
17553
  const age = Date.now() - s.mtimeMs;
17298
17554
  let payload;
17299
17555
  try {
17300
- payload = JSON.parse(readFileSync30(lockPath, "utf-8"));
17556
+ payload = JSON.parse(readFileSync29(lockPath, "utf-8"));
17301
17557
  } catch {
17302
17558
  rmSync4(lockPath, { force: true });
17303
17559
  return true;
@@ -17335,7 +17591,10 @@ var init_index_lock = __esm({
17335
17591
  });
17336
17592
 
17337
17593
  // src/http.ts
17338
- import { readFileSync as readFileSync31 } from "node:fs";
17594
+ import { readFileSync as readFileSync30 } from "node:fs";
17595
+ function cliVersion() {
17596
+ return CLI_VERSION;
17597
+ }
17339
17598
  function clientHeaders() {
17340
17599
  return {
17341
17600
  "x-chat-recall-cli": CLI_VERSION,
@@ -17353,10 +17612,10 @@ var init_http = __esm({
17353
17612
  "use strict";
17354
17613
  CLI_VERSION = "0.0.0";
17355
17614
  try {
17356
- CLI_VERSION = JSON.parse(readFileSync31(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
17615
+ CLI_VERSION = JSON.parse(readFileSync30(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
17357
17616
  } catch {
17358
17617
  try {
17359
- if (true) CLI_VERSION = "0.5.5";
17618
+ if (true) CLI_VERSION = "0.5.6";
17360
17619
  } catch {
17361
17620
  }
17362
17621
  }
@@ -17480,6 +17739,7 @@ var sync_client_exports = {};
17480
17739
  __export(sync_client_exports, {
17481
17740
  BATCH_SCAN_MAX_BYTES: () => BATCH_SCAN_MAX_BYTES,
17482
17741
  FULL_BUILD_MAX_BYTES: () => FULL_BUILD_MAX_BYTES,
17742
+ _resetCapsCacheForTests: () => _resetCapsCacheForTests,
17483
17743
  _resetTenantSecurityConfigCache: () => _resetTenantSecurityConfigCache,
17484
17744
  _resetTenantSyncConfigCache: () => _resetTenantSyncConfigCache,
17485
17745
  batchScanExternal: () => batchScanExternal,
@@ -17497,7 +17757,7 @@ __export(sync_client_exports, {
17497
17757
  syncSessions: () => syncSessions
17498
17758
  });
17499
17759
  import { join as join41, dirname as dirname16, basename as basename22 } from "node:path";
17500
- import { readFileSync as readFileSync32, writeFileSync as writeFileSync8, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync4, rmSync as rmSync5, chmodSync as chmodSync4, unlinkSync as unlinkSync2, readdirSync as readdirSync30, statSync as statSync35 } from "node:fs";
17760
+ import { readFileSync as readFileSync31, writeFileSync as writeFileSync8, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync4, rmSync as rmSync5, chmodSync as chmodSync4, unlinkSync as unlinkSync2, readdirSync as readdirSync30, statSync as statSync35 } from "node:fs";
17501
17761
  import { tmpdir as tmpdir3, hostname } from "node:os";
17502
17762
  import { createHash as createHash12 } from "node:crypto";
17503
17763
  import { fileURLToPath } from "node:url";
@@ -17519,8 +17779,10 @@ function saveCredentials(c) {
17519
17779
  }
17520
17780
  }
17521
17781
  function loadAllCredentials() {
17782
+ const fromEnv = envCredentials();
17783
+ if (fromEnv) return [{ serverUrl: fromEnv.serverUrl, token: fromEnv.token }];
17522
17784
  try {
17523
- const parsed = JSON.parse(readFileSync32(credPath(), "utf-8"));
17785
+ const parsed = JSON.parse(readFileSync31(credPath(), "utf-8"));
17524
17786
  if (Array.isArray(parsed?.targets)) return parsed.targets.filter((t) => t?.serverUrl).map((t) => ({ serverUrl: t.serverUrl, token: t.token || "" }));
17525
17787
  if (parsed?.serverUrl) return [{ serverUrl: parsed.serverUrl, token: parsed.token || "" }];
17526
17788
  } catch {
@@ -17679,6 +17941,10 @@ function isLocalHost(serverUrl) {
17679
17941
  async function syncSessions(opts = {}) {
17680
17942
  const targets = loadAllCredentials();
17681
17943
  if (targets.length === 0) throw new Error("Not logged in \u2014 run `chat-recall login <server-url> --token <token>`");
17944
+ try {
17945
+ pruneLedgerTargets(targets.map((t) => t.serverUrl));
17946
+ } catch {
17947
+ }
17682
17948
  let agg = null;
17683
17949
  const errors = [];
17684
17950
  for (const cred of targets) {
@@ -17701,6 +17967,7 @@ async function syncSessions(opts = {}) {
17701
17967
  errors.push(`${cred.serverUrl}: ${e instanceof Error ? e.message : e}`);
17702
17968
  }
17703
17969
  }
17970
+ flushLedger();
17704
17971
  if (!agg) throw new Error(`sync failed for all targets:
17705
17972
  ${errors.join("\n ")}`);
17706
17973
  if (errors.length > 0) console.error(`[sync] ${errors.length} target(s) failed (others succeeded):
@@ -17726,11 +17993,11 @@ async function reconcileFieldsForTarget(cred, fullRefs, opts = {}) {
17726
17993
  let pushed = 0, absent = 0, scanned = 0;
17727
17994
  for (const field of SYNC_FIELDS) {
17728
17995
  const needFull = !!opts.force || fieldNeedsFullPass(base, field);
17729
- const candidates = needFull ? fullRefs() : opts.convRefs ?? fullRefs();
17996
+ const candidates2 = needFull ? fullRefs() : opts.convRefs ?? fullRefs();
17730
17997
  const send = [];
17731
17998
  const mtimeById = /* @__PURE__ */ new Map();
17732
17999
  const absentIds = [];
17733
- for (const ref of candidates) {
18000
+ for (const ref of candidates2) {
17734
18001
  if (!fieldNeedsScan(ledger.get(ref.prefixedId), field, ref.mtime, opts.force)) continue;
17735
18002
  scanned++;
17736
18003
  mtimeById.set(ref.prefixedId, ref.mtime);
@@ -17851,6 +18118,20 @@ function batchScanExternal(items, opts = {}) {
17851
18118
  }
17852
18119
  return { findings, scanned, slices, scanMs };
17853
18120
  }
18121
+ function _resetCapsCacheForTests() {
18122
+ capsOk.clear();
18123
+ }
18124
+ async function verifyServerApi(serverUrl, minApi) {
18125
+ const base = serverUrl.replace(/\/$/, "");
18126
+ const seen = capsOk.get(base);
18127
+ if (seen !== void 0 && Date.now() - seen < CAPS_TTL_MS) return true;
18128
+ const caps = await fetchWithTimeout(`${base}/api/capabilities`).then((r) => r.json());
18129
+ if ((caps.apiVersion ?? 0) < minApi) {
18130
+ throw new Error(`server too old: apiVersion ${caps.apiVersion ?? "none"} < required ${minApi} \u2014 update the server image before syncing`);
18131
+ }
18132
+ capsOk.set(base, Date.now());
18133
+ return true;
18134
+ }
17854
18135
  async function syncToTarget(cred, opts = {}) {
17855
18136
  const sync = loadSettings().sync;
17856
18137
  const serverCfg = await fetchTenantSyncConfig(cred);
@@ -17871,14 +18152,9 @@ async function syncToTarget(cred, opts = {}) {
17871
18152
  return isProjectSyncable(r.source === "ignored" ? "" : r.id, projectPath, { syncMode: "only", syncOnly });
17872
18153
  };
17873
18154
  const MIN_SERVER_API = 2;
17874
- try {
17875
- const caps = await fetchWithTimeout(`${cred.serverUrl.replace(/\/$/, "")}/api/capabilities`).then((r) => r.json());
17876
- if ((caps.apiVersion ?? 0) < MIN_SERVER_API) {
17877
- throw new Error(`server too old: apiVersion ${caps.apiVersion ?? "none"} < required ${MIN_SERVER_API} \u2014 update the server image before syncing`);
17878
- }
17879
- } catch (e) {
17880
- if (e instanceof Error && e.message.startsWith("server too old")) throw e;
17881
- throw new Error(`cannot verify server version (${e instanceof Error ? e.message : e}) \u2014 refusing to sync blind`);
18155
+ {
18156
+ const ok = await verifyServerApi(cred.serverUrl, MIN_SERVER_API);
18157
+ if (!ok) throw new Error(`cannot verify server version \u2014 refusing to sync blind`);
17882
18158
  }
17883
18159
  const { tenantRules, verifySecrets } = await fetchTenantSecurityConfig(cred);
17884
18160
  const CHANGED_WALK_OVERLAP_MS = 15 * 6e4;
@@ -17935,8 +18211,11 @@ async function syncToTarget(cred, opts = {}) {
17935
18211
  try {
17936
18212
  const body2 = JSON.parse(text);
17937
18213
  detail = body2.error ?? text;
18214
+ if (body2.detail) detail += `
18215
+ ${body2.detail}`;
17938
18216
  if (body2.resetsAt) detail += ` (resets ${new Date(body2.resetsAt).toISOString().slice(0, 10)})`;
17939
- if (body2.upgradeUrl) detail += ` \u2014 ${body2.upgradeUrl}`;
18217
+ if (body2.upgradeUrl) detail += `
18218
+ ${body2.upgradeUrl}`;
17940
18219
  } catch {
17941
18220
  }
17942
18221
  throw new Error(`sync paused by the server: ${detail}`);
@@ -18083,16 +18362,21 @@ async function syncToTarget(cred, opts = {}) {
18083
18362
  const findingsBatch = makeBatcher("findings", 200, true);
18084
18363
  const scanSecrets = isSecretScannerAvailable() && upload.findings && process.env.CHAT_RECALL_SCAN_SECRETS !== "0";
18085
18364
  const verifySecretsScan = verifySecrets;
18365
+ let skipped = 0, redactions = 0, walked = 0;
18086
18366
  const kgSeen = /* @__PURE__ */ new Set();
18087
18367
  const kgEntityType = /* @__PURE__ */ new Map();
18088
- const kgRows = [];
18089
- const accumulateKg = (triples, sourceSession) => {
18368
+ const kgEntityNames = /* @__PURE__ */ new Set();
18369
+ const kgKey = (subject, predicate, object, validFrom) => createHash12("sha1").update(`${subject}|${predicate}|${object}|${validFrom}`).digest("hex").slice(0, 16);
18370
+ const accumulateKg = async (triples, sourceSession) => {
18090
18371
  for (const t of triples) {
18091
- const key = `${t.subject}|${t.predicate}|${t.object}|${t.validFrom ?? ""}`;
18372
+ const key = kgKey(t.subject, t.predicate, t.object, t.validFrom ?? "");
18092
18373
  if (kgSeen.has(key)) continue;
18093
18374
  kgSeen.add(key);
18094
18375
  if (t.predicate === "is_a" && !kgEntityType.has(t.subject)) kgEntityType.set(t.subject, t.object);
18095
- kgRows.push({
18376
+ kgEntityNames.add(t.subject);
18377
+ kgEntityNames.add(t.object);
18378
+ const count = { redactions: 0 };
18379
+ await kgTripleBatch.add(redactDeep({
18096
18380
  subject: t.subject,
18097
18381
  predicate: t.predicate,
18098
18382
  object: t.object,
@@ -18100,10 +18384,10 @@ async function syncToTarget(cred, opts = {}) {
18100
18384
  valid_to: null,
18101
18385
  confidence: t.confidence ?? 1,
18102
18386
  source_session: sourceSession
18103
- });
18387
+ }, count));
18388
+ redactions += count.redactions;
18104
18389
  }
18105
18390
  };
18106
- let skipped = 0, redactions = 0, walked = 0;
18107
18391
  let scanMs = 0, scanned = 0;
18108
18392
  const slice = refs.slice(0, opts.limit ?? refs.length);
18109
18393
  const trace = process.env.CHAT_RECALL_SYNC_TRACE === "1" ? (msg) => {
@@ -18236,7 +18520,7 @@ async function syncToTarget(cred, opts = {}) {
18236
18520
  await convBatch.add(built.conv);
18237
18521
  if (built.findings.length > 0) await findingsBatch.add(built.findings);
18238
18522
  if (upload.sessionMeta) {
18239
- 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);
18523
+ 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);
18240
18524
  }
18241
18525
  if (upload.sessionMeta && sessionFileBytes(ref) <= FULL_BUILD_MAX_BYTES) {
18242
18526
  try {
@@ -18315,7 +18599,7 @@ async function syncToTarget(cred, opts = {}) {
18315
18599
  chunks: redactedChunks
18316
18600
  });
18317
18601
  if (upload.sessionMeta && redactedChunks.length > 0) {
18318
- accumulateKg(
18602
+ await accumulateKg(
18319
18603
  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 }),
18320
18604
  null
18321
18605
  );
@@ -18340,16 +18624,8 @@ async function syncToTarget(cred, opts = {}) {
18340
18624
  }
18341
18625
  }
18342
18626
  }
18343
- if (upload.sessionMeta && kgRows.length > 0) {
18344
- const entityNames = /* @__PURE__ */ new Set();
18345
- for (const t of kgRows) {
18346
- const count = { redactions: 0 };
18347
- await kgTripleBatch.add(redactDeep(t, count));
18348
- redactions += count.redactions;
18349
- entityNames.add(t.subject);
18350
- entityNames.add(t.object);
18351
- }
18352
- for (const name of entityNames) {
18627
+ if (upload.sessionMeta && kgEntityNames.size > 0) {
18628
+ for (const name of kgEntityNames) {
18353
18629
  const count = { redactions: 0 };
18354
18630
  await kgEntityBatch.add(redactDeep({ name, type: kgEntityType.get(name) ?? "unknown", properties: {} }, count));
18355
18631
  redactions += count.redactions;
@@ -18709,56 +18985,58 @@ function collectDerived(ref, mtime, count) {
18709
18985
  const id = ref.prefixedId;
18710
18986
  const compute = [];
18711
18987
  let fullOutcome = null;
18712
- for (const kind of COMPUTE_KINDS) {
18713
- let data = null;
18714
- try {
18715
- if (kind === "diff") {
18716
- const replay = replaySessionAny(id);
18717
- if (replay.found) data = replay;
18718
- } else if (kind === "outcome") {
18719
- fullOutcome = computeOutcome(id);
18720
- if (fullOutcome.found) data = fullOutcome;
18721
- } else if (kind === "commits") {
18722
- if (!fullOutcome) fullOutcome = computeOutcome(id);
18723
- if (fullOutcome.found) data = fullOutcome.commits;
18724
- } else if (kind === "markers") {
18725
- const turns = extractTurnsAny(id, { maxTurns: 5e4 });
18726
- 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) }));
18727
- data = { sessionId: id, prompts, summary: summarizeMarkers(prompts) };
18728
- }
18729
- } catch {
18730
- data = null;
18988
+ return withSessionReadCache(() => {
18989
+ for (const kind of COMPUTE_KINDS) {
18990
+ let data = null;
18991
+ try {
18992
+ if (kind === "diff") {
18993
+ const replay = replaySessionAny(id);
18994
+ if (replay.found) data = replay;
18995
+ } else if (kind === "outcome") {
18996
+ fullOutcome = computeOutcome(id);
18997
+ if (fullOutcome.found) data = fullOutcome;
18998
+ } else if (kind === "commits") {
18999
+ if (!fullOutcome) fullOutcome = computeOutcome(id);
19000
+ if (fullOutcome.found) data = fullOutcome.commits;
19001
+ } else if (kind === "markers") {
19002
+ const turns = extractTurnsAny(id, { maxTurns: 5e4 });
19003
+ 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) }));
19004
+ data = { sessionId: id, prompts, summary: summarizeMarkers(prompts) };
19005
+ }
19006
+ } catch {
19007
+ data = null;
19008
+ }
19009
+ if (data === null) continue;
19010
+ const redacted = redactDeep(data, count);
19011
+ if (JSON.stringify(redacted).length > MAX_DERIVED_ROW_BYTES) continue;
19012
+ compute.push({ kind, mtime, data: redacted });
19013
+ }
19014
+ let outcomeRow = null;
19015
+ if (fullOutcome?.found) {
19016
+ outcomeRow = {
19017
+ sessionId: id,
19018
+ tool: ref.toolId,
19019
+ status: fullOutcome.status,
19020
+ reason: fullOutcome.reason,
19021
+ fileMtime: mtime,
19022
+ fileSize: 0,
19023
+ contentHash: "",
19024
+ fileCount: fullOutcome.fileCount,
19025
+ linesAdded: fullOutcome.totalLinesAdded,
19026
+ linesRemoved: fullOutcome.totalLinesRemoved,
19027
+ commits: fullOutcome.commits?.totalCommits ?? 0,
19028
+ isFull: true,
19029
+ classifiedAt: Date.now(),
19030
+ lastScannedOffset: 0
19031
+ };
18731
19032
  }
18732
- if (data === null) continue;
18733
- const redacted = redactDeep(data, count);
18734
- if (JSON.stringify(redacted).length > MAX_DERIVED_ROW_BYTES) continue;
18735
- compute.push({ kind, mtime, data: redacted });
18736
- }
18737
- let outcomeRow = null;
18738
- if (fullOutcome?.found) {
18739
- outcomeRow = {
18740
- sessionId: id,
18741
- tool: ref.toolId,
18742
- status: fullOutcome.status,
18743
- reason: fullOutcome.reason,
18744
- fileMtime: mtime,
18745
- fileSize: 0,
18746
- contentHash: "",
18747
- fileCount: fullOutcome.fileCount,
18748
- linesAdded: fullOutcome.totalLinesAdded,
18749
- linesRemoved: fullOutcome.totalLinesRemoved,
18750
- commits: fullOutcome.commits?.totalCommits ?? 0,
18751
- isFull: true,
18752
- classifiedAt: Date.now(),
18753
- lastScannedOffset: 0
19033
+ return {
19034
+ session_id: id,
19035
+ mtime,
19036
+ compute,
19037
+ outcome_row: outcomeRow ? redactDeep({ ...outcomeRow, sessionId: void 0 }, count) : null
18754
19038
  };
18755
- }
18756
- return {
18757
- session_id: id,
18758
- mtime,
18759
- compute,
18760
- outcome_row: outcomeRow ? redactDeep({ ...outcomeRow, sessionId: void 0 }, count) : null
18761
- };
19039
+ });
18762
19040
  }
18763
19041
  function isSyncSkip(r) {
18764
19042
  return typeof r.skipped === "string";
@@ -18787,10 +19065,11 @@ async function syncIncremental(opts = {}) {
18787
19065
  lock.release();
18788
19066
  }
18789
19067
  }
18790
- 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;
19068
+ 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;
18791
19069
  var init_sync_client = __esm({
18792
19070
  "src/sync-client.ts"() {
18793
19071
  "use strict";
19072
+ init_credentials_env();
18794
19073
  init_secret_redactor();
18795
19074
  init_source_discovery();
18796
19075
  init_home_discovery();
@@ -18818,14 +19097,15 @@ var init_sync_client = __esm({
18818
19097
  init_sync_fields();
18819
19098
  init_index_lock();
18820
19099
  init_http();
19100
+ init_live_session_scan();
18821
19101
  init_backends();
18822
19102
  ownVersion = "0.0.0";
18823
19103
  try {
18824
19104
  const dir = dirname16(fileURLToPath(import.meta.url));
18825
- ownVersion = JSON.parse(readFileSync32(join41(dir, "../package.json"), "utf-8")).version || ownVersion;
19105
+ ownVersion = JSON.parse(readFileSync31(join41(dir, "../package.json"), "utf-8")).version || ownVersion;
18826
19106
  } catch {
18827
19107
  try {
18828
- if (true) ownVersion = "0.5.5";
19108
+ if (true) ownVersion = "0.5.6";
18829
19109
  } catch {
18830
19110
  }
18831
19111
  }
@@ -18836,6 +19116,8 @@ var init_sync_client = __esm({
18836
19116
  hashPath = (p) => p ? "p_" + createHash12("sha256").update(p).digest("hex").slice(0, 12) : "";
18837
19117
  COMPUTE_KINDS = ["diff", "outcome", "commits", "markers"];
18838
19118
  MAX_DERIVED_ROW_BYTES = 2 * 1024 * 1024;
19119
+ CAPS_TTL_MS = 60 * 6e4;
19120
+ capsOk = /* @__PURE__ */ new Map();
18839
19121
  FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "64", 10)) * 1024 * 1024;
18840
19122
  FULL_BUILD_TAIL_BYTES = 16 * 1024 * 1024;
18841
19123
  BATCH_SCAN_MAX_BYTES = Math.max(1, parseInt(process.env.CHAT_RECALL_BATCHSCAN_MAX_MB || "64", 10)) * 1024 * 1024;
@@ -18848,7 +19130,7 @@ __export(client_events_exports, {
18848
19130
  isTelemetryEnabled: () => isTelemetryEnabled,
18849
19131
  reportClientEvent: () => reportClientEvent
18850
19132
  });
18851
- import { readFileSync as readFileSync33 } from "node:fs";
19133
+ import { readFileSync as readFileSync32 } from "node:fs";
18852
19134
  function scrub(msg) {
18853
19135
  if (!msg) return "";
18854
19136
  const noHome = msg.replace(/\/(?:home|Users)\/[^/\s]+/g, "~");
@@ -18901,29 +19183,186 @@ var init_client_events = __esm({
18901
19183
  init_secret_redactor();
18902
19184
  CLI_VERSION2 = "0.0.0";
18903
19185
  try {
18904
- CLI_VERSION2 = JSON.parse(readFileSync33(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION2;
19186
+ CLI_VERSION2 = JSON.parse(readFileSync32(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION2;
18905
19187
  } catch {
18906
19188
  }
18907
19189
  }
18908
19190
  });
18909
19191
 
19192
+ // ../engine/dist/core/which.js
19193
+ import { existsSync as existsSync40, statSync as statSync36, accessSync, constants } from "node:fs";
19194
+ import { join as join42, isAbsolute as isAbsolute2, delimiter as delimiter2, sep as sep2 } from "node:path";
19195
+ function executable(p) {
19196
+ try {
19197
+ if (!statSync36(p).isFile())
19198
+ return false;
19199
+ } catch {
19200
+ return false;
19201
+ }
19202
+ if (isWindows)
19203
+ return true;
19204
+ try {
19205
+ accessSync(p, constants.X_OK);
19206
+ return true;
19207
+ } catch {
19208
+ return false;
19209
+ }
19210
+ }
19211
+ function candidates(name) {
19212
+ if (!isWindows)
19213
+ return [name];
19214
+ const exts = (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
19215
+ if (exts.some((e) => name.toLowerCase().endsWith(e.toLowerCase())))
19216
+ return [name];
19217
+ return exts.map((e) => name + e);
19218
+ }
19219
+ function resolveOnPath(name) {
19220
+ if (!name)
19221
+ return null;
19222
+ if (name.includes("/") || isWindows && name.includes(sep2) || isAbsolute2(name)) {
19223
+ for (const c of candidates(name))
19224
+ if (executable(c))
19225
+ return c;
19226
+ return null;
19227
+ }
19228
+ for (const raw of (process.env.PATH || "").split(delimiter2)) {
19229
+ const dir = raw.replace(/^"(.*)"$/, "$1");
19230
+ if (!dir)
19231
+ continue;
19232
+ for (const c of candidates(name)) {
19233
+ const full = join42(dir, c);
19234
+ if (existsSync40(full) && executable(full))
19235
+ return full;
19236
+ }
19237
+ }
19238
+ return null;
19239
+ }
19240
+ function isOnPath(name) {
19241
+ return resolveOnPath(name) !== null;
19242
+ }
19243
+ var isWindows;
19244
+ var init_which = __esm({
19245
+ "../engine/dist/core/which.js"() {
19246
+ "use strict";
19247
+ isWindows = process.platform === "win32";
19248
+ }
19249
+ });
19250
+
19251
+ // src/update-notice.ts
19252
+ var update_notice_exports = {};
19253
+ __export(update_notice_exports, {
19254
+ printUpdateNotice: () => printUpdateNotice,
19255
+ refreshUpdateCheck: () => refreshUpdateCheck,
19256
+ updateNotice: () => updateNotice
19257
+ });
19258
+ import { readFileSync as readFileSync34, writeFileSync as writeFileSync10 } from "node:fs";
19259
+ import { join as join44 } from "node:path";
19260
+ function readCheck() {
19261
+ try {
19262
+ const c = JSON.parse(readFileSync34(checkPath(), "utf-8"));
19263
+ return c && typeof c.serverVersion === "string" ? c : null;
19264
+ } catch {
19265
+ return null;
19266
+ }
19267
+ }
19268
+ function writeCheck(c) {
19269
+ try {
19270
+ writeFileSync10(checkPath(), JSON.stringify(c));
19271
+ } catch {
19272
+ }
19273
+ }
19274
+ function updateNotice() {
19275
+ const c = readCheck();
19276
+ if (!c) return null;
19277
+ const own = cliVersion();
19278
+ if (compareVersions(c.serverVersion, own) <= 0) return null;
19279
+ return `chat-recall ${c.serverVersion} is available (you have ${own}). Auto-update should apply it; force it now with: chat-recall update`;
19280
+ }
19281
+ async function refreshUpdateCheck(force = false) {
19282
+ const prev = readCheck();
19283
+ if (!force && prev && Date.now() - prev.checkedAt < CHECK_TTL_MS) return prev;
19284
+ let newest = "0.0.0";
19285
+ try {
19286
+ const { loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
19287
+ for (const cred of loadAllCredentials2()) {
19288
+ if (!cred.serverUrl) continue;
19289
+ const base = cred.serverUrl.replace(/\/+$/, "");
19290
+ try {
19291
+ const res = await fetchWithTimeout(`${base}/api/capabilities`, {
19292
+ headers: cred.token ? { authorization: `Bearer ${cred.token}` } : {}
19293
+ }, 5e3);
19294
+ if (!res.ok) continue;
19295
+ const caps = await res.json();
19296
+ const v = caps?.cli?.version;
19297
+ if (v && compareVersions(v, newest) > 0) newest = v;
19298
+ } catch {
19299
+ }
19300
+ }
19301
+ } catch {
19302
+ return prev;
19303
+ }
19304
+ if (newest === "0.0.0") return prev;
19305
+ const next = { serverVersion: newest, checkedAt: Date.now() };
19306
+ writeCheck(next);
19307
+ return next;
19308
+ }
19309
+ function printUpdateNotice() {
19310
+ if (printed) return;
19311
+ printed = true;
19312
+ try {
19313
+ const msg = updateNotice();
19314
+ if (msg) console.error(`
19315
+ \u26A0 ${msg}
19316
+ `);
19317
+ const t = setTimeout(() => {
19318
+ void refreshUpdateCheck().catch(() => {
19319
+ });
19320
+ }, 0);
19321
+ if (typeof t.unref === "function") t.unref();
19322
+ } catch {
19323
+ }
19324
+ }
19325
+ var CHECK_TTL_MS, checkPath, printed;
19326
+ var init_update_notice = __esm({
19327
+ "src/update-notice.ts"() {
19328
+ "use strict";
19329
+ init_paths();
19330
+ init_auto_update();
19331
+ init_http();
19332
+ CHECK_TTL_MS = 6 * 3600 * 1e3;
19333
+ checkPath = () => join44(getDataDir(), "update-check.json");
19334
+ printed = false;
19335
+ }
19336
+ });
19337
+
18910
19338
  // ../engine/dist/core/companions.js
18911
- import { existsSync as existsSync40, mkdirSync as mkdirSync10, statSync as statSync36, chmodSync as chmodSync5, unlinkSync as unlinkSync3, renameSync as renameSync5, createWriteStream, readFileSync as readFileSync34, writeFileSync as writeFileSync9 } from "node:fs";
19339
+ import { existsSync as existsSync41, mkdirSync as mkdirSync11, statSync as statSync37, chmodSync as chmodSync5, unlinkSync as unlinkSync3, renameSync as renameSync5, createWriteStream, readFileSync as readFileSync35, writeFileSync as writeFileSync11 } from "node:fs";
18912
19340
  import { homedir as homedir15 } from "node:os";
18913
- import { join as join42 } from "node:path";
19341
+ import { join as join45 } from "node:path";
18914
19342
  import { execSync as execSync3 } from "node:child_process";
19343
+ import { createHash as createHash13 } from "node:crypto";
19344
+ function expectedCodeindexSha256(artifact) {
19345
+ return CODEINDEX_SHA256[artifact.replace(/^codeindex-/, "").replace(/\.exe$/, "")] ?? null;
19346
+ }
18915
19347
  function detectPlatform() {
18916
19348
  const arch = process.arch;
18917
19349
  const platform = process.platform;
18918
19350
  const archMap = { x64: "x86_64", arm64: "aarch64" };
18919
- const platformMap = { linux: "linux", darwin: "darwin", win32: "windows" };
19351
+ const platformMap = { linux: "linux", darwin: "macos", win32: "windows" };
18920
19352
  const a = archMap[arch];
18921
19353
  const p = platformMap[platform];
18922
19354
  if (!a || !p) {
18923
19355
  return { reason: `Unsupported platform: ${arch}-${platform}` };
18924
19356
  }
18925
- const prebuiltCombos = /* @__PURE__ */ new Set(["x86_64-linux"]);
18926
- const artifact = `codeindex-${a}-${p}`;
19357
+ const prebuiltCombos = /* @__PURE__ */ new Set([
19358
+ "x86_64-linux",
19359
+ "aarch64-linux",
19360
+ "x86_64-macos",
19361
+ "aarch64-macos",
19362
+ "x86_64-windows",
19363
+ "aarch64-windows"
19364
+ ]);
19365
+ const artifact = `codeindex-${a}-${p}${p === "windows" ? ".exe" : ""}`;
18927
19366
  if (!prebuiltCombos.has(`${a}-${p}`)) {
18928
19367
  return {
18929
19368
  artifact,
@@ -18940,20 +19379,15 @@ function checkCodeindexStatus() {
18940
19379
  artifactName: platform.artifact,
18941
19380
  unsupportedReason: platform.reason
18942
19381
  };
18943
- let foundPath = null;
18944
- try {
18945
- const out = execSync3("command -v codeindex", { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
18946
- if (out)
18947
- foundPath = out;
18948
- } catch {
19382
+ let foundPath = resolveOnPath(CODEINDEX_BIN_NAME);
19383
+ if (!foundPath) {
19384
+ foundPath = resolveOnPath(CODEINDEX_BIN_PATH);
18949
19385
  }
18950
- if (!foundPath && existsSync40(CODEINDEX_BIN_PATH))
18951
- foundPath = CODEINDEX_BIN_PATH;
18952
19386
  if (foundPath) {
18953
19387
  status.installed = true;
18954
19388
  status.path = foundPath;
18955
19389
  try {
18956
- status.size = statSync36(foundPath).size;
19390
+ status.size = statSync37(foundPath).size;
18957
19391
  } catch {
18958
19392
  }
18959
19393
  try {
@@ -18965,16 +19399,9 @@ function checkCodeindexStatus() {
18965
19399
  return status;
18966
19400
  }
18967
19401
  async function downloadArtifact(artifact, dest) {
18968
- const hasGh = (() => {
18969
- try {
18970
- execSync3("command -v gh", { stdio: "ignore" });
18971
- return true;
18972
- } catch {
18973
- return false;
18974
- }
18975
- })();
19402
+ const hasGh = isOnPath("gh");
18976
19403
  if (hasGh) {
18977
- execSync3(`gh release download ${CODEINDEX_VERSION} --repo ${CODEINDEX_REPO} -p ${artifact} -O ${dest}`, {
19404
+ execSync3(`gh release download ${CODEINDEX_VERSION} --repo ${CODEINDEX_REPO} -p ${artifact} -O "${dest}"`, {
18978
19405
  stdio: ["ignore", "pipe", "pipe"],
18979
19406
  timeout: 6e4
18980
19407
  });
@@ -19013,15 +19440,26 @@ async function installCodeindex(opts = {}) {
19013
19440
  return status;
19014
19441
  if (!status.prebuiltAvailable)
19015
19442
  return status;
19016
- mkdirSync10(CODEINDEX_INSTALL_DIR, { recursive: true });
19443
+ mkdirSync11(CODEINDEX_INSTALL_DIR, { recursive: true });
19017
19444
  const tmpPath = `${CODEINDEX_BIN_PATH}.partial`;
19018
19445
  try {
19019
19446
  await downloadArtifact(status.artifactName, tmpPath);
19447
+ const expected = expectedCodeindexSha256(status.artifactName);
19448
+ if (!expected) {
19449
+ 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.`);
19450
+ }
19451
+ const actual = createHash13("sha256").update(readFileSync35(tmpPath)).digest("hex");
19452
+ if (actual !== expected) {
19453
+ throw new Error(`Checksum mismatch for ${status.artifactName}.
19454
+ expected ${expected}
19455
+ actual ${actual}
19456
+ Refusing to install. The release may have been re-uploaded, or something is serving different bytes.`);
19457
+ }
19020
19458
  chmodSync5(tmpPath, 493);
19021
19459
  renameSync5(tmpPath, CODEINDEX_BIN_PATH);
19022
19460
  } catch (err) {
19023
19461
  try {
19024
- if (existsSync40(tmpPath))
19462
+ if (existsSync41(tmpPath))
19025
19463
  unlinkSync3(tmpPath);
19026
19464
  } catch {
19027
19465
  }
@@ -19029,15 +19467,24 @@ async function installCodeindex(opts = {}) {
19029
19467
  }
19030
19468
  return checkCodeindexStatus();
19031
19469
  }
19032
- var CODEINDEX_REPO, CODEINDEX_VERSION, CODEINDEX_INSTALL_DIR, CODEINDEX_BIN_NAME, CODEINDEX_BIN_PATH;
19470
+ var CODEINDEX_REPO, CODEINDEX_VERSION, CODEINDEX_SHA256, CODEINDEX_INSTALL_DIR, CODEINDEX_BIN_NAME, CODEINDEX_BIN_PATH;
19033
19471
  var init_companions = __esm({
19034
19472
  "../engine/dist/core/companions.js"() {
19035
19473
  "use strict";
19474
+ init_which();
19036
19475
  CODEINDEX_REPO = "munhq/codeindex";
19037
- CODEINDEX_VERSION = "v0.1.0";
19038
- CODEINDEX_INSTALL_DIR = join42(homedir15(), ".local", "bin");
19039
- CODEINDEX_BIN_NAME = "codeindex";
19040
- CODEINDEX_BIN_PATH = join42(CODEINDEX_INSTALL_DIR, CODEINDEX_BIN_NAME);
19476
+ CODEINDEX_VERSION = "v0.3.1";
19477
+ CODEINDEX_SHA256 = {
19478
+ "x86_64-linux": "4378f12e7c80fa8bb53e41c02db4b95955f2f878369abfb28b5edd94b4adbb85",
19479
+ "aarch64-linux": "5a1d6c9c2592a3721547c42cd9b6b11f460e903bfc46407ae27d5879e7259587",
19480
+ "x86_64-macos": "6ad2a2b40faa4ec72ac35fee84f35ea7c848cddc4f6382b4cafedb644f30389f",
19481
+ "aarch64-macos": "f366d00918f79a4a0acc09c6091d97c6c378c79edbdbe3cb22e4014ea57f08d2",
19482
+ "x86_64-windows": "a96fc8c8949bb399a44cc0615b12e458460a529c69f01e8829cc39a20f45ab29",
19483
+ "aarch64-windows": "42620d56272cd9ba9d785569d8d754d6b4a3e80f36f28db3e0c483fd600ed117"
19484
+ };
19485
+ CODEINDEX_INSTALL_DIR = join45(homedir15(), ".local", "bin");
19486
+ CODEINDEX_BIN_NAME = process.platform === "win32" ? "codeindex.exe" : "codeindex";
19487
+ CODEINDEX_BIN_PATH = join45(CODEINDEX_INSTALL_DIR, CODEINDEX_BIN_NAME);
19041
19488
  }
19042
19489
  });
19043
19490
 
@@ -19143,15 +19590,17 @@ var init_prompts = __esm({
19143
19590
  var collector_exports = {};
19144
19591
  __export(collector_exports, {
19145
19592
  COLLECTOR_VERSION: () => COLLECTOR_VERSION,
19593
+ GENERATED_PATTERNS: () => GENERATED_PATTERNS,
19146
19594
  collectCode: () => collectCode,
19595
+ isGenerated: () => isGenerated,
19147
19596
  isJunkWorkspacePath: () => isJunkWorkspacePath,
19148
19597
  isSecretRule: () => isSecretRule,
19149
19598
  resolveCodeindexBin: () => resolveCodeindexBin
19150
19599
  });
19151
19600
  import { spawn } from "node:child_process";
19152
19601
  import { execSync as execSync4 } from "node:child_process";
19153
- import { readFileSync as readFileSync35 } from "node:fs";
19154
- import { join as join43, basename as basename23 } from "node:path";
19602
+ import { readFileSync as readFileSync36 } from "node:fs";
19603
+ import { join as join46, basename as basename23 } from "node:path";
19155
19604
  async function resolveCodeindexBin(autoInstall = true) {
19156
19605
  let st = checkCodeindexStatus();
19157
19606
  if (!st.installed && autoInstall)
@@ -19238,10 +19687,11 @@ function isSecretRule(rule) {
19238
19687
  return /secret|password|credential|key|token|auth/i.test(rule);
19239
19688
  }
19240
19689
  function isJunkWorkspacePath(ws) {
19241
- return /(^|\/)(scratchpad|\.cache|node_modules|\.git|tmp)(\/|$)|claude-1000/i.test(ws);
19690
+ const norm = ws.replace(/\\/g, "/");
19691
+ return /(^|\/)(scratchpad|\.cache|node_modules|\.git|tmp)(\/|$)|claude-1000/i.test(norm);
19242
19692
  }
19243
19693
  async function collectCode(opts) {
19244
- const ws = opts.workspace.replace(/\/+$/, "");
19694
+ const ws = opts.workspace.replace(/[/\\]+$/, "");
19245
19695
  const log6 = opts.log ?? (() => {
19246
19696
  });
19247
19697
  if (isJunkWorkspacePath(ws)) {
@@ -19249,9 +19699,11 @@ async function collectCode(opts) {
19249
19699
  }
19250
19700
  const bin = opts.binPath ?? await resolveCodeindexBin(opts.autoInstall ?? true);
19251
19701
  const projectId = resolveProjectId(ws).id || `path:${ws}`;
19252
- const wsPrefix = ws + "/";
19702
+ const wsNorm = ws.replace(/\\/g, "/");
19703
+ const wsPrefix = wsNorm + "/";
19253
19704
  const rel = (p) => {
19254
- let r = p.startsWith(wsPrefix) ? p.slice(wsPrefix.length) : p;
19705
+ const n = p.replace(/\\/g, "/");
19706
+ let r = n.startsWith(wsPrefix) ? n.slice(wsPrefix.length) : n;
19255
19707
  if (r.startsWith("./"))
19256
19708
  r = r.slice(2);
19257
19709
  return r;
@@ -19312,7 +19764,7 @@ async function collectCode(opts) {
19312
19764
  }
19313
19765
  const complexity = (r) => {
19314
19766
  try {
19315
- const txt = readFileSync35(join43(ws, r), "utf8");
19767
+ const txt = readFileSync36(join46(ws, r), "utf8");
19316
19768
  if (txt.length > 2e6)
19317
19769
  return 1;
19318
19770
  const m = txt.match(CF_RE);
@@ -19896,7 +20348,7 @@ async function collectCode(opts) {
19896
20348
  void health;
19897
20349
  return { project, findings, hotspots, actions };
19898
20350
  }
19899
- var COLLECTOR_VERSION, ANALYSES, GENERATED, CF_RE, DEPTH, MCP_OUT_MAX_BYTES, call, COMMON_SYMBOL_NAMES;
20351
+ var COLLECTOR_VERSION, ANALYSES, GENERATED, CF_RE, DEPTH, MCP_OUT_MAX_BYTES, call, GENERATED_PATTERNS, COMMON_SYMBOL_NAMES;
19900
20352
  var init_collector = __esm({
19901
20353
  "../engine/dist/core/code/collector.js"() {
19902
20354
  "use strict";
@@ -19938,7 +20390,13 @@ var init_collector = __esm({
19938
20390
  "/vendor/",
19939
20391
  "/third_party/",
19940
20392
  "/site-packages/",
20393
+ // Lockfiles. '.lock' alone catches yarn.lock, Cargo.lock, poetry.lock and
20394
+ // composer.lock, and MISSES the two biggest ones in a JS repo —
20395
+ // package-lock.json and pnpm-lock.yaml are named '-lock.<ext>'. Found by
20396
+ // replacing this file's mirror test with one that calls the real predicate.
19941
20397
  ".lock",
20398
+ "-lock.json",
20399
+ "-lock.yaml",
19942
20400
  "/dist/",
19943
20401
  "/build/",
19944
20402
  "/out/",
@@ -19955,6 +20413,7 @@ var init_collector = __esm({
19955
20413
  DEPTH = 2;
19956
20414
  MCP_OUT_MAX_BYTES = 64 * 1024 * 1024;
19957
20415
  call = (id, name, args = {}) => ({ jsonrpc: "2.0", id, method: "tools/call", params: { name, arguments: args } });
20416
+ GENERATED_PATTERNS = GENERATED;
19958
20417
  COMMON_SYMBOL_NAMES = /* @__PURE__ */ new Set([
19959
20418
  "error",
19960
20419
  "err",
@@ -20030,26 +20489,26 @@ __export(install_skills_exports, {
20030
20489
  skillsSourceDir: () => skillsSourceDir,
20031
20490
  uninstallSkills: () => uninstallSkills
20032
20491
  });
20033
- import { readdirSync as readdirSync31, existsSync as existsSync41, mkdirSync as mkdirSync11, rmSync as rmSync6, cpSync, writeFileSync as writeFileSync10, readFileSync as readFileSync36, statSync as statSync37, realpathSync as realpathSync4 } from "fs";
20034
- import { createHash as createHash13 } from "crypto";
20035
- import { join as join44, basename as basename24 } from "path";
20492
+ import { readdirSync as readdirSync31, existsSync as existsSync42, mkdirSync as mkdirSync12, rmSync as rmSync6, cpSync, writeFileSync as writeFileSync12, readFileSync as readFileSync37, statSync as statSync38, realpathSync as realpathSync4 } from "fs";
20493
+ import { createHash as createHash14 } from "crypto";
20494
+ import { join as join47, basename as basename24 } from "path";
20036
20495
  function skillsSourceDir() {
20037
- return join44(import.meta.dirname, "..", "skills");
20496
+ return join47(import.meta.dirname, "..", "skills");
20038
20497
  }
20039
20498
  function version() {
20040
20499
  try {
20041
- return JSON.parse(readFileSync36(new URL("../package.json", import.meta.url), "utf-8")).version || "0.0.0";
20500
+ return JSON.parse(readFileSync37(new URL("../package.json", import.meta.url), "utf-8")).version || "0.0.0";
20042
20501
  } catch {
20043
20502
  return "0.0.0";
20044
20503
  }
20045
20504
  }
20046
20505
  function skillsFingerprint() {
20047
20506
  const src = skillsSourceDir();
20048
- const h = createHash13("sha256");
20507
+ const h = createHash14("sha256");
20049
20508
  for (const name of bundledSkillNames()) {
20050
- for (const rel of filesUnder(join44(src, name)).sort()) {
20509
+ for (const rel of filesUnder(join47(src, name)).sort()) {
20051
20510
  h.update(name + "/" + rel);
20052
- h.update(readFileSync36(join44(src, name, rel)));
20511
+ h.update(readFileSync37(join47(src, name, rel)));
20053
20512
  }
20054
20513
  }
20055
20514
  return h.digest("hex").slice(0, 12);
@@ -20059,7 +20518,7 @@ function filesUnder(dir, prefix = "") {
20059
20518
  for (const e of readdirSync31(dir, { withFileTypes: true })) {
20060
20519
  if (e.name === MARKER) continue;
20061
20520
  const rel = prefix ? `${prefix}/${e.name}` : e.name;
20062
- if (e.isDirectory()) out.push(...filesUnder(join44(dir, e.name), rel));
20521
+ if (e.isDirectory()) out.push(...filesUnder(join47(dir, e.name), rel));
20063
20522
  else out.push(rel);
20064
20523
  }
20065
20524
  return out;
@@ -20069,8 +20528,8 @@ function skillStamp() {
20069
20528
  }
20070
20529
  function bundledSkillNames() {
20071
20530
  const src = skillsSourceDir();
20072
- if (!existsSync41(src)) return [];
20073
- return readdirSync31(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync41(join44(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
20531
+ if (!existsSync42(src)) return [];
20532
+ return readdirSync31(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync42(join47(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
20074
20533
  }
20075
20534
  function realPathOrSelf(p) {
20076
20535
  try {
@@ -20083,7 +20542,7 @@ function claudeSkillTargets() {
20083
20542
  const targets = [];
20084
20543
  const seen = /* @__PURE__ */ new Set();
20085
20544
  for (const home of claudeHomeDirs()) {
20086
- const dir = join44(home, "skills");
20545
+ const dir = join47(home, "skills");
20087
20546
  const key = realPathOrSelf(dir);
20088
20547
  if (seen.has(key)) continue;
20089
20548
  seen.add(key);
@@ -20094,7 +20553,7 @@ function claudeSkillTargets() {
20094
20553
  dir,
20095
20554
  // Judge each profile on ITS OWN transcripts, not the primary's: a home
20096
20555
  // with no projects/ is a directory someone made, not a Claude install.
20097
- available: existsSync41(join44(home, "projects"))
20556
+ available: existsSync42(join47(home, "projects"))
20098
20557
  });
20099
20558
  }
20100
20559
  if (targets.length === 0) {
@@ -20117,7 +20576,7 @@ function skillTargets() {
20117
20576
  ];
20118
20577
  }
20119
20578
  function isManaged(dir) {
20120
- return existsSync41(join44(dir, MARKER));
20579
+ return existsSync42(join47(dir, MARKER));
20121
20580
  }
20122
20581
  function installSkills(opts = {}) {
20123
20582
  const onlyAvailable = opts.onlyAvailable ?? true;
@@ -20134,15 +20593,15 @@ function installSkills(opts = {}) {
20134
20593
  continue;
20135
20594
  }
20136
20595
  for (const name of names) {
20137
- const dest = join44(t.dir, name);
20138
- if (existsSync41(dest) && !isManaged(dest)) {
20596
+ const dest = join47(t.dir, name);
20597
+ if (existsSync42(dest) && !isManaged(dest)) {
20139
20598
  skippedUserOwned.push(name);
20140
20599
  continue;
20141
20600
  }
20142
- mkdirSync11(t.dir, { recursive: true });
20601
+ mkdirSync12(t.dir, { recursive: true });
20143
20602
  rmSync6(dest, { recursive: true, force: true });
20144
- cpSync(join44(src, name), dest, { recursive: true });
20145
- writeFileSync10(join44(dest, MARKER), `${stamp}
20603
+ cpSync(join47(src, name), dest, { recursive: true });
20604
+ writeFileSync12(join47(dest, MARKER), `${stamp}
20146
20605
  `);
20147
20606
  installed.push(name);
20148
20607
  }
@@ -20155,13 +20614,13 @@ function uninstallSkills() {
20155
20614
  const perTarget = [];
20156
20615
  for (const t of skillTargets()) {
20157
20616
  const removed = [];
20158
- if (!existsSync41(t.dir)) {
20617
+ if (!existsSync42(t.dir)) {
20159
20618
  perTarget.push({ id: t.id, label: t.label, removed });
20160
20619
  continue;
20161
20620
  }
20162
20621
  for (const entry of readdirSync31(t.dir, { withFileTypes: true })) {
20163
20622
  if (!entry.isDirectory()) continue;
20164
- const dir = join44(t.dir, entry.name);
20623
+ const dir = join47(t.dir, entry.name);
20165
20624
  if (isManaged(dir) && (names.has(entry.name) || entry.name.startsWith("chat-recall"))) {
20166
20625
  rmSync6(dir, { recursive: true, force: true });
20167
20626
  removed.push(entry.name);
@@ -20178,10 +20637,10 @@ function skillsNeedRefresh() {
20178
20637
  for (const t of skillTargets()) {
20179
20638
  if (!t.available) continue;
20180
20639
  for (const name of names) {
20181
- const marker = join44(t.dir, name, MARKER);
20182
- if (!existsSync41(marker)) return true;
20640
+ const marker = join47(t.dir, name, MARKER);
20641
+ if (!existsSync42(marker)) return true;
20183
20642
  try {
20184
- if (readFileSync36(marker, "utf-8").trim() !== cur) return true;
20643
+ if (readFileSync37(marker, "utf-8").trim() !== cur) return true;
20185
20644
  } catch {
20186
20645
  return true;
20187
20646
  }
@@ -20196,11 +20655,11 @@ function skillStatus() {
20196
20655
  let installed = 0;
20197
20656
  let ver = null;
20198
20657
  for (const name of names) {
20199
- const marker = join44(t.dir, name, MARKER);
20200
- if (existsSync41(marker)) {
20658
+ const marker = join47(t.dir, name, MARKER);
20659
+ if (existsSync42(marker)) {
20201
20660
  installed++;
20202
20661
  try {
20203
- ver = readFileSync36(marker, "utf-8").trim();
20662
+ ver = readFileSync37(marker, "utf-8").trim();
20204
20663
  } catch {
20205
20664
  }
20206
20665
  }
@@ -20235,15 +20694,15 @@ __export(service_installer_exports, {
20235
20694
  uninstallService: () => uninstallService
20236
20695
  });
20237
20696
  import { execSync as execSync5 } from "child_process";
20238
- import { existsSync as existsSync42, mkdirSync as mkdirSync12, rmSync as rmSync7, writeFileSync as writeFileSync11 } from "fs";
20697
+ import { existsSync as existsSync43, mkdirSync as mkdirSync13, rmSync as rmSync7, writeFileSync as writeFileSync13 } from "fs";
20239
20698
  import { homedir as homedir16 } from "os";
20240
- import { join as join45 } from "path";
20699
+ import { join as join48 } from "path";
20241
20700
  import { fileURLToPath as fileURLToPath2 } from "url";
20242
20701
  function resolveDaemonPaths() {
20243
20702
  const watchJs = fileURLToPath2(new URL("./watch.js", import.meta.url));
20244
20703
  const node = process.execPath;
20245
- const dataDir = join45(homedir16(), ".chat-recall");
20246
- const logFile = join45(dataDir, "watch.log");
20704
+ const dataDir = join48(homedir16(), ".chat-recall");
20705
+ const logFile = join48(dataDir, "watch.log");
20247
20706
  return { watchJs, node, logFile, dataDir };
20248
20707
  }
20249
20708
  function tryCmd(cmd) {
@@ -20268,7 +20727,7 @@ function isServiceRunning() {
20268
20727
  }
20269
20728
  function installService() {
20270
20729
  const { watchJs, node, logFile, dataDir } = resolveDaemonPaths();
20271
- mkdirSync12(dataDir, { recursive: true });
20730
+ mkdirSync13(dataDir, { recursive: true });
20272
20731
  if (process.platform === "linux") {
20273
20732
  return installSystemd(watchJs, node, logFile);
20274
20733
  }
@@ -20284,11 +20743,11 @@ function installService() {
20284
20743
  );
20285
20744
  }
20286
20745
  function installSystemd(watchJs, node, logFile) {
20287
- const unitDir = join45(homedir16(), ".config", "systemd", "user");
20288
- const unitPath = join45(unitDir, "chat-recall-watch.service");
20746
+ const unitDir = join48(homedir16(), ".config", "systemd", "user");
20747
+ const unitPath = join48(unitDir, "chat-recall-watch.service");
20289
20748
  const unit = renderSystemdUnit(watchJs, node, logFile);
20290
- mkdirSync12(unitDir, { recursive: true });
20291
- writeFileSync11(unitPath, unit);
20749
+ mkdirSync13(unitDir, { recursive: true });
20750
+ writeFileSync13(unitPath, unit);
20292
20751
  try {
20293
20752
  execSync5("systemctl --user daemon-reload && systemctl --user enable chat-recall-watch.service && systemctl --user restart chat-recall-watch.service", { stdio: "inherit" });
20294
20753
  try {
@@ -20316,10 +20775,14 @@ function renderSystemdUnit(watchJs, node, logFile) {
20316
20775
  "Restart=on-failure",
20317
20776
  "RestartSec=10",
20318
20777
  "Nice=10",
20319
- // Keep the service's own stdout/stderr out of the journal noise but
20320
- // available for `chat-recall service status` without paging journald.
20321
- `StandardOutput=append:${logFile}`,
20322
- `StandardError=append:${logFile}`,
20778
+ // JOURNAL, not append:. The file version had no rotation and nothing to
20779
+ // give it any: systemd holds the fd open, so the daemon cannot rename or
20780
+ // truncate its own log without leaving systemd writing into a hole. It
20781
+ // reached 45 MB on this developer's machine and would have grown forever.
20782
+ // journald already rotates and applies disk limits. Read it with
20783
+ // journalctl --user -u chat-recall-watch -f
20784
+ "StandardOutput=journal",
20785
+ "StandardError=journal",
20323
20786
  "",
20324
20787
  "[Install]",
20325
20788
  "WantedBy=default.target",
@@ -20328,11 +20791,11 @@ function renderSystemdUnit(watchJs, node, logFile) {
20328
20791
  }
20329
20792
  function installLaunchd(watchJs, node, logFile) {
20330
20793
  const label = "com.chat-recall.watch";
20331
- const plistDir = join45(homedir16(), "Library", "LaunchAgents");
20332
- const plistPath = join45(plistDir, `${label}.plist`);
20794
+ const plistDir = join48(homedir16(), "Library", "LaunchAgents");
20795
+ const plistPath = join48(plistDir, `${label}.plist`);
20333
20796
  const plist = renderLaunchdPlist(watchJs, node, logFile, label);
20334
- mkdirSync12(plistDir, { recursive: true });
20335
- writeFileSync11(plistPath, plist);
20797
+ mkdirSync13(plistDir, { recursive: true });
20798
+ writeFileSync13(plistPath, plist);
20336
20799
  try {
20337
20800
  try {
20338
20801
  execSync5(`launchctl unload "${plistPath}"`, { stdio: "ignore" });
@@ -20361,7 +20824,7 @@ function renderLaunchdPlist(watchJs, node, logFile, label = "com.chat-recall.wat
20361
20824
  }
20362
20825
  function installWindowsTask(watchJs, node, logFile) {
20363
20826
  const taskName = "chat-recall-watch";
20364
- const tr = `\\"${node}\\" --max-old-space-size=1536 \\"${watchJs}\\"`;
20827
+ const tr = `cmd /c \\"\\"${node}\\" --max-old-space-size=1536 \\"${watchJs}\\" >> \\"${logFile}\\" 2>&1\\"`;
20365
20828
  try {
20366
20829
  execSync5(`schtasks /create /tn "${taskName}" /tr "${tr}" /sc onlogon /rl LIMITED /f`, { stdio: "inherit" });
20367
20830
  try {
@@ -20373,8 +20836,8 @@ function installWindowsTask(watchJs, node, logFile) {
20373
20836
  }
20374
20837
  return { definitionPath: null, logFile };
20375
20838
  }
20376
- function renderWindowsTaskCommand(watchJs, node, taskName = "chat-recall-watch") {
20377
- const tr = `\\"${node}\\" --max-old-space-size=1536 \\"${watchJs}\\"`;
20839
+ function renderWindowsTaskCommand(watchJs, node, taskName = "chat-recall-watch", logFile) {
20840
+ const tr = logFile ? `cmd /c \\"\\"${node}\\" --max-old-space-size=1536 \\"${watchJs}\\" >> \\"${logFile}\\" 2>&1\\"` : `\\"${node}\\" --max-old-space-size=1536 \\"${watchJs}\\"`;
20378
20841
  return `schtasks /create /tn "${taskName}" /tr "${tr}" /sc onlogon /rl LIMITED /f`;
20379
20842
  }
20380
20843
  function uninstallService() {
@@ -20385,8 +20848,8 @@ function uninstallService() {
20385
20848
  removed = true;
20386
20849
  } catch {
20387
20850
  }
20388
- const unitPath = join45(homedir16(), ".config", "systemd", "user", "chat-recall-watch.service");
20389
- if (existsSync42(unitPath)) {
20851
+ const unitPath = join48(homedir16(), ".config", "systemd", "user", "chat-recall-watch.service");
20852
+ if (existsSync43(unitPath)) {
20390
20853
  rmSync7(unitPath, { force: true });
20391
20854
  removed = true;
20392
20855
  }
@@ -20397,13 +20860,13 @@ function uninstallService() {
20397
20860
  return removed;
20398
20861
  }
20399
20862
  if (process.platform === "darwin") {
20400
- const plistPath = join45(homedir16(), "Library", "LaunchAgents", "com.chat-recall.watch.plist");
20863
+ const plistPath = join48(homedir16(), "Library", "LaunchAgents", "com.chat-recall.watch.plist");
20401
20864
  try {
20402
20865
  execSync5(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
20403
20866
  } catch {
20404
20867
  }
20405
20868
  let removed = false;
20406
- if (existsSync42(plistPath)) {
20869
+ if (existsSync43(plistPath)) {
20407
20870
  rmSync7(plistPath, { force: true });
20408
20871
  removed = true;
20409
20872
  }
@@ -20472,11 +20935,11 @@ function stalenessBanner(state) {
20472
20935
  const days = state.periodEnd ? Math.max(0, Math.floor((Date.now() - state.periodEnd) / 864e5)) : null;
20473
20936
  const reason = state.status === "trialing" ? "the trial has ended" : state.status === "past_due" ? "a payment has not gone through" : "the subscription has lapsed";
20474
20937
  return [
20475
- `\u26A0 FREE PLAN \u2014 ${reason}.`,
20476
- "Search and lists cover only the recent window (default 7 days); older history is stored but locked.",
20938
+ `\u26A0 SYNCING IS OFF \u2014 ${reason}.`,
20939
+ "Search and lists cover the WHOLE synced history; nothing is windowed or locked.",
20477
20940
  since ? `The plan lapsed ${since}${days !== null ? ` (${days} days ago)` : ""}.` : "",
20478
- "Do not tell the user their older work does not exist; say it is locked behind the free plan and unlocks on upgrade.",
20479
- "Sync continues under a monthly quota, export always works, and nothing has been deleted."
20941
+ "What is missing is only work done SINCE the lapse, because new sessions stopped uploading.",
20942
+ "Do not tell the user their history is gone or locked. Say new sessions are not syncing, and that subscribing plus one `chat-recall sync --full` brings everything current. Export always works."
20480
20943
  ].filter(Boolean).join(" ");
20481
20944
  }
20482
20945
 
@@ -20488,9 +20951,9 @@ import {
20488
20951
  ListToolsRequestSchema
20489
20952
  } from "@modelcontextprotocol/sdk/types.js";
20490
20953
  import { z } from "zod";
20491
- import { existsSync as existsSync43, readFileSync as readFileSync37 } from "fs";
20492
- import { join as join46 } from "path";
20493
- import { execSync as _execSync, spawn as spawn2 } from "child_process";
20954
+ import { existsSync as existsSync44, readFileSync as readFileSync38 } from "fs";
20955
+ import { join as join49 } from "path";
20956
+ import { spawn as spawn2 } from "child_process";
20494
20957
  import { fileURLToPath as fileURLToPath3 } from "node:url";
20495
20958
 
20496
20959
  // ../engine/dist/core/context.js
@@ -20544,6 +21007,7 @@ function formatContext(context) {
20544
21007
 
20545
21008
  // src/mcp.ts
20546
21009
  init_paths();
21010
+ init_credentials_env();
20547
21011
  init_backends();
20548
21012
  init_tool_backend();
20549
21013
 
@@ -20635,7 +21099,7 @@ function sanitizeQuery(query) {
20635
21099
 
20636
21100
  // ../engine/dist/core/write-ahead-log.js
20637
21101
  init_paths();
20638
- import { existsSync as existsSync14, mkdirSync as mkdirSync4, appendFileSync, openSync as openSync6, closeSync as closeSync6, chmodSync as chmodSync2 } from "fs";
21102
+ import { existsSync as existsSync14, mkdirSync as mkdirSync4, appendFileSync, openSync as openSync7, closeSync as closeSync7, chmodSync as chmodSync2 } from "fs";
20639
21103
  import { join as join15 } from "path";
20640
21104
  var REDACT_KEYS = /* @__PURE__ */ new Set([
20641
21105
  "content",
@@ -20661,8 +21125,8 @@ var WriteAheadLog = class {
20661
21125
  }
20662
21126
  this.walPath = join15(dir, "write_log.jsonl");
20663
21127
  if (!existsSync14(this.walPath)) {
20664
- const fd = openSync6(this.walPath, "w", 384);
20665
- closeSync6(fd);
21128
+ const fd = openSync7(this.walPath, "w", 384);
21129
+ closeSync7(fd);
20666
21130
  }
20667
21131
  }
20668
21132
  /**
@@ -20716,6 +21180,63 @@ function getWAL() {
20716
21180
 
20717
21181
  // src/mcp.ts
20718
21182
  init_client_events();
21183
+ init_which();
21184
+
21185
+ // ../engine/dist/core/collector-health.js
21186
+ init_paths();
21187
+ import { readFileSync as readFileSync33, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
21188
+ import { join as join43, dirname as dirname17 } from "node:path";
21189
+ var healthPath = () => join43(getDataDir(), "collector-health.json");
21190
+ function readCollectorHealth() {
21191
+ try {
21192
+ const h = JSON.parse(readFileSync33(healthPath(), "utf-8"));
21193
+ return h && h.v === 1 && typeof h.updatedAt === "number" ? h : null;
21194
+ } catch {
21195
+ return null;
21196
+ }
21197
+ }
21198
+ var STALE_AFTER_MS = 30 * 6e4;
21199
+ var CRASHLOOP_RESTARTS = 3;
21200
+ var ago = (ms) => {
21201
+ const m = Math.floor(ms / 6e4);
21202
+ if (m < 60)
21203
+ return `${m}m`;
21204
+ const h = Math.floor(m / 60);
21205
+ return h < 48 ? `${h}h` : `${Math.floor(h / 24)}d`;
21206
+ };
21207
+ function judgeHealth(h, now = Date.now()) {
21208
+ if (!h)
21209
+ return { ok: true, summary: null, reasons: [] };
21210
+ const reasons = [];
21211
+ const silent = now - h.updatedAt;
21212
+ if (silent > STALE_AFTER_MS) {
21213
+ reasons.push(`the collector has not reported for ${ago(silent)} \u2014 it may not be running`);
21214
+ }
21215
+ if (h.restartsLastHour >= CRASHLOOP_RESTARTS) {
21216
+ reasons.push(`it restarted ${h.restartsLastHour} times in the last hour`);
21217
+ }
21218
+ const targets = Object.entries(h.targets ?? {});
21219
+ const stale = targets.filter(([, t]) => t.lastOkAt === null || now - t.lastOkAt > STALE_AFTER_MS);
21220
+ if (targets.length > 0 && stale.length === targets.length) {
21221
+ const ranked = stale.map(([url, t]) => ({ url, t })).sort((a, b) => {
21222
+ const err = Number(!!b.t.lastError) - Number(!!a.t.lastError);
21223
+ if (err !== 0)
21224
+ return err;
21225
+ return (a.t.lastOkAt ?? 0) - (b.t.lastOkAt ?? 0);
21226
+ });
21227
+ const worst = ranked[0];
21228
+ const when = worst.t.lastOkAt ? `${ago(now - worst.t.lastOkAt)} ago` : "never";
21229
+ const why = worst.t.lastError ? ` (${worst.t.lastError})` : "";
21230
+ reasons.push(`nothing has synced since ${when}${why}`);
21231
+ }
21232
+ if (reasons.length === 0)
21233
+ return { ok: true, summary: null, reasons: [] };
21234
+ return {
21235
+ ok: false,
21236
+ summary: `chat-recall is not syncing: ${reasons.join("; ")}.`,
21237
+ reasons
21238
+ };
21239
+ }
20719
21240
 
20720
21241
  // src/recommendation-merge.ts
20721
21242
  var INSTRUCTION_KINDS = ["rule", "skill"];
@@ -20783,7 +21304,7 @@ var _codeindexAvailable = null;
20783
21304
  function codeindexAvailable() {
20784
21305
  if (_codeindexAvailable !== null) return _codeindexAvailable;
20785
21306
  try {
20786
- _execSync("command -v codeindex", { stdio: "ignore" });
21307
+ if (!isOnPath("codeindex")) throw new Error("codeindex not on PATH");
20787
21308
  _codeindexAvailable = true;
20788
21309
  } catch {
20789
21310
  _codeindexAvailable = false;
@@ -20799,8 +21320,10 @@ function withCodeindexHint(body, kind) {
20799
21320
  return body + hints[kind];
20800
21321
  }
20801
21322
  function remoteCredentials() {
21323
+ const fromEnv = envCredentials();
21324
+ if (fromEnv) return { base: fromEnv.serverUrl, token: fromEnv.token };
20802
21325
  try {
20803
- const raw = readFileSync37(join46(getDataDir(), "credentials.json"), "utf-8");
21326
+ const raw = readFileSync38(join49(getDataDir(), "credentials.json"), "utf-8");
20804
21327
  const parsed = JSON.parse(raw);
20805
21328
  if (Array.isArray(parsed.targets) && parsed.targets.length) {
20806
21329
  const valid = parsed.targets.filter((t) => t?.serverUrl);
@@ -21013,21 +21536,25 @@ var RecallTeamActivitySchema = z.object({
21013
21536
  member: z.string().optional().describe("Filter to one teammate by their user id (sub)"),
21014
21537
  since_days: z.number().optional().describe("Only activity in the last N days")
21015
21538
  });
21016
- var TASK_STATUSES = ["todo", "in_progress", "blocked", "done"];
21539
+ var TASK_STATUS_FILTER = ["todo", "in_progress", "done", "rejected"];
21540
+ var TASK_STATUS_SETTABLE = ["todo", "in_progress", "done"];
21017
21541
  var RecallTasksSchema = z.object({
21018
21542
  mine: z.boolean().optional().describe("Only tasks assigned to me"),
21019
21543
  project: z.string().optional().describe("Filter to one project_id"),
21020
- status: z.enum(TASK_STATUSES).optional().describe("Filter by status")
21544
+ status: z.enum(TASK_STATUS_FILTER).optional().describe("Filter by status"),
21545
+ detail: z.boolean().optional().describe("Return the full brief per task \u2014 the fix, the file locations and the agent prompt \u2014 plus how to claim and close one. Use this when you intend to DO the tasks, not just list them.")
21021
21546
  });
21022
21547
  var RecallTaskCreateSchema = z.object({
21023
21548
  title: z.string().describe("Task title"),
21024
21549
  description: z.string().optional(),
21025
21550
  project: z.string().optional().describe("project_id this task belongs to"),
21026
- assignee: z.string().optional().describe("Teammate user id (sub) to assign; omit to leave unassigned")
21551
+ assignee: z.string().optional().describe("Teammate user id (sub) to assign; omit to leave unassigned"),
21552
+ due: z.number().optional().describe("Due date, epoch milliseconds. Set one when the work is time-bound; omit otherwise."),
21553
+ linked_finding_id: z.string().optional().describe("The code finding this task comes from (ca_\u2026) \u2014 lets the card auto-close and dedupe.")
21027
21554
  });
21028
21555
  var RecallTaskUpdateSchema = z.object({
21029
21556
  id: z.string().describe("Task id (t_\u2026)"),
21030
- status: z.enum(TASK_STATUSES).optional(),
21557
+ status: z.enum(TASK_STATUS_SETTABLE).optional(),
21031
21558
  assignee: z.string().optional().describe("Reassign to this user id (sub); empty string unassigns"),
21032
21559
  title: z.string().optional(),
21033
21560
  comment: z.string().optional().describe("Add a comment to the task"),
@@ -21126,7 +21653,13 @@ var RecallWakeUpSchema = z.object({
21126
21653
  max_facts: z.number().optional().default(10).describe("How many high-importance classifier hits to include"),
21127
21654
  max_kg_facts: z.number().optional().default(15).describe("How many current knowledge-graph facts to include"),
21128
21655
  identity: z.string().optional().describe('Optional override for the identity blurb. Defaults to <data dir>/identity.txt or "AI coding assistant"'),
21129
- project_filter: z.string().optional().describe("Scope facts and KG entities to a project (substring match against project_path / entity name). Without this, facts are global and bleed across unrelated projects.")
21656
+ project_filter: z.string().optional().describe("Scope facts, KG entities and the task count to a project (substring match against project_path / entity name). Without this, facts are global and bleed across unrelated projects."),
21657
+ // Alias. Its sibling tools (recall_tasks, recall_project_context) all take
21658
+ // `project`, and passing that here used to be silently ignored: no error, and
21659
+ // a WHOLE-TENANT answer that looks exactly like a scoped one. That is the
21660
+ // failure this tool exists to prevent — being told about another project's
21661
+ // backlog — so accepting both names is worth more than naming purity.
21662
+ project: z.string().optional().describe("Alias for project_filter.")
21130
21663
  });
21131
21664
  var RecallSetSchema = z.object({
21132
21665
  key: z.string().describe('Key name (e.g. "current-pr", "default-test-runner")'),
@@ -21164,7 +21697,7 @@ var server = new Server(
21164
21697
  name: "chat-recall",
21165
21698
  // Resolves to packages/cli/package.json from both src/ and the bundled dist/
21166
21699
  version: JSON.parse(
21167
- readFileSync37(new URL("../package.json", import.meta.url), "utf-8")
21700
+ readFileSync38(new URL("../package.json", import.meta.url), "utf-8")
21168
21701
  ).version
21169
21702
  },
21170
21703
  {
@@ -21209,6 +21742,59 @@ var LEAN_TOOLS = /* @__PURE__ */ new Set([
21209
21742
  function toolProfile() {
21210
21743
  return (process.env.CHAT_RECALL_MCP_PROFILE || "").toLowerCase() === "full" ? "full" : "lean";
21211
21744
  }
21745
+ var WRITE_TOOLS = /* @__PURE__ */ new Set([
21746
+ "recall_index",
21747
+ // indexes and ships new rows
21748
+ "recall_code_index",
21749
+ // runs the analyzer, then syncs findings
21750
+ "recall_kg_add",
21751
+ "recall_kg_invalidate",
21752
+ "recall_diary_write",
21753
+ "recall_decision_record",
21754
+ "recall_set",
21755
+ "recall_task_create",
21756
+ "recall_task_update",
21757
+ "recall_task_comment",
21758
+ "recall_security_dismiss",
21759
+ "recall_reclassify",
21760
+ // rewrites classifier tags on indexed chunks
21761
+ "recall_regenerate_summary",
21762
+ // overwrites a stored summary
21763
+ "recall_rename_session"
21764
+ // overwrites the session name
21765
+ ]);
21766
+ var DESTRUCTIVE_TOOLS = /* @__PURE__ */ new Set([
21767
+ "recall_kg_invalidate",
21768
+ "recall_reclassify",
21769
+ "recall_regenerate_summary",
21770
+ "recall_rename_session",
21771
+ "recall_security_dismiss"
21772
+ ]);
21773
+ var IDEMPOTENT_TOOLS = /* @__PURE__ */ new Set([
21774
+ "recall_set",
21775
+ "recall_kg_invalidate",
21776
+ "recall_rename_session",
21777
+ "recall_security_dismiss",
21778
+ "recall_reclassify",
21779
+ "recall_task_update"
21780
+ ]);
21781
+ function toolTitle(name) {
21782
+ const words = name.replace(/^recall_/, "").replace(/_/g, " ");
21783
+ return words.charAt(0).toUpperCase() + words.slice(1);
21784
+ }
21785
+ function annotate(t) {
21786
+ const writes = WRITE_TOOLS.has(t.name);
21787
+ return {
21788
+ ...t,
21789
+ annotations: {
21790
+ title: toolTitle(t.name),
21791
+ readOnlyHint: !writes,
21792
+ destructiveHint: DESTRUCTIVE_TOOLS.has(t.name),
21793
+ idempotentHint: writes ? IDEMPOTENT_TOOLS.has(t.name) : true,
21794
+ openWorldHint: true
21795
+ }
21796
+ };
21797
+ }
21212
21798
  server.setRequestHandler(ListToolsRequestSchema, async () => {
21213
21799
  const all = [
21214
21800
  {
@@ -21575,17 +22161,28 @@ or standup/status. Filter by project, by member, or by recency (since_days).`,
21575
22161
  },
21576
22162
  {
21577
22163
  name: "recall_tasks",
21578
- description: `List collaborative team tasks (the shared task board).
22164
+ description: `List the task board \u2014 and, with detail, the work itself.
22165
+
22166
+ Without \`detail\`: one line per card (title, id, assignee). Use for "what's on
22167
+ the team's plate", "my tasks", or to find a task id to update.
22168
+
22169
+ With \`detail: true\`: the full brief per open card \u2014 the fix, the file
22170
+ locations and the agent prompt that the auto-filer already wrote \u2014 plus how to
22171
+ claim a card and how it gets closed. Use that whenever you intend to DO the
22172
+ work rather than report on it.
21579
22173
 
21580
- Team-visible tasks with status, assignee, and project. Use for "what's on the
21581
- team's plate", "my tasks", or to find a task id to update. Filter by mine /
21582
- project / status.`,
22174
+ Closing is earned: 'done' is refused without a linked session. Rejecting is the
22175
+ user's call, never yours.`,
21583
22176
  inputSchema: {
21584
22177
  type: "object",
21585
22178
  properties: {
21586
22179
  mine: { type: "boolean", description: "Only tasks assigned to me" },
21587
22180
  project: { type: "string", description: "Filter to one project_id" },
21588
- status: { type: "string", enum: [...TASK_STATUSES], description: "Filter by status" }
22181
+ status: { type: "string", enum: [...TASK_STATUS_FILTER], description: "Filter by status" },
22182
+ detail: {
22183
+ type: "boolean",
22184
+ description: "Return the full brief per task \u2014 the fix, the file locations and the agent prompt \u2014 plus how to claim and close one. Use this when you intend to DO the tasks, not just list them."
22185
+ }
21589
22186
  }
21590
22187
  }
21591
22188
  },
@@ -21603,7 +22200,9 @@ lets the board verify the work actually shipped.`,
21603
22200
  title: { type: "string", description: "Task title" },
21604
22201
  description: { type: "string" },
21605
22202
  project: { type: "string", description: "project_id this task belongs to" },
21606
- assignee: { type: "string", description: "Teammate user id (sub) to assign" }
22203
+ assignee: { type: "string", description: "Teammate user id (sub) to assign" },
22204
+ due: { type: "number", description: "Due date, epoch milliseconds. Set one when the work is time-bound; omit otherwise." },
22205
+ linked_finding_id: { type: "string", description: "The code finding this task comes from (ca_\u2026). Setting it lets the card close itself when a re-index stops reporting the finding, and dedupes against an existing card for the same finding." }
21607
22206
  },
21608
22207
  required: ["title"]
21609
22208
  }
@@ -21623,7 +22222,7 @@ WORKFLOW \u2014 do this whenever the user asks you to work on a task from the bo
21623
22222
  type: "object",
21624
22223
  properties: {
21625
22224
  id: { type: "string", description: "Task id (t_\u2026)" },
21626
- status: { type: "string", enum: [...TASK_STATUSES] },
22225
+ status: { type: "string", enum: [...TASK_STATUS_SETTABLE], description: "Set the card's state. There is no 'rejected' here on purpose: rejecting is the user's verdict, not yours." },
21627
22226
  assignee: { type: "string", description: "Reassign to this user id (sub)" },
21628
22227
  title: { type: "string" },
21629
22228
  comment: { type: "string", description: "Add a comment" },
@@ -21922,7 +22521,8 @@ the start of a session to "remember who you are and what's true right now".`,
21922
22521
  max_facts: { type: "number", default: 10 },
21923
22522
  max_kg_facts: { type: "number", default: 15 },
21924
22523
  identity: { type: "string" },
21925
- project_filter: { type: "string", description: "Scope facts and KG entities to a project (substring match). Without it, facts are global." }
22524
+ project_filter: { type: "string", description: "Scope facts, KG entities and the task count to a project (substring match). Without it, facts are global." },
22525
+ project: { type: "string", description: "Alias for project_filter \u2014 the name every sibling tool uses." }
21926
22526
  }
21927
22527
  }
21928
22528
  },
@@ -22115,7 +22715,7 @@ Complements recall_memory_search: search finds candidates, this reads the specif
22115
22715
  inputSchema: { type: "object", properties: {} }
22116
22716
  }
22117
22717
  ];
22118
- if (toolProfile() === "full") return { tools: all };
22718
+ if (toolProfile() === "full") return { tools: all.map(annotate) };
22119
22719
  const lean = all.filter((t) => LEAN_TOOLS.has(t.name));
22120
22720
  const hidden = all.length - lean.length;
22121
22721
  if (hidden > 0) {
@@ -22125,7 +22725,7 @@ Complements recall_memory_search: search finds candidates, this reads the specif
22125
22725
  inputSchema: { type: "object", properties: {} }
22126
22726
  });
22127
22727
  }
22128
- return { tools: lean };
22728
+ return { tools: lean.map(annotate) };
22129
22729
  });
22130
22730
  var syncStateCache = null;
22131
22731
  var SYNC_STATE_TTL_MS = 5 * 60 * 1e3;
@@ -22157,8 +22757,13 @@ async function syncState() {
22157
22757
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
22158
22758
  const result = await dispatchTool(request);
22159
22759
  try {
22160
- const banner = stalenessBanner(await syncState());
22161
- if (!banner) return result;
22760
+ const health = judgeHealth(readCollectorHealth());
22761
+ const banners = [
22762
+ health.ok ? null : `\u26A0 ${health.summary} Recent work may be missing from these answers. Tell the user, and suggest \`chat-recall doctor\`.`,
22763
+ stalenessBanner(await syncState())
22764
+ ].filter(Boolean);
22765
+ if (banners.length === 0) return result;
22766
+ const banner = banners.join("\n\n");
22162
22767
  const content = result.content;
22163
22768
  if (!Array.isArray(content) || !content.length) return result;
22164
22769
  return { ...result, content: [{ type: "text", text: banner }, ...content] };
@@ -22288,6 +22893,11 @@ async function dispatchTool(request) {
22288
22893
  `FTS5 chunks: ${status.totalChunks}`,
22289
22894
  `Raw archives: ${sync.rawArchived}`
22290
22895
  ];
22896
+ {
22897
+ const { updateNotice: updateNotice2 } = await Promise.resolve().then(() => (init_update_notice(), update_notice_exports));
22898
+ const notice = updateNotice2();
22899
+ if (notice) lines.push("", `\u26A0 ${notice}`);
22900
+ }
22291
22901
  if (sync.newestSessionAgeMs !== null) {
22292
22902
  const mins = Math.round(sync.newestSessionAgeMs / 6e4);
22293
22903
  lines.push(`Freshness: newest synced session ${mins} min ago`);
@@ -23016,21 +23626,67 @@ _Edits stayed local or window was off \u2014 try increasing buffer_minutes._` }]
23016
23626
  if (params.status) qs.status = params.status;
23017
23627
  const { tasks } = await remoteGetQS("/api/tasks", qs);
23018
23628
  if (!tasks || tasks.length === 0) return { content: [{ type: "text", text: "No team tasks match." }] };
23019
- const lines = tasks.map((t) => `- [${t.status}] ${t.title} \`${t.id}\`${t.projectId ? ` \xB7 ${t.projectId}` : ""}${t.assigneeSub ? ` \xB7 @${t.assigneeSub.slice(0, 8)}` : " \xB7 unassigned"}`);
23020
- return { content: [{ type: "text", text: `# Team tasks (${tasks.length})
23629
+ if (!params.detail) {
23630
+ const lines = tasks.map((t) => `- [${t.status}] ${t.title} \`${t.id}\`${t.projectId ? ` \xB7 ${t.projectId}` : ""}${t.assigneeSub ? ` \xB7 @${t.assigneeSub.slice(0, 8)}` : " \xB7 unassigned"}`);
23631
+ return { content: [{ type: "text", text: `# Team tasks (${tasks.length})
23632
+
23633
+ ${lines.join("\n")}
23021
23634
 
23022
- ${lines.join("\n")}` }] };
23635
+ _Call again with \`detail: true\` for the full brief (fix, locations, agent prompt) so you can start work._` }] };
23636
+ }
23637
+ const open3 = tasks.filter((t) => t.status !== "done" && t.status !== "rejected");
23638
+ const claimed = open3.filter((t) => t.status === "in_progress");
23639
+ const blocks = open3.map((t, i) => {
23640
+ const head = `## ${i + 1}. ${t.title}
23641
+
23642
+ - id: \`${t.id}\` \xB7 status: ${t.status}${t.projectId ? ` \xB7 project: ${t.projectId}` : ""}${t.linkedSessionId ? ` \xB7 already claimed by session ${t.linkedSessionId.slice(0, 8)}` : ""}`;
23643
+ const body = (t.description || "").trim();
23644
+ return body ? `${head}
23645
+
23646
+ ${body}` : head;
23647
+ });
23648
+ const howTo = [
23649
+ "---",
23650
+ "",
23651
+ "### Working these tasks",
23652
+ "",
23653
+ '1. Claim one before you touch code: `recall_task_update` with `id`, `status: "in_progress"`',
23654
+ " and `linked_session_id` set to YOUR current session id.",
23655
+ "2. Do the work. Each brief above carries the fix and, where the task came from a code finding,",
23656
+ " an agent prompt written for exactly this.",
23657
+ '3. Close it with `recall_task_update` `status: "done"` and a `comment` saying what you changed.',
23658
+ "",
23659
+ "The session link is not bookkeeping: **done is refused without it**. A card asserts a problem",
23660
+ "exists in the code, so closing one asserts the code changed, and the board shows the files,",
23661
+ "lines and commits from your session as the evidence. A person cannot mark these done by hand.",
23662
+ "",
23663
+ "If a task is wrong \u2014 not a real problem, or not worth doing \u2014 do NOT close it. Tell the user;",
23664
+ "rejecting is their call, and it dismisses the finding so it stops being re-filed.",
23665
+ "",
23666
+ "Auto-filed cards also close themselves once a re-index no longer reports their finding, so a",
23667
+ "real fix ends the task either way."
23668
+ ].join("\n");
23669
+ const header = `# Tasks ready to work (${open3.length}${tasks.length !== open3.length ? ` of ${tasks.length}` : ""})${params.project ? ` \xB7 ${params.project}` : ""}${claimed.length ? `
23670
+
23671
+ _${claimed.length} already in progress \u2014 check the session link before starting one of those._` : ""}`;
23672
+ return { content: [{ type: "text", text: `${header}
23673
+
23674
+ ${blocks.join("\n\n")}
23675
+
23676
+ ${howTo}` }] };
23023
23677
  }
23024
23678
  case "recall_task_create": {
23025
23679
  const params = RecallTaskCreateSchema.parse(args);
23026
23680
  requireRemote();
23027
- const { task } = await remotePost("/api/tasks", {
23681
+ const { task, deduped } = await remotePost("/api/tasks", {
23028
23682
  title: params.title,
23029
23683
  description: params.description,
23030
23684
  projectId: params.project,
23031
- assigneeSub: params.assignee ?? null
23685
+ assigneeSub: params.assignee ?? null,
23686
+ due: params.due,
23687
+ linkedFindingId: params.linked_finding_id
23032
23688
  });
23033
- return { content: [{ type: "text", text: `Created task \`${task.id}\`: ${task.title}` }] };
23689
+ return { content: [{ type: "text", text: deduped ? `A task for that finding already exists: \`${task.id}\`: ${task.title}` : `Created task \`${task.id}\`: ${task.title}` }] };
23034
23690
  }
23035
23691
  case "recall_task_update": {
23036
23692
  const params = RecallTaskUpdateSchema.parse(args);
@@ -23713,8 +24369,8 @@ Subject: ${params.subject}`;
23713
24369
  `];
23714
24370
  for (const r of rows) {
23715
24371
  const preview = r.value.length > 80 ? r.value.slice(0, 80) + "\u2026" : r.value;
23716
- const ago2 = Math.floor((Date.now() - r.updated_at) / 1e3);
23717
- lines.push(`- **${r.scope}:${r.key}** \u2014 ${preview} _(${ago2}s ago)_`);
24372
+ const ago3 = Math.floor((Date.now() - r.updated_at) / 1e3);
24373
+ lines.push(`- **${r.scope}:${r.key}** \u2014 ${preview} _(${ago3}s ago)_`);
23718
24374
  }
23719
24375
  return { content: [{ type: "text", text: lines.join("\n") }] };
23720
24376
  }
@@ -23724,11 +24380,11 @@ Subject: ${params.subject}`;
23724
24380
  { scope, key: params.key }
23725
24381
  );
23726
24382
  if (!row) return { content: [{ type: "text", text: `(no value at ${scope}:${params.key})` }] };
23727
- const ago = Math.floor((Date.now() - row.updated_at) / 1e3);
24383
+ const ago2 = Math.floor((Date.now() - row.updated_at) / 1e3);
23728
24384
  return {
23729
24385
  content: [{
23730
24386
  type: "text",
23731
- text: `${scope}:${params.key} (set ${ago}s ago)
24387
+ text: `${scope}:${params.key} (set ${ago2}s ago)
23732
24388
 
23733
24389
  ${row.value}`
23734
24390
  }]
@@ -23736,13 +24392,14 @@ ${row.value}`
23736
24392
  }
23737
24393
  // ── Wake-up context ────────────────────────────────────────
23738
24394
  case "recall_wake_up": {
23739
- const params = RecallWakeUpSchema.parse(args);
24395
+ const parsedWake = RecallWakeUpSchema.parse(args);
24396
+ const params = { ...parsedWake, project_filter: parsedWake.project_filter ?? parsedWake.project };
23740
24397
  let identity = params.identity ?? "AI coding assistant";
23741
24398
  if (!params.identity) {
23742
24399
  const idFile = getIdentityFilePath();
23743
- if (existsSync43(idFile)) {
24400
+ if (existsSync44(idFile)) {
23744
24401
  try {
23745
- identity = readFileSync37(idFile, "utf-8").trim() || identity;
24402
+ identity = readFileSync38(idFile, "utf-8").trim() || identity;
23746
24403
  } catch {
23747
24404
  }
23748
24405
  }
@@ -23766,8 +24423,12 @@ ${row.value}`
23766
24423
  });
23767
24424
  const ot = wake.openTasks;
23768
24425
  if (ot && ot.total > 0) {
24426
+ const scope = ot.scope;
23769
24427
  lines.push("## Task board");
23770
- lines.push(` ${ot.total} open task(s)${ot.auto ? ` \u2014 ${ot.auto} auto-filed from code findings` : ""}. List them with recall_tasks; when you start one, set it in_progress and pass your session id as linked_session_id.`);
24428
+ lines.push(` ${ot.total} open task(s)${scope ? ` in \`${scope}\`` : ""}${ot.auto ? ` \u2014 ${ot.auto} auto-filed from code findings` : ""}.`);
24429
+ lines.push(` Offer to work them: \`recall_tasks\` with \`detail: true\`${scope ? ` and \`project\`` : ""} returns each task's fix, file locations and agent prompt.`);
24430
+ lines.push(' Claim one before editing: `recall_task_update` with `status: "in_progress"` and your session id as `linked_session_id`.');
24431
+ lines.push(" You close them, not the user: `done` is refused without a linked session, because the board shows the diff behind it.");
23771
24432
  lines.push("");
23772
24433
  }
23773
24434
  if (wake.highFacts.length > 0) {
@@ -24151,6 +24812,21 @@ function startBackgroundSync() {
24151
24812
  setTimeout(() => {
24152
24813
  void tick("full");
24153
24814
  }, 15e3).unref();
24815
+ const updateTick = async () => {
24816
+ try {
24817
+ const { refreshUpdateCheck: refreshUpdateCheck2, updateNotice: updateNotice2 } = await Promise.resolve().then(() => (init_update_notice(), update_notice_exports));
24818
+ await refreshUpdateCheck2();
24819
+ const notice = updateNotice2();
24820
+ if (notice) console.error(`[mcp] ${notice}`);
24821
+ } catch {
24822
+ }
24823
+ };
24824
+ setTimeout(() => {
24825
+ void updateTick();
24826
+ }, 2e4).unref();
24827
+ setInterval(() => {
24828
+ void updateTick();
24829
+ }, 6 * 60 * 60 * 1e3).unref();
24154
24830
  }
24155
24831
  function installCrashGuards() {
24156
24832
  process.on("unhandledRejection", (reason) => {