chat-recall 0.6.8 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/watch.js CHANGED
@@ -115,6 +115,25 @@ var init_tool_backend = __esm({
115
115
  }
116
116
  });
117
117
 
118
+ // ../engine/dist/parsers/banners.js
119
+ function stripInjectedBanners(text) {
120
+ let result = text;
121
+ for (const re of INJECTED_BANNERS)
122
+ result = result.replace(re, " ");
123
+ return result;
124
+ }
125
+ var INJECTED_BANNERS;
126
+ var init_banners = __esm({
127
+ "../engine/dist/parsers/banners.js"() {
128
+ "use strict";
129
+ INJECTED_BANNERS = [
130
+ /MCP issues detected\. ?Run \/mcp list for status\.?/g,
131
+ /Context low[^\n]*Run \/compact[^\n]*/g,
132
+ /API Error:[^\n]{0,120}/g
133
+ ];
134
+ }
135
+ });
136
+
118
137
  // ../engine/dist/core/project-path-match.js
119
138
  function canonicalProjectPath(p) {
120
139
  return (p || "").trim().replace(/\\/g, "/").replace(/[-_]/g, "/").replace(/\/{2,}/g, "/").replace(/\/+$/, "").toLowerCase();
@@ -5346,929 +5365,291 @@ var init_tool_paths = __esm({
5346
5365
  }
5347
5366
  });
5348
5367
 
5349
- // ../engine/dist/core/secret-redactor.js
5350
- import { statSync as statSync3 } from "fs";
5351
- function secretOf(rule, m) {
5352
- const raw = rule.secret != null ? m[rule.secret] : m[0];
5353
- if (raw == null)
5354
- return null;
5355
- if (rule.where && !rule.where(raw))
5356
- return null;
5357
- return raw;
5368
+ // ../engine/dist/core/project-dir-name.js
5369
+ import { readdirSync as readdirSync4 } from "node:fs";
5370
+ function looksWindowsEncoded(dirName) {
5371
+ return /^[A-Za-z]--/.test(dirName);
5358
5372
  }
5359
- function looksLikeSecretToken(t) {
5360
- if (t.length < 32)
5361
- return false;
5362
- if (/^[a-f0-9]+$/i.test(t))
5363
- return true;
5364
- return /[A-Z]/.test(t) && /[a-z]/.test(t) && /[0-9]/.test(t);
5373
+ function decodeProjectDirName(dirName) {
5374
+ if (!dirName)
5375
+ return "";
5376
+ if (looksWindowsEncoded(dirName)) {
5377
+ const drive = dirName[0].toUpperCase();
5378
+ const rest = dirName.slice(3).replace(/-/g, "\\");
5379
+ return `${drive}:\\${rest}`;
5380
+ }
5381
+ return "/" + dirName.replace(/^-/, "").replace(/-/g, "/");
5365
5382
  }
5366
- function findContextualSecrets(text) {
5367
- if (!text)
5368
- return [];
5369
- const ranges = [];
5370
- let m;
5371
- SECRET_CONTEXT_RE.lastIndex = 0;
5372
- while ((m = SECRET_CONTEXT_RE.exec(text)) !== null) {
5373
- const winStart = m.index + m[0].length;
5374
- const window = text.slice(winStart, winStart + 80);
5375
- HIGH_ENTROPY_TOKEN_RE.lastIndex = 0;
5376
- let tm;
5377
- while ((tm = HIGH_ENTROPY_TOKEN_RE.exec(window)) !== null) {
5378
- if (!looksLikeSecretToken(tm[0]))
5379
- continue;
5380
- if (/^[a-f0-9]+$/i.test(tm[0]) && /\b(?:commit|sha-?\d*|hash|digest|checksum|uuid|guid|etag|blob|ref)\b/i.test(window.slice(0, tm.index))) {
5381
- continue;
5382
- }
5383
- ranges.push({ start: winStart + tm.index, end: winStart + tm.index + tm[0].length });
5383
+ function resolveProjectDirName(dirName, deps = {}) {
5384
+ const structural = decodeProjectDirName(dirName);
5385
+ if (!dirName)
5386
+ return structural;
5387
+ if (looksWindowsEncoded(dirName))
5388
+ return structural;
5389
+ const readdir3 = deps.readdir ?? ((p) => readdirSync4(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name));
5390
+ const encodeName = (name) => name.replace(/[-_.]/g, "-");
5391
+ let remaining = dirName.replace(/^-/, "");
5392
+ let current = "/";
5393
+ while (remaining) {
5394
+ let children;
5395
+ try {
5396
+ children = readdir3(current);
5397
+ } catch {
5384
5398
  break;
5385
5399
  }
5386
- }
5387
- ranges.sort((a, b) => a.start - b.start);
5388
- const out = [];
5389
- for (const r of ranges) {
5390
- const last = out[out.length - 1];
5391
- if (last && r.start < last.end) {
5392
- last.end = Math.max(last.end, r.end);
5393
- continue;
5400
+ let best = "";
5401
+ for (const child of children) {
5402
+ const encoded = encodeName(child);
5403
+ if (!encoded || encoded.length < best.length)
5404
+ continue;
5405
+ if (!remaining.startsWith(encoded))
5406
+ continue;
5407
+ const next = remaining[encoded.length];
5408
+ if (next !== void 0 && next !== "-")
5409
+ continue;
5410
+ if (encoded.length > best.length)
5411
+ best = child;
5394
5412
  }
5395
- out.push({ ...r });
5413
+ if (!best)
5414
+ break;
5415
+ current = current === "/" ? `/${best}` : `${current}/${best}`;
5416
+ remaining = remaining.slice(encodeName(best).length).replace(/^-/, "");
5396
5417
  }
5397
- return out;
5398
- }
5399
- function isEnvRedactionRequested() {
5400
- const v = process.env.CHAT_RECALL_REDACT_INDEX;
5401
- if (v === void 0)
5402
- return null;
5403
- return v === "1" || v === "true" || v === "yes";
5418
+ return remaining ? structural : current;
5404
5419
  }
5405
- function settingsView() {
5406
- let mtimeMs = -1;
5407
- try {
5408
- mtimeMs = statSync3(settingsFilePath()).mtimeMs;
5409
- } catch {
5420
+ var init_project_dir_name = __esm({
5421
+ "../engine/dist/core/project-dir-name.js"() {
5422
+ "use strict";
5410
5423
  }
5411
- if (cache && cache.mtimeMs === mtimeMs)
5412
- return cache;
5413
- let enabled = false;
5414
- const extra = [];
5415
- try {
5416
- const s = loadSettings();
5417
- enabled = !!s.privacy?.redactIndex;
5418
- for (const r of s.privacy?.redactionRules ?? []) {
5419
- try {
5420
- extra.push({ label: r.label, pattern: new RegExp(r.pattern, "g") });
5421
- } catch {
5422
- }
5424
+ });
5425
+
5426
+ // ../engine/dist/core/model-pricing.js
5427
+ function pricingFor(modelId) {
5428
+ if (!modelId)
5429
+ return null;
5430
+ const lower = modelId.toLowerCase();
5431
+ let best = null;
5432
+ let bestLen = -1;
5433
+ for (const [prefix, entry] of Object.entries(TABLE)) {
5434
+ if (lower.startsWith(prefix.toLowerCase()) && prefix.length > bestLen) {
5435
+ best = entry;
5436
+ bestLen = prefix.length;
5423
5437
  }
5424
- } catch {
5425
- }
5426
- cache = { mtimeMs, enabled, rules: [...DEFAULT_REDACTION_RULES, ...extra] };
5427
- return cache;
5428
- }
5429
- function compileServerRule(spec) {
5430
- const name = spec.name || "(unnamed)";
5431
- if (!spec.regex)
5432
- return { name, reason: "empty pattern" };
5433
- if (spec.regex.length > MAX_PATTERN_LEN)
5434
- return { name, reason: `pattern longer than ${MAX_PATTERN_LEN} chars` };
5435
- let re;
5436
- try {
5437
- const flags = "g" + (spec.flags || "").replace(/[^ims]/g, "");
5438
- re = new RegExp(spec.regex, flags);
5439
- } catch (e) {
5440
- return { name, reason: `invalid regex: ${e instanceof Error ? e.message : e}` };
5441
5438
  }
5442
- if (re.test(""))
5443
- return { name, reason: "matches the empty string (would redact everything)" };
5444
- re.lastIndex = 0;
5445
- if (re.test(CANARY))
5446
- return { name, reason: "matches benign text \u2014 too broad" };
5447
- re.lastIndex = 0;
5448
- return { label: `${spec.source === "pack" ? "pack" : "tenant"}:${name}`, pattern: re };
5439
+ return best;
5449
5440
  }
5450
- function installServerRulePack(pack) {
5451
- const rejected = [];
5452
- const rules = [];
5453
- for (const spec of pack.rules || []) {
5454
- if (rules.length >= MAX_PACK_RULES) {
5455
- rejected.push({ name: spec.name || "(unnamed)", reason: `pack exceeds ${MAX_PACK_RULES} rules` });
5441
+ function estimateCostUsdOrNull(modelsUsed, tokens) {
5442
+ let best = null;
5443
+ let bestRate = -1;
5444
+ for (const m of modelsUsed) {
5445
+ if (!m || m === "<synthetic>")
5456
5446
  continue;
5447
+ const p = pricingFor(m);
5448
+ if (p && p.output > bestRate) {
5449
+ best = p;
5450
+ bestRate = p.output;
5457
5451
  }
5458
- const out = compileServerRule(spec);
5459
- if ("reason" in out)
5460
- rejected.push(out);
5461
- else
5462
- rules.push(out);
5463
- }
5464
- const version = pack.version || "";
5465
- serverPack = { version, rules };
5466
- return { version, accepted: rules.length, rejected };
5467
- }
5468
- function serverRulePackVersion() {
5469
- return serverPack ? serverPack.version || "unversioned" : null;
5470
- }
5471
- function activeRules() {
5472
- const base2 = settingsView().rules;
5473
- return serverPack && serverPack.rules.length > 0 ? [...base2, ...serverPack.rules] : base2;
5474
- }
5475
- function isRedactionEnabled() {
5476
- const env = isEnvRedactionRequested();
5477
- if (env !== null)
5478
- return env;
5479
- return settingsView().enabled;
5480
- }
5481
- function redactSecrets(text, opts = {}) {
5482
- if (!opts.force && !isRedactionEnabled())
5483
- return text;
5484
- if (!text)
5485
- return text;
5486
- const rules = opts.rules || activeRules();
5487
- let out = text;
5488
- for (const r of rules) {
5489
- out = out.replace(r.pattern, (...args) => {
5490
- const whole = args[0];
5491
- const m = args.slice(0, args.length);
5492
- const secret = secretOf(r, m);
5493
- if (secret == null)
5494
- return whole;
5495
- if (opts.count)
5496
- opts.count.redactions++;
5497
- return whole.slice(0, whole.length - secret.length) + `[REDACTED:${r.label}]`;
5498
- });
5499
- }
5500
- const ctx = findContextualSecrets(out);
5501
- for (let i = ctx.length - 1; i >= 0; i--) {
5502
- const { start, end } = ctx[i];
5503
- if (opts.count)
5504
- opts.count.redactions++;
5505
- out = out.slice(0, start) + "[REDACTED:secret-context]" + out.slice(end);
5506
5452
  }
5507
- return out;
5508
- }
5509
- function maskSecret(s) {
5510
- if (!s)
5511
- return "";
5512
- if (s.length <= 4)
5513
- return "*".repeat(s.length);
5514
- return "*".repeat(Math.min(s.length - 4, 60)) + s.slice(-4);
5453
+ if (!best)
5454
+ return null;
5455
+ const nonCachedInput = Math.max(0, tokens.inputTokens - tokens.cacheReadTokens - tokens.cacheCreationTokens);
5456
+ const cost = nonCachedInput / 1e6 * best.input + tokens.outputTokens / 1e6 * best.output + tokens.cacheReadTokens / 1e6 * best.cacheRead + tokens.cacheCreationTokens / 1e6 * best.cacheCreate;
5457
+ return Math.round(cost * 100) / 100;
5515
5458
  }
5516
- function globalRegexFor(pattern) {
5517
- const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
5518
- const key2 = pattern.source + "\0" + flags;
5519
- let re = globalRegexCache.get(key2);
5520
- if (!re) {
5521
- re = new RegExp(pattern.source, flags);
5522
- globalRegexCache.set(key2, re);
5523
- }
5524
- return re;
5459
+ function estimateCostUsd(modelsUsed, tokens) {
5460
+ return estimateCostUsdOrNull(modelsUsed, tokens) ?? 0;
5525
5461
  }
5526
- function makeLineResolver(text) {
5527
- const starts = [];
5528
- for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1))
5529
- starts.push(i);
5530
- return (index) => {
5531
- let lo = 0, hi = starts.length;
5532
- while (lo < hi) {
5533
- const mid = lo + hi >> 1;
5534
- if (starts[mid] < index)
5535
- lo = mid + 1;
5536
- else
5537
- hi = mid;
5538
- }
5539
- return lo + 1;
5540
- };
5541
- }
5542
- function scanTextForFindings(text, opts = {}) {
5543
- if (!text)
5544
- return [];
5545
- const rules = opts.rules || activeRules();
5546
- const findings = [];
5547
- const seenSpans = /* @__PURE__ */ new Set();
5548
- const lineAt = makeLineResolver(text);
5549
- for (const r of rules) {
5550
- const re = globalRegexFor(r.pattern);
5551
- re.lastIndex = 0;
5552
- let m;
5553
- while ((m = re.exec(text)) !== null) {
5554
- const secret = secretOf(r, m);
5555
- if (secret == null) {
5556
- if (re.lastIndex === m.index)
5557
- re.lastIndex++;
5558
- continue;
5559
- }
5560
- const at = m.index + (m[0].length - secret.length);
5561
- const span = `${at}:${secret.length}`;
5562
- if (!seenSpans.has(span)) {
5563
- seenSpans.add(span);
5564
- findings.push({ rule: r.label, line: lineAt(at), preview: maskSecret(secret) });
5462
+ var TABLE, METADATA_VERSION;
5463
+ var init_model_pricing = __esm({
5464
+ "../engine/dist/core/model-pricing.js"() {
5465
+ "use strict";
5466
+ TABLE = {
5467
+ // Anthropic — Claude 4.x family
5468
+ "claude-opus-4-7": {
5469
+ input: 15,
5470
+ output: 75,
5471
+ cacheRead: 1.5,
5472
+ cacheCreate: 18.75,
5473
+ lastReviewed: "2026-01-15",
5474
+ source: "Anthropic public pricing (Opus 4.x)"
5475
+ },
5476
+ "claude-opus-4-6": {
5477
+ input: 15,
5478
+ output: 75,
5479
+ cacheRead: 1.5,
5480
+ cacheCreate: 18.75,
5481
+ lastReviewed: "2026-01-15",
5482
+ source: "Anthropic public pricing (Opus 4.x)"
5483
+ },
5484
+ "claude-opus-4": {
5485
+ input: 15,
5486
+ output: 75,
5487
+ cacheRead: 1.5,
5488
+ cacheCreate: 18.75,
5489
+ lastReviewed: "2026-01-15",
5490
+ source: "Anthropic public pricing (Opus 4.x)"
5491
+ },
5492
+ "claude-sonnet-4-6": {
5493
+ input: 3,
5494
+ output: 15,
5495
+ cacheRead: 0.3,
5496
+ cacheCreate: 3.75,
5497
+ lastReviewed: "2026-01-15",
5498
+ source: "Anthropic public pricing (Sonnet 4.x)"
5499
+ },
5500
+ "claude-sonnet-4": {
5501
+ input: 3,
5502
+ output: 15,
5503
+ cacheRead: 0.3,
5504
+ cacheCreate: 3.75,
5505
+ lastReviewed: "2026-01-15",
5506
+ source: "Anthropic public pricing (Sonnet 4.x)"
5507
+ },
5508
+ "claude-haiku-4-5": {
5509
+ input: 1,
5510
+ output: 5,
5511
+ cacheRead: 0.1,
5512
+ cacheCreate: 1.25,
5513
+ lastReviewed: "2026-01-15",
5514
+ source: "Anthropic public pricing (Haiku 4.5)"
5515
+ },
5516
+ // Anthropic — older Claude 3.x (kept for back-compat with historical transcripts)
5517
+ "claude-3-7-sonnet": {
5518
+ input: 3,
5519
+ output: 15,
5520
+ cacheRead: 0.3,
5521
+ cacheCreate: 3.75,
5522
+ lastReviewed: "2026-01-15",
5523
+ source: "Anthropic public pricing"
5524
+ },
5525
+ "claude-3-5-sonnet": {
5526
+ input: 3,
5527
+ output: 15,
5528
+ cacheRead: 0.3,
5529
+ cacheCreate: 3.75,
5530
+ lastReviewed: "2026-01-15",
5531
+ source: "Anthropic public pricing"
5532
+ },
5533
+ "claude-3-5-haiku": {
5534
+ input: 0.8,
5535
+ output: 4,
5536
+ cacheRead: 0.08,
5537
+ cacheCreate: 1,
5538
+ lastReviewed: "2026-01-15",
5539
+ source: "Anthropic public pricing"
5540
+ },
5541
+ "claude-3-opus": {
5542
+ input: 15,
5543
+ output: 75,
5544
+ cacheRead: 1.5,
5545
+ cacheCreate: 18.75,
5546
+ lastReviewed: "2026-01-15",
5547
+ source: "Anthropic public pricing"
5548
+ },
5549
+ // OpenAI — current GPT-* (used by Codex CLI)
5550
+ "gpt-5": {
5551
+ input: 1.25,
5552
+ output: 10,
5553
+ cacheRead: 0.125,
5554
+ cacheCreate: 1.25,
5555
+ lastReviewed: "2026-01-15",
5556
+ source: "OpenAI public pricing (GPT-5)"
5557
+ },
5558
+ "gpt-5-mini": {
5559
+ input: 0.25,
5560
+ output: 2,
5561
+ cacheRead: 0.025,
5562
+ cacheCreate: 0.25,
5563
+ lastReviewed: "2026-01-15",
5564
+ source: "OpenAI public pricing (GPT-5 Mini)"
5565
+ },
5566
+ "gpt-4o": {
5567
+ input: 2.5,
5568
+ output: 10,
5569
+ cacheRead: 1.25,
5570
+ cacheCreate: 2.5,
5571
+ lastReviewed: "2026-01-15",
5572
+ source: "OpenAI public pricing"
5573
+ },
5574
+ // Google Gemini — 2.x family (used by Gemini CLI)
5575
+ "gemini-2.5-pro": {
5576
+ input: 1.25,
5577
+ output: 10,
5578
+ cacheRead: 0.31,
5579
+ cacheCreate: 1.25,
5580
+ lastReviewed: "2026-01-15",
5581
+ source: "Google AI Studio public pricing"
5582
+ },
5583
+ "gemini-2.5-flash": {
5584
+ input: 0.3,
5585
+ output: 2.5,
5586
+ cacheRead: 0.075,
5587
+ cacheCreate: 0.3,
5588
+ lastReviewed: "2026-01-15",
5589
+ source: "Google AI Studio public pricing"
5565
5590
  }
5566
- if (re.lastIndex === m.index)
5567
- re.lastIndex++;
5568
- }
5591
+ };
5592
+ METADATA_VERSION = 3;
5569
5593
  }
5570
- for (const { start, end } of findContextualSecrets(text)) {
5571
- const line = lineAt(start);
5572
- findings.push({ rule: "secret-context", line, preview: maskSecret(text.slice(start, end)) });
5594
+ });
5595
+
5596
+ // ../engine/dist/parsers/session.js
5597
+ import { createReadStream, existsSync as existsSync5, readdirSync as readdirSync5, readFileSync as readFileSync2, statSync as statSync3, openSync, readSync, closeSync } from "fs";
5598
+ import { createInterface } from "readline";
5599
+ import { join as join5, basename as basename2 } from "path";
5600
+ function removeCodeBlocks(text) {
5601
+ let result = text.replace(/```[\s\S]*?```/g, "[code block]");
5602
+ result = result.replace(/`[^`]+`/g, "[code]");
5603
+ return result;
5604
+ }
5605
+ function extractKeyText(text, maxLength = 800) {
5606
+ let result = removeCodeBlocks(text);
5607
+ const boilerplate = [
5608
+ /^Let me /i,
5609
+ /^I'll /i,
5610
+ /^I will /i,
5611
+ /^Now I'll /i,
5612
+ /^Here's what /i
5613
+ ];
5614
+ for (const pattern of boilerplate) {
5615
+ result = result.replace(pattern, "");
5573
5616
  }
5574
- return findings;
5617
+ result = result.replace(/\s+/g, " ").trim();
5618
+ return result.slice(0, maxLength);
5575
5619
  }
5576
- function privacyView() {
5577
- let mtimeMs = -1;
5578
- try {
5579
- mtimeMs = statSync3(settingsFilePath()).mtimeMs;
5580
- } catch {
5620
+ function isValuableToolResult(content, toolName = "") {
5621
+ if (!content || content.length < 20) {
5622
+ return false;
5581
5623
  }
5582
- if (privacyCache && privacyCache.mtimeMs === mtimeMs)
5583
- return privacyCache;
5584
- let redactToolOutputs = false, redactFilePaths = false;
5585
- try {
5586
- const s = loadSettings();
5587
- redactToolOutputs = !!s.privacy?.redactToolOutputs;
5588
- redactFilePaths = !!s.privacy?.redactFilePaths;
5589
- } catch {
5624
+ const skipPatterns = [
5625
+ /^Tool ran without output/i,
5626
+ /^No (?:matches|results) found/i,
5627
+ /^File (?:created|updated|deleted)/i,
5628
+ /^Command completed/i,
5629
+ /^\s*$/
5630
+ ];
5631
+ for (const pattern of skipPatterns) {
5632
+ if (pattern.test(content)) {
5633
+ return false;
5634
+ }
5590
5635
  }
5591
- privacyCache = { mtimeMs, redactToolOutputs, redactFilePaths };
5592
- return privacyCache;
5636
+ if (["WebSearch", "WebFetch"].includes(toolName) || content.toLowerCase().includes("http")) {
5637
+ return true;
5638
+ }
5639
+ if (content.length > 5e3) {
5640
+ return false;
5641
+ }
5642
+ return true;
5593
5643
  }
5594
- function isToolOutputRedactionEnabled() {
5595
- return privacyView().redactToolOutputs;
5644
+ function hasSubagentsDir(sessionPath) {
5645
+ const dir = sessionPath.slice(0, -6);
5646
+ return existsSync5(join5(dir, "subagents"));
5596
5647
  }
5597
- function redactToolResultBody(toolName, body) {
5598
- if (!body)
5599
- return body;
5600
- return `[redacted: ${toolName} output, ${body.length} chars]`;
5601
- }
5602
- var DEFAULT_REDACTION_RULES, SECRET_CONTEXT_RE, HIGH_ENTROPY_TOKEN_RE, cache, CANARY, MAX_PACK_RULES, MAX_PATTERN_LEN, serverPack, globalRegexCache, privacyCache;
5603
- var init_secret_redactor = __esm({
5604
- "../engine/dist/core/secret-redactor.js"() {
5605
- "use strict";
5606
- init_settings();
5607
- DEFAULT_REDACTION_RULES = [
5608
- // AWS access tokens. Prefix-anchored: AKIA, ASIA, ABIA (long-term,
5609
- // STS, EC2). Always exactly 16 hex/alphanumeric after the prefix.
5610
- // NOTE: this matches only the access-key-ID, NOT the secret key value —
5611
- // the `env-secret` rule below covers AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN.
5612
- { label: "aws-access-token", pattern: /\b(?:AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}\b/g },
5613
- // AWS STS session tokens. These are long base64 blobs with NO `NAME=` context
5614
- // when pasted bare into prose/code, so `env-secret` misses them — but they
5615
- // reliably start with one of a handful of base64 prefixes (the encoded
5616
- // `{"...` JSON header). Prefix-anchored → low false-positive. This is the
5617
- // bare-value form that leaked (`search('IQoJEXAMPLEfakeSESSIONtokenNOTreal01234567890abcDEF==...')`).
5618
- { label: "aws-session-token", pattern: /\b(?:IQoJ|FwoG|FwoH|FQoG|FQoH|FwoD)[A-Za-z0-9/+]{40,}={0,2}/g },
5619
- // Env/shell-assignment secrets — a VAR whose name ends in KEY/SECRET/TOKEN/
5620
- // PASSWORD/CREDENTIAL, then `=`/`:`, then the value. Zero-width lookbehind so
5621
- // only the VALUE is redacted (the var name stays for context). This is the
5622
- // dominant real-world leak vector (`export` dumps, .env files, `curl -H`,
5623
- // tool output) and is exactly what leaked AWS_SECRET_ACCESS_KEY and
5624
- // AWS_SESSION_TOKEN in cleartext while the access-key-ID rule above caught
5625
- // only the (least-sensitive) ID.
5626
- // Suffix list deliberately broad: DB_PASS (our own helm chart's name!),
5627
- // NPM_AUTH, SENTRY_DSN, SESSION/COOKIE secrets and BEARER vars all leaked
5628
- // past the original KEY/SECRET/TOKEN/PASSWORD-only list. Values under 12
5629
- // chars don't redact, so flag-ish vars (BYPASS=true) stay readable.
5630
- // Context CONSUMED, secret in group 1 — see RedactionRule.secret. As a
5631
- // lookbehind this was the second most expensive rule in the collector.
5632
- { label: "env-secret", secret: 1, pattern: /\b[A-Z][A-Z0-9_]*(?:KEY|SECRET|TOKEN|PASSWORD|PASSWD|PASS|PWD|CREDENTIAL|AUTH|DSN|SESSION|COOKIE|BEARER|SIGNATURE)S?["']?[ \t]{0,3}[:=][ \t]{0,3}["']?([A-Za-z0-9/+_.~=:@-]{12,})/g },
5633
- // Credentials embedded in a connection URL: scheme://user:PASSWORD@host.
5634
- // Redacts only the password segment (keeps scheme/user/host for context).
5635
- //
5636
- // Scheme-agnostic on purpose. The old rule allowlisted eight DB schemes, so
5637
- // `https://user:pass@host`, `ssh://`, `ftp://`, `smtp://` and every other
5638
- // userinfo URL walked through in cleartext — and a `jdbc:postgresql://…`
5639
- // prefix broke the \b anchor for the one family it did cover. Any scheme
5640
- // followed by userinfo is a credential, so match the shape, not a list.
5641
- { label: "url-password", secret: 1, pattern: /\b[a-zA-Z][a-zA-Z0-9+.-]{1,31}:\/\/[^:@\s/]{1,64}:([^@\s/]{1,256})(?=@)/g },
5642
- // Bare AWS secret-access-key VALUE: EXACTLY 40 base64 chars, word-bounded,
5643
- // containing upper+lower+digit. This is the gitleaks heuristic — the mixed-case
5644
- // requirement excludes 40-char lowercase-hex git SHAs, and the exact-40 bound
5645
- // excludes longer blobs/tokens. It catches the value when it appears bare in
5646
- // prose with NO `NAME=` and NO nearby context word (e.g. inside a markdown
5647
- // table or quoted in analysis) — the residual leak path the env-secret and
5648
- // contextual rules both miss. Placed AFTER env-secret/url-password so the
5649
- // keyed/URL forms keep their more-specific labels.
5650
- // The three stacked lookaheads each re-scanned the same 40 characters, which
5651
- // made this the MOST expensive rule of all 106 the collector runs (13.1% of a
5652
- // 29.5 MB scan). The mixed-case-plus-digit test is now one JS pass.
5653
- {
5654
- label: "aws-secret-key",
5655
- secret: 1,
5656
- pattern: /(?<![A-Za-z0-9/+])([A-Za-z0-9/+]{40})(?![A-Za-z0-9/+])/g,
5657
- where: (v) => /[a-z]/.test(v) && /[A-Z]/.test(v) && /[0-9]/.test(v)
5658
- },
5659
- // GitHub personal access tokens.
5660
- { label: "github-pat", pattern: /\bghp_[A-Za-z0-9]{36}\b/g },
5661
- // GitHub fine-grained PATs.
5662
- { label: "github-fine", pattern: /\bgithub_pat_[A-Za-z0-9_]{82}\b/g },
5663
- // GitHub OAuth.
5664
- { label: "github-oauth", pattern: /\bgho_[A-Za-z0-9]{36}\b/g },
5665
- // GitHub app + refresh.
5666
- { label: "github-app", pattern: /\b(?:ghs|ghu|ghr)_[A-Za-z0-9]{36}\b/g },
5667
- // Slack bot/user/app tokens.
5668
- { label: "slack-token", pattern: /\bxox[abopr]-[A-Za-z0-9-]{10,}\b/g },
5669
- // Slack incoming webhook URL.
5670
- { label: "slack-webhook", pattern: /\bhttps:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+\b/g },
5671
- // Stripe live + test keys.
5672
- { label: "stripe-key", pattern: /\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{24,}\b/g },
5673
- // Anthropic.
5674
- { label: "anthropic-key", pattern: /\bsk-ant-[a-zA-Z0-9_-]{40,}\b/g },
5675
- // OpenAI. Two shapes, deliberately kept apart. The classic key is
5676
- // `sk-` + base62 with NO hyphens; the modern scoped keys carry an explicit
5677
- // proj/svcacct/admin segment first. Allowing hyphens after a bare `sk-`
5678
- // (which is what admitted sk-proj- in the first place) also matches ordinary
5679
- // kebab-case text — `sk-fix-the-login-redirect-bug`, `.sk-button-primary`,
5680
- // `sk-2024-01-01-backup` — and this corpus is full of branch names and CSS
5681
- // classes. Requiring the segment keeps sk-proj- coverage without that.
5682
- { label: "openai-key", pattern: /\bsk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,}\b|\bsk-[A-Za-z0-9]{20,}\b/g },
5683
- // Google API key (AIza…).
5684
- { label: "google-api-key", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
5685
- // Generic JWT — three base64url segments.
5686
- { label: "jwt", pattern: /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g },
5687
- // Private key blocks (PEM). The label is a free word run rather than a fixed
5688
- // list, so ENCRYPTED PRIVATE KEY and PGP PRIVATE KEY BLOCK are covered
5689
- // without another enumeration, and \1 forces END to name the same kind as
5690
- // BEGIN.
5691
- //
5692
- // The body is [^-], NOT [\s\S]*?. That is a performance boundary, not a
5693
- // style choice. An unanchored prefix with an unbounded lazy gap re-scans to
5694
- // end-of-input from every candidate start, so text carrying many BEGIN
5695
- // markers and no matching END costs O(n^2): 500KB of that measured 810ms
5696
- // here, and this runs synchronously on the server's event loop over
5697
- // tenant-supplied text (services/secret-rescan.ts). A PEM body is base64
5698
- // plus headers and contains no '-', so excluding it makes the scan stop at
5699
- // the next delimiter instead of at EOF — same matches, linear cost.
5700
- { label: "private-key", pattern: /-----BEGIN ([A-Z0-9 ]{0,32})PRIVATE KEY(?: BLOCK)?-----[^-]{0,10000}?-----END \1PRIVATE KEY(?: BLOCK)?-----/g },
5701
- // PuTTY keys are not PEM. Match the Private-Lines payload — the actual key
5702
- // material — rather than spanning the whole file, which would need the same
5703
- // unbounded gap the rule above just removed. Line-anchored and base64-only,
5704
- // so it cannot run away.
5705
- { label: "putty-key", pattern: /^Private-Lines:[ \t]*\d{1,4}[\r\n]+(?:[A-Za-z0-9+/=]{8,120}[\r\n]+){1,400}/gm },
5706
- // npm tokens (uuid-shaped — overlap with our UUID filter; intentional).
5707
- { label: "npm-token", pattern: /\bnpm_[A-Za-z0-9]{36}\b/g },
5708
- // .npmrc auth lines: //registry.npmjs.org/:_authToken=VALUE (the key is not
5709
- // ALL-CAPS, so the env-secret rule above never matched it). The leading `:`
5710
- // is required — it is what makes this npmrc syntax rather than any shell var
5711
- // ending in _AUTH, which env-secret already owns and labels better.
5712
- // [^\s"'`]+ so the closing quote is not swallowed, and a ${VAR} placeholder
5713
- // is left alone — redacting it destroys a config example and hides nothing.
5714
- // The rule the perf profile caught the wedged daemon inside: 98.94% of samples.
5715
- { label: "npmrc-auth", secret: 1, pattern: /:_(?:authToken|password|auth)[ \t]*=[ \t]*(?!\$\{)([^\s"'`]{12,})/gi },
5716
- // HTTP Authorization headers. env-secret requires an ALL-CAPS name, and
5717
- // `Authorization` is mixed case, so both Bearer and Basic walked straight
5718
- // through — including short opaque tokens the contextual pass also misses
5719
- // (it needs 32+ chars AND mixed-case-plus-digit).
5720
- // Same two guards as npmrc-auth: stop at a quote, and skip the obvious
5721
- // placeholders that fill documentation (<YOUR_TOKEN>, ${TOKEN}, xxx…).
5722
- { label: "auth-header", secret: 1, pattern: /\bauthorization[ \t]*:[ \t]*(?:bearer|basic|token)[ \t]+(?![<$])([^\s"'`<]{8,})/gi },
5723
- // Vendor tokens with unambiguous prefixes. Each is a fixed literal, so a
5724
- // false positive is essentially impossible and the cost of listing is low.
5725
- { label: "gitlab-pat", pattern: /\bglpat-[A-Za-z0-9_-]{20,}\b/g },
5726
- { label: "slack-app-token", pattern: /\bxapp-\d-[A-Z0-9]{8,}-\d{10,}-[a-f0-9]{32,}\b/g },
5727
- { label: "sendgrid-key", pattern: /\bSG\.[A-Za-z0-9_-]{20,24}\.[A-Za-z0-9_-]{40,50}\b/g },
5728
- // (?<![-\w]) so `cache-key-<md5>` is not read as a mailgun key.
5729
- { label: "mailgun-key", pattern: /(?<![-\w])key-[a-f0-9]{32}\b/g },
5730
- { label: "digitalocean-pat", pattern: /\bdop_v1_[a-f0-9]{64}\b/g },
5731
- { label: "huggingface-token", pattern: /\bhf_[A-Za-z0-9]{30,}\b/g },
5732
- { label: "shopify-token", pattern: /\bshp(?:at|ca|pa|ss)_[a-fA-F0-9]{32}\b/g },
5733
- { label: "pypi-token", pattern: /\bpypi-AgEIcHlwaS5vcmc[A-Za-z0-9_-]{50,}\b/g },
5734
- // No twilio-sid rule: AC…/SK… are Twilio SIDs, which are public account and
5735
- // key identifiers, not the auth token. Redacting them hid nothing and
5736
- // collided with 32-hex etags and MD5 sums.
5737
- // No discord-bot-token rule either: the shape (word.word.word) matched
5738
- // ordinary dotted symbols such as a.b.ApplicationContextProviderImpl.
5739
- { label: "telegram-bot-token", pattern: /\b\d{8,10}:AA[A-Za-z0-9_-]{32,}\b/g },
5740
- { label: "doppler-token", pattern: /\bdp\.(?:pt|st|sa|ct)\.[A-Za-z0-9_-]{20,}\b/g },
5741
- // Azure Storage SAS signature — the `sig=` parameter is the actual secret.
5742
- { label: "azure-sas", secret: 1, pattern: /[?&]sig=([A-Za-z0-9%/+=]{20,})/g }
5743
- ];
5744
- SECRET_CONTEXT_RE = /\b(?:secret|password|passwd|credential|access[ _-]?key|private[ _-]?key|api[ _-]?key|auth(?:oriz\w*)?|bearer|token)\b/gi;
5745
- HIGH_ENTROPY_TOKEN_RE = /[A-Za-z0-9/+_-]{32,}/g;
5746
- cache = null;
5747
- CANARY = "the quick brown fox jumps over the lazy dog 0123456789 commit 8ba1f109551bd432803012645ac136ddd475d0fb src/index.ts:42";
5748
- MAX_PACK_RULES = 200;
5749
- MAX_PATTERN_LEN = 512;
5750
- serverPack = null;
5751
- globalRegexCache = /* @__PURE__ */ new Map();
5752
- privacyCache = null;
5753
- }
5754
- });
5755
-
5756
- // ../engine/dist/parsers/chunker.js
5757
- function stripInjectedBanners(text) {
5758
- let result = text;
5759
- for (const re of INJECTED_BANNERS)
5760
- result = result.replace(re, " ");
5761
- return result;
5762
- }
5763
- function cleanText(text) {
5764
- let result = text.replace(/<system-reminder>.*?<\/system-reminder>/gs, "");
5765
- result = stripInjectedBanners(result);
5766
- result = result.replace(/\x1b\[[0-9;]*m/g, "");
5767
- result = result.replace(/\s+/g, " ");
5768
- return result.trim();
5769
- }
5770
- function windowText(text, maxChars, maxWindows = 12) {
5771
- const clean = cleanText(text);
5772
- if (clean.length <= maxChars)
5773
- return clean ? [clean] : [];
5774
- const out = [];
5775
- const overlap = 80;
5776
- const step = Math.max(1, maxChars - overlap);
5777
- for (let i = 0; i < clean.length && out.length < maxWindows; i += step) {
5778
- out.push(clean.slice(i, i + maxChars));
5779
- }
5780
- return out;
5781
- }
5782
- function chunkSession(entry, content, maxChunks = 80, maxTokensPerChunk = 500) {
5783
- const chunks = [];
5784
- const maxChars = maxTokensPerChunk * 4;
5785
- const baseData = {
5786
- sessionId: entry.sessionId,
5787
- projectPath: entry.projectPath,
5788
- created: entry.created,
5789
- modified: entry.modified,
5790
- mtime: entry.fileMtime,
5791
- firstPrompt: entry.firstPrompt || content.firstPrompt,
5792
- sourceFile: content.sessionPath
5793
- };
5794
- for (let i = 0; i < Math.min(2, content.summaries.length); i++) {
5795
- const summary = content.summaries[i];
5796
- if (!summary?.trim())
5797
- continue;
5798
- const chunkText = cleanText(summary.slice(0, maxChars));
5799
- if (chunkText.length > 8e3) {
5800
- chunks.push({
5801
- chunkId: `${entry.sessionId}_summary_${i}`,
5802
- chunkType: "summary",
5803
- text: chunkText.slice(0, 8e3),
5804
- sourceLine: 0,
5805
- ...baseData
5806
- });
5807
- } else {
5808
- chunks.push({
5809
- chunkId: `${entry.sessionId}_summary_${i}`,
5810
- chunkType: "summary",
5811
- text: chunkText,
5812
- sourceLine: 0,
5813
- ...baseData
5814
- });
5815
- }
5816
- }
5817
- if (chunks.length >= maxChunks) {
5818
- return chunks.slice(0, maxChunks);
5819
- }
5820
- const firstPrompt = entry.firstPrompt || content.firstPrompt;
5821
- if (firstPrompt?.trim()) {
5822
- const firstLine = content.userMessages[0]?.lineNumber || 0;
5823
- chunks.push({
5824
- chunkId: `${entry.sessionId}_first_prompt`,
5825
- chunkType: "first_prompt",
5826
- text: cleanText(firstPrompt.slice(0, maxChars)),
5827
- sourceLine: firstLine,
5828
- ...baseData
5829
- });
5830
- }
5831
- if (chunks.length >= maxChunks) {
5832
- return chunks.slice(0, maxChunks);
5833
- }
5834
- const userStart = firstPrompt ? 1 : 0;
5835
- for (let i = userStart; i < content.userMessages.length; i++) {
5836
- if (chunks.length >= maxChunks)
5837
- break;
5838
- const msg = content.userMessages[i];
5839
- if (!msg.text.trim())
5840
- continue;
5841
- const windows = windowText(msg.text, maxChars);
5842
- for (let w = 0; w < windows.length; w++) {
5843
- if (chunks.length >= maxChunks)
5844
- break;
5845
- chunks.push({
5846
- chunkId: `${entry.sessionId}_user_${i}_${w}`,
5847
- chunkType: "user_context",
5848
- text: windows[w],
5849
- sourceLine: msg.lineNumber,
5850
- ...baseData
5851
- });
5852
- }
5853
- }
5854
- if (chunks.length >= maxChunks) {
5855
- return chunks.slice(0, maxChunks);
5856
- }
5857
- for (let i = 0; i < content.assistantMessages.length; i++) {
5858
- if (chunks.length >= maxChunks)
5859
- break;
5860
- const msg = content.assistantMessages[i];
5861
- if (!msg.text.trim() || msg.text.length <= 50)
5862
- continue;
5863
- const windows = windowText(msg.text, maxChars);
5864
- for (let w = 0; w < windows.length; w++) {
5865
- if (chunks.length >= maxChunks)
5866
- break;
5867
- chunks.push({
5868
- chunkId: `${entry.sessionId}_assistant_${i}_${w}`,
5869
- chunkType: "assistant",
5870
- text: windows[w],
5871
- sourceLine: msg.lineNumber,
5872
- ...baseData
5873
- });
5874
- }
5875
- }
5876
- if (chunks.length >= maxChunks) {
5877
- return chunks.slice(0, maxChunks);
5878
- }
5879
- if (content.toolResults.length > 0) {
5880
- const webSearches = content.toolResults.filter((r) => r.contentType === "web_search");
5881
- const otherResults = content.toolResults.filter((r) => r.contentType !== "web_search");
5882
- const resultsToProcess = [...webSearches.slice(0, 5), ...otherResults.slice(0, 10)];
5883
- const stripBodies = isToolOutputRedactionEnabled();
5884
- for (let i = 0; i < resultsToProcess.length; i++) {
5885
- if (chunks.length >= maxChunks)
5886
- break;
5887
- const result = resultsToProcess[i];
5888
- if (result.text.trim()) {
5889
- const body = stripBodies && result.contentType !== "web_search" ? redactToolResultBody(result.contentType, result.text) : result.text.slice(0, maxChars);
5890
- chunks.push({
5891
- chunkId: `${entry.sessionId}_${result.contentType}_${i}`,
5892
- chunkType: result.contentType,
5893
- text: cleanText(body),
5894
- sourceLine: result.lineNumber,
5895
- ...baseData
5896
- });
5897
- }
5898
- }
5899
- }
5900
- return chunks.slice(0, maxChunks);
5901
- }
5902
- var INJECTED_BANNERS;
5903
- var init_chunker = __esm({
5904
- "../engine/dist/parsers/chunker.js"() {
5905
- "use strict";
5906
- init_secret_redactor();
5907
- INJECTED_BANNERS = [
5908
- /MCP issues detected\. ?Run \/mcp list for status\.?/g,
5909
- /Context low[^\n]*Run \/compact[^\n]*/g,
5910
- /API Error:[^\n]{0,120}/g
5911
- ];
5912
- }
5913
- });
5914
-
5915
- // ../engine/dist/core/project-dir-name.js
5916
- import { readdirSync as readdirSync4 } from "node:fs";
5917
- function looksWindowsEncoded(dirName) {
5918
- return /^[A-Za-z]--/.test(dirName);
5919
- }
5920
- function decodeProjectDirName(dirName) {
5921
- if (!dirName)
5922
- return "";
5923
- if (looksWindowsEncoded(dirName)) {
5924
- const drive = dirName[0].toUpperCase();
5925
- const rest = dirName.slice(3).replace(/-/g, "\\");
5926
- return `${drive}:\\${rest}`;
5927
- }
5928
- return "/" + dirName.replace(/^-/, "").replace(/-/g, "/");
5929
- }
5930
- function resolveProjectDirName(dirName, deps = {}) {
5931
- const structural = decodeProjectDirName(dirName);
5932
- if (!dirName)
5933
- return structural;
5934
- if (looksWindowsEncoded(dirName))
5935
- return structural;
5936
- const readdir3 = deps.readdir ?? ((p) => readdirSync4(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name));
5937
- const encodeName = (name) => name.replace(/[-_.]/g, "-");
5938
- let remaining = dirName.replace(/^-/, "");
5939
- let current = "/";
5940
- while (remaining) {
5941
- let children;
5942
- try {
5943
- children = readdir3(current);
5944
- } catch {
5945
- break;
5946
- }
5947
- let best = "";
5948
- for (const child of children) {
5949
- const encoded = encodeName(child);
5950
- if (!encoded || encoded.length < best.length)
5951
- continue;
5952
- if (!remaining.startsWith(encoded))
5953
- continue;
5954
- const next = remaining[encoded.length];
5955
- if (next !== void 0 && next !== "-")
5956
- continue;
5957
- if (encoded.length > best.length)
5958
- best = child;
5959
- }
5960
- if (!best)
5961
- break;
5962
- current = current === "/" ? `/${best}` : `${current}/${best}`;
5963
- remaining = remaining.slice(encodeName(best).length).replace(/^-/, "");
5964
- }
5965
- return remaining ? structural : current;
5966
- }
5967
- var init_project_dir_name = __esm({
5968
- "../engine/dist/core/project-dir-name.js"() {
5969
- "use strict";
5970
- }
5971
- });
5972
-
5973
- // ../engine/dist/core/model-pricing.js
5974
- function pricingFor(modelId) {
5975
- if (!modelId)
5976
- return null;
5977
- const lower = modelId.toLowerCase();
5978
- let best = null;
5979
- let bestLen = -1;
5980
- for (const [prefix, entry] of Object.entries(TABLE)) {
5981
- if (lower.startsWith(prefix.toLowerCase()) && prefix.length > bestLen) {
5982
- best = entry;
5983
- bestLen = prefix.length;
5984
- }
5985
- }
5986
- return best;
5987
- }
5988
- function estimateCostUsdOrNull(modelsUsed, tokens) {
5989
- let best = null;
5990
- let bestRate = -1;
5991
- for (const m of modelsUsed) {
5992
- if (!m || m === "<synthetic>")
5993
- continue;
5994
- const p = pricingFor(m);
5995
- if (p && p.output > bestRate) {
5996
- best = p;
5997
- bestRate = p.output;
5998
- }
5999
- }
6000
- if (!best)
6001
- return null;
6002
- const nonCachedInput = Math.max(0, tokens.inputTokens - tokens.cacheReadTokens - tokens.cacheCreationTokens);
6003
- const cost = nonCachedInput / 1e6 * best.input + tokens.outputTokens / 1e6 * best.output + tokens.cacheReadTokens / 1e6 * best.cacheRead + tokens.cacheCreationTokens / 1e6 * best.cacheCreate;
6004
- return Math.round(cost * 100) / 100;
6005
- }
6006
- function estimateCostUsd(modelsUsed, tokens) {
6007
- return estimateCostUsdOrNull(modelsUsed, tokens) ?? 0;
6008
- }
6009
- var TABLE, METADATA_VERSION;
6010
- var init_model_pricing = __esm({
6011
- "../engine/dist/core/model-pricing.js"() {
6012
- "use strict";
6013
- TABLE = {
6014
- // Anthropic — Claude 4.x family
6015
- "claude-opus-4-7": {
6016
- input: 15,
6017
- output: 75,
6018
- cacheRead: 1.5,
6019
- cacheCreate: 18.75,
6020
- lastReviewed: "2026-01-15",
6021
- source: "Anthropic public pricing (Opus 4.x)"
6022
- },
6023
- "claude-opus-4-6": {
6024
- input: 15,
6025
- output: 75,
6026
- cacheRead: 1.5,
6027
- cacheCreate: 18.75,
6028
- lastReviewed: "2026-01-15",
6029
- source: "Anthropic public pricing (Opus 4.x)"
6030
- },
6031
- "claude-opus-4": {
6032
- input: 15,
6033
- output: 75,
6034
- cacheRead: 1.5,
6035
- cacheCreate: 18.75,
6036
- lastReviewed: "2026-01-15",
6037
- source: "Anthropic public pricing (Opus 4.x)"
6038
- },
6039
- "claude-sonnet-4-6": {
6040
- input: 3,
6041
- output: 15,
6042
- cacheRead: 0.3,
6043
- cacheCreate: 3.75,
6044
- lastReviewed: "2026-01-15",
6045
- source: "Anthropic public pricing (Sonnet 4.x)"
6046
- },
6047
- "claude-sonnet-4": {
6048
- input: 3,
6049
- output: 15,
6050
- cacheRead: 0.3,
6051
- cacheCreate: 3.75,
6052
- lastReviewed: "2026-01-15",
6053
- source: "Anthropic public pricing (Sonnet 4.x)"
6054
- },
6055
- "claude-haiku-4-5": {
6056
- input: 1,
6057
- output: 5,
6058
- cacheRead: 0.1,
6059
- cacheCreate: 1.25,
6060
- lastReviewed: "2026-01-15",
6061
- source: "Anthropic public pricing (Haiku 4.5)"
6062
- },
6063
- // Anthropic — older Claude 3.x (kept for back-compat with historical transcripts)
6064
- "claude-3-7-sonnet": {
6065
- input: 3,
6066
- output: 15,
6067
- cacheRead: 0.3,
6068
- cacheCreate: 3.75,
6069
- lastReviewed: "2026-01-15",
6070
- source: "Anthropic public pricing"
6071
- },
6072
- "claude-3-5-sonnet": {
6073
- input: 3,
6074
- output: 15,
6075
- cacheRead: 0.3,
6076
- cacheCreate: 3.75,
6077
- lastReviewed: "2026-01-15",
6078
- source: "Anthropic public pricing"
6079
- },
6080
- "claude-3-5-haiku": {
6081
- input: 0.8,
6082
- output: 4,
6083
- cacheRead: 0.08,
6084
- cacheCreate: 1,
6085
- lastReviewed: "2026-01-15",
6086
- source: "Anthropic public pricing"
6087
- },
6088
- "claude-3-opus": {
6089
- input: 15,
6090
- output: 75,
6091
- cacheRead: 1.5,
6092
- cacheCreate: 18.75,
6093
- lastReviewed: "2026-01-15",
6094
- source: "Anthropic public pricing"
6095
- },
6096
- // OpenAI — current GPT-* (used by Codex CLI)
6097
- "gpt-5": {
6098
- input: 1.25,
6099
- output: 10,
6100
- cacheRead: 0.125,
6101
- cacheCreate: 1.25,
6102
- lastReviewed: "2026-01-15",
6103
- source: "OpenAI public pricing (GPT-5)"
6104
- },
6105
- "gpt-5-mini": {
6106
- input: 0.25,
6107
- output: 2,
6108
- cacheRead: 0.025,
6109
- cacheCreate: 0.25,
6110
- lastReviewed: "2026-01-15",
6111
- source: "OpenAI public pricing (GPT-5 Mini)"
6112
- },
6113
- "gpt-4o": {
6114
- input: 2.5,
6115
- output: 10,
6116
- cacheRead: 1.25,
6117
- cacheCreate: 2.5,
6118
- lastReviewed: "2026-01-15",
6119
- source: "OpenAI public pricing"
6120
- },
6121
- // Google Gemini — 2.x family (used by Gemini CLI)
6122
- "gemini-2.5-pro": {
6123
- input: 1.25,
6124
- output: 10,
6125
- cacheRead: 0.31,
6126
- cacheCreate: 1.25,
6127
- lastReviewed: "2026-01-15",
6128
- source: "Google AI Studio public pricing"
6129
- },
6130
- "gemini-2.5-flash": {
6131
- input: 0.3,
6132
- output: 2.5,
6133
- cacheRead: 0.075,
6134
- cacheCreate: 0.3,
6135
- lastReviewed: "2026-01-15",
6136
- source: "Google AI Studio public pricing"
6137
- }
6138
- };
6139
- METADATA_VERSION = 3;
6140
- }
6141
- });
6142
-
6143
- // ../engine/dist/parsers/session.js
6144
- import { createReadStream, existsSync as existsSync5, readdirSync as readdirSync5, readFileSync as readFileSync2, statSync as statSync4, openSync, readSync, closeSync } from "fs";
6145
- import { createInterface } from "readline";
6146
- import { join as join5, basename as basename2 } from "path";
6147
- function removeCodeBlocks(text) {
6148
- let result = text.replace(/```[\s\S]*?```/g, "[code block]");
6149
- result = result.replace(/`[^`]+`/g, "[code]");
6150
- return result;
6151
- }
6152
- function extractKeyText(text, maxLength = 800) {
6153
- let result = removeCodeBlocks(text);
6154
- const boilerplate = [
6155
- /^Let me /i,
6156
- /^I'll /i,
6157
- /^I will /i,
6158
- /^Now I'll /i,
6159
- /^Here's what /i
6160
- ];
6161
- for (const pattern of boilerplate) {
6162
- result = result.replace(pattern, "");
6163
- }
6164
- result = result.replace(/\s+/g, " ").trim();
6165
- return result.slice(0, maxLength);
6166
- }
6167
- function isValuableToolResult(content, toolName = "") {
6168
- if (!content || content.length < 20) {
6169
- return false;
6170
- }
6171
- const skipPatterns = [
6172
- /^Tool ran without output/i,
6173
- /^No (?:matches|results) found/i,
6174
- /^File (?:created|updated|deleted)/i,
6175
- /^Command completed/i,
6176
- /^\s*$/
6177
- ];
6178
- for (const pattern of skipPatterns) {
6179
- if (pattern.test(content)) {
6180
- return false;
6181
- }
6182
- }
6183
- if (["WebSearch", "WebFetch"].includes(toolName) || content.toLowerCase().includes("http")) {
6184
- return true;
6185
- }
6186
- if (content.length > 5e3) {
6187
- return false;
6188
- }
6189
- return true;
6190
- }
6191
- function parseSessionsIndex(indexPath) {
6192
- if (!existsSync5(indexPath)) {
6193
- return [];
6194
- }
6195
- const data = JSON.parse(readFileSync2(indexPath, "utf-8"));
6196
- const entries = data.entries || [];
6197
- return entries.map((e) => ({
6198
- sessionId: e.sessionId || "",
6199
- fullPath: e.fullPath || "",
6200
- fileMtime: e.fileMtime || 0,
6201
- firstPrompt: e.firstPrompt || "",
6202
- messageCount: e.messageCount || 0,
6203
- created: e.created || "",
6204
- modified: e.modified || "",
6205
- gitBranch: e.gitBranch || "",
6206
- projectPath: e.projectPath || "",
6207
- isSidechain: e.isSidechain || false
6208
- }));
6209
- }
6210
- function* iterAllSessionIndices(claudeDir) {
6211
- const dir = claudeDir || claudeBackend.projectsDir();
6212
- if (!existsSync5(dir)) {
6213
- return;
6214
- }
6215
- const entries = readdirSync5(dir, { withFileTypes: true });
6216
- for (const entry of entries) {
6217
- if (!entry.isDirectory())
6218
- continue;
6219
- const projectDir = join5(dir, entry.name);
6220
- const indexPath = join5(projectDir, "sessions-index.json");
6221
- if (existsSync5(indexPath)) {
6222
- const sessions = parseSessionsIndex(indexPath);
6223
- if (sessions.length > 0) {
6224
- yield [projectDir, sessions];
6225
- }
6226
- }
6227
- }
6228
- }
6229
- function hasSubagentsDir(sessionPath) {
6230
- const dir = sessionPath.slice(0, -6);
6231
- return existsSync5(join5(dir, "subagents"));
6232
- }
6233
- function getSubagentPaths(sessionPath) {
6234
- const subagentsDir = join5(sessionPath.slice(0, -6), "subagents");
6235
- if (!existsSync5(subagentsDir))
6236
- return [];
6237
- return readdirSync5(subagentsDir).filter((f) => f.endsWith(".jsonl")).sort().map((f) => join5(subagentsDir, f));
6238
- }
6239
- function getSubagentProjectPath(sessionPath, folderDerivedPath) {
6240
- const subagentPaths = getSubagentPaths(sessionPath);
6241
- if (subagentPaths.length === 0)
6242
- return folderDerivedPath;
6243
- const cwdCounts = /* @__PURE__ */ new Map();
6244
- for (const filePath of subagentPaths) {
6245
- try {
6246
- const lines = readFileSync2(filePath, "utf-8").split("\n");
6247
- for (const line of lines) {
6248
- if (!line.trim())
6249
- continue;
6250
- try {
6251
- const entry = JSON.parse(line);
6252
- if (entry.type === "user" && typeof entry.cwd === "string" && entry.cwd) {
6253
- cwdCounts.set(entry.cwd, (cwdCounts.get(entry.cwd) ?? 0) + 1);
6254
- }
6255
- } catch {
6256
- }
6257
- }
6258
- } catch {
6259
- }
6260
- }
6261
- if (cwdCounts.size === 0)
6262
- return folderDerivedPath;
6263
- let best = folderDerivedPath;
6264
- let bestCount = 0;
6265
- for (const [cwd, count] of cwdCounts) {
6266
- if (count > bestCount) {
6267
- best = cwd;
6268
- bestCount = count;
6269
- }
6270
- }
6271
- return best;
5648
+ function getSubagentPaths(sessionPath) {
5649
+ const subagentsDir = join5(sessionPath.slice(0, -6), "subagents");
5650
+ if (!existsSync5(subagentsDir))
5651
+ return [];
5652
+ return readdirSync5(subagentsDir).filter((f) => f.endsWith(".jsonl")).sort().map((f) => join5(subagentsDir, f));
6272
5653
  }
6273
5654
  function readCwdFromJsonl(sessionPath) {
6274
5655
  try {
@@ -6298,22 +5679,6 @@ function readCwdFromJsonl(sessionPath) {
6298
5679
  }
6299
5680
  return "";
6300
5681
  }
6301
- function getSessionMtime(sessionPath) {
6302
- const subagentPaths = getSubagentPaths(sessionPath);
6303
- if (subagentPaths.length === 0) {
6304
- return statSync4(sessionPath).mtimeMs;
6305
- }
6306
- let maxMtime = statSync4(sessionPath).mtimeMs;
6307
- for (const p of subagentPaths) {
6308
- try {
6309
- const m = statSync4(p).mtimeMs;
6310
- if (m > maxMtime)
6311
- maxMtime = m;
6312
- } catch {
6313
- }
6314
- }
6315
- return maxMtime;
6316
- }
6317
5682
  async function parseSessionFile(sessionPath, maxMessages = 50) {
6318
5683
  const sessionId = basename2(sessionPath, ".jsonl");
6319
5684
  const content = {
@@ -6551,76 +5916,10 @@ ${resultText}` : resultText;
6551
5916
  });
6552
5917
  return content;
6553
5918
  }
6554
- function* getAllSessions(claudeDir) {
6555
- const seenSessionIds = /* @__PURE__ */ new Set();
6556
- const projectDirs = claudeDir ? [claudeDir] : claudeProjectDirs();
6557
- for (const dir of projectDirs) {
6558
- for (const [, entries] of iterAllSessionIndices(dir)) {
6559
- for (const entry of entries) {
6560
- if (!seenSessionIds.has(entry.sessionId) && existsSync5(entry.fullPath)) {
6561
- seenSessionIds.add(entry.sessionId);
6562
- yield [entry, entry.fullPath];
6563
- }
6564
- }
6565
- }
6566
- if (!existsSync5(dir))
6567
- continue;
6568
- const dirEntries = readdirSync5(dir, { withFileTypes: true });
6569
- for (const projDirEntry of dirEntries) {
6570
- if (!projDirEntry.isDirectory())
6571
- continue;
6572
- const projectPath = join5(dir, projDirEntry.name);
6573
- const files = readdirSync5(projectPath);
6574
- for (const file of files) {
6575
- if (!file.endsWith(".jsonl"))
6576
- continue;
6577
- if (file.startsWith("agent-"))
6578
- continue;
6579
- if (file === "sessions-index.json")
6580
- continue;
6581
- const sessionId = basename2(file, ".jsonl");
6582
- if (seenSessionIds.has(sessionId))
6583
- continue;
6584
- const fullPath = join5(projectPath, file);
6585
- try {
6586
- const stat3 = statSync4(fullPath);
6587
- const derivedProjectPath = resolveProjectDirName(projDirEntry.name);
6588
- const effectiveMtime = getSessionMtime(fullPath);
6589
- let projectPath2 = "";
6590
- if (hasSubagentsDir(fullPath)) {
6591
- projectPath2 = getSubagentProjectPath(fullPath, derivedProjectPath);
6592
- } else {
6593
- const fromJsonl = readCwdFromJsonl(fullPath);
6594
- if (fromJsonl)
6595
- projectPath2 = fromJsonl;
6596
- }
6597
- if (!projectPath2)
6598
- projectPath2 = derivedProjectPath;
6599
- const entry = {
6600
- sessionId,
6601
- fullPath,
6602
- fileMtime: effectiveMtime,
6603
- firstPrompt: "",
6604
- messageCount: 0,
6605
- created: stat3.birthtime.toISOString(),
6606
- modified: new Date(effectiveMtime).toISOString(),
6607
- gitBranch: "",
6608
- projectPath: projectPath2,
6609
- isSidechain: false
6610
- };
6611
- seenSessionIds.add(sessionId);
6612
- yield [entry, fullPath];
6613
- } catch {
6614
- continue;
6615
- }
6616
- }
6617
- }
6618
- }
6619
- }
6620
5919
  var init_session = __esm({
6621
5920
  "../engine/dist/parsers/session.js"() {
6622
5921
  "use strict";
6623
- init_chunker();
5922
+ init_banners();
6624
5923
  init_claude2();
6625
5924
  init_tool_paths();
6626
5925
  init_project_dir_name();
@@ -7286,7 +6585,7 @@ var init_raw = __esm({
7286
6585
  // ../engine/dist/transcript/shadow.js
7287
6586
  import { gzipSync as gzipSync2, gunzipSync as gunzipSync2 } from "zlib";
7288
6587
  import { createHash as createHash2 } from "crypto";
7289
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync as renameSync2, statSync as statSync5 } from "fs";
6588
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync as renameSync2, statSync as statSync4 } from "fs";
7290
6589
  import { dirname as dirname7, join as join8 } from "path";
7291
6590
  function shadowRoot() {
7292
6591
  return join8(getDataDir(), "shadow");
@@ -7494,7 +6793,7 @@ function liveFingerprint(backend, rawId) {
7494
6793
  const loc = backend.findSession(rawId);
7495
6794
  if (!loc?.path)
7496
6795
  return "";
7497
- const st = statSync5(loc.path);
6796
+ const st = statSync4(loc.path);
7498
6797
  return `${st.mtimeMs}:${st.size}`;
7499
6798
  } catch {
7500
6799
  return "";
@@ -7512,7 +6811,7 @@ function seedShadow(sessionId, container) {
7512
6811
  }
7513
6812
  function shadowSizeOnDisk(tool, rawId) {
7514
6813
  try {
7515
- return statSync5(shadowFileFor(tool, rawId)).size;
6814
+ return statSync4(shadowFileFor(tool, rawId)).size;
7516
6815
  } catch {
7517
6816
  return 0;
7518
6817
  }
@@ -7530,7 +6829,7 @@ var init_shadow = __esm({
7530
6829
  });
7531
6830
 
7532
6831
  // ../engine/dist/core/live-session-scan.js
7533
- import { existsSync as existsSync7, readdirSync as readdirSync6, readFileSync as readFileSync4, statSync as statSync6, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
6832
+ import { existsSync as existsSync7, readdirSync as readdirSync6, readFileSync as readFileSync4, statSync as statSync5, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
7534
6833
  import { join as join9, basename as basename5 } from "path";
7535
6834
  function detectTool(sessionId) {
7536
6835
  return getBackendForId(sessionId)?.id ?? "claude";
@@ -7622,20 +6921,33 @@ function resolveSessionContentGroups(sessionId) {
7622
6921
  }
7623
6922
  function withSessionReadCache(fn) {
7624
6923
  readScopeDepth++;
6924
+ if (readScopeDepth === 1)
6925
+ derivedScope = /* @__PURE__ */ new Map();
7625
6926
  try {
7626
6927
  return fn();
7627
6928
  } finally {
7628
- if (--readScopeDepth === 0)
6929
+ if (--readScopeDepth === 0) {
7629
6930
  readScope = null;
6931
+ derivedScope = null;
6932
+ }
7630
6933
  }
7631
6934
  }
6935
+ function memoInSessionScope(key2, compute) {
6936
+ if (!derivedScope)
6937
+ return compute();
6938
+ if (derivedScope.has(key2))
6939
+ return derivedScope.get(key2);
6940
+ const value = compute();
6941
+ derivedScope.set(key2, value);
6942
+ return value;
6943
+ }
7632
6944
  function readSessionGroupText(group) {
7633
6945
  let key2 = "";
7634
6946
  if (readScopeDepth > 0) {
7635
6947
  const parts = [];
7636
6948
  for (const p of group.paths) {
7637
6949
  try {
7638
- const st = statSync6(p);
6950
+ const st = statSync5(p);
7639
6951
  parts.push(`${p}:${st.mtimeMs}:${st.size}`);
7640
6952
  } catch {
7641
6953
  parts.push(`${p}:missing`);
@@ -7655,7 +6967,7 @@ function readSessionGroupText(group) {
7655
6967
  continue;
7656
6968
  }
7657
6969
  try {
7658
- mtime = Math.max(mtime, statSync6(p).mtimeMs);
6970
+ mtime = Math.max(mtime, statSync5(p).mtimeMs);
7659
6971
  } catch {
7660
6972
  }
7661
6973
  text = text ? mergeLineText(text, raw).text : raw;
@@ -7740,7 +7052,7 @@ function findCodexSessionFileIn(root, sessionId) {
7740
7052
  }
7741
7053
  return scanDir(root);
7742
7054
  }
7743
- var scanScopeDepth, indexCache, readScope, readScopeDepth, geminiCache, MAX_EVENT_LINE_BYTES;
7055
+ var scanScopeDepth, indexCache, readScope, readScopeDepth, derivedScope, geminiCache, MAX_EVENT_LINE_BYTES;
7744
7056
  var init_live_session_scan = __esm({
7745
7057
  "../engine/dist/core/live-session-scan.js"() {
7746
7058
  "use strict";
@@ -7754,6 +7066,7 @@ var init_live_session_scan = __esm({
7754
7066
  indexCache = null;
7755
7067
  readScope = null;
7756
7068
  readScopeDepth = 0;
7069
+ derivedScope = null;
7757
7070
  geminiCache = null;
7758
7071
  MAX_EVENT_LINE_BYTES = 8 * 1024 * 1024;
7759
7072
  }
@@ -8031,6 +7344,28 @@ var init_session_git = __esm({
8031
7344
  }
8032
7345
  });
8033
7346
 
7347
+ // ../engine/dist/core/session-multi-tool.js
7348
+ function extractTurnsAny(sessionId, opts = {}) {
7349
+ const backend = getBackendForId(sessionId);
7350
+ if (!backend)
7351
+ return { sessionId, found: false, turns: [], startMs: 0, endMs: 0 };
7352
+ return memoInSessionScope(`turns:${sessionId}:${opts.maxTurns ?? ""}:${opts.assistantMax ?? ""}`, () => backend.extractTurns(sessionId, opts));
7353
+ }
7354
+ function replaySessionAny(sessionId) {
7355
+ const backend = getBackendForId(sessionId);
7356
+ if (!backend)
7357
+ return { sessionId, found: false, projectPath: "", files: [], totalLinesAdded: 0, totalLinesRemoved: 0 };
7358
+ return memoInSessionScope(`replay:${sessionId}`, () => backend.replay(sessionId));
7359
+ }
7360
+ var init_session_multi_tool = __esm({
7361
+ "../engine/dist/core/session-multi-tool.js"() {
7362
+ "use strict";
7363
+ init_tool_backend();
7364
+ init_live_session_scan();
7365
+ init_backends();
7366
+ }
7367
+ });
7368
+
8034
7369
  // ../engine/dist/core/session-outcome.js
8035
7370
  function extractDecisionsFromTurn(turn) {
8036
7371
  if (turn.kind !== "assistant_text" || !turn.text)
@@ -8127,7 +7462,7 @@ function computeOutcome(sessionId, opts = {}) {
8127
7462
  };
8128
7463
  if (!backend)
8129
7464
  return notFound;
8130
- const turnsRes = backend.extractTurns(sessionId, { assistantMax: 2e3 });
7465
+ const turnsRes = extractTurnsAny(sessionId, { assistantMax: 2e3 });
8131
7466
  if (!turnsRes.found)
8132
7467
  return notFound;
8133
7468
  const decisions = [];
@@ -8166,7 +7501,7 @@ function computeOutcome(sessionId, opts = {}) {
8166
7501
  totalLinesAdded = fast.totalLinesAdded;
8167
7502
  totalLinesRemoved = fast.totalLinesRemoved;
8168
7503
  } else {
8169
- const replay = backend.replay(sessionId);
7504
+ const replay = replaySessionAny(sessionId);
8170
7505
  filesChanged = replay.files.map((f) => f.file);
8171
7506
  totalLinesAdded = replay.totalLinesAdded;
8172
7507
  totalLinesRemoved = replay.totalLinesRemoved;
@@ -8205,6 +7540,7 @@ var init_session_outcome = __esm({
8205
7540
  init_session_sentiment();
8206
7541
  init_session_git();
8207
7542
  init_tool_backend();
7543
+ init_session_multi_tool();
8208
7544
  init_backends();
8209
7545
  DECISION_PATTERNS = [
8210
7546
  /\b(?:I'?ll|I will|I'?m going to|Going to|Let me|We'?ll|We will|We should|Let'?s|I'?m about to|I'?m going to)\b[^\n.!?]*[.!?]/i,
@@ -8340,11 +7676,11 @@ var init_first_prompt = __esm({
8340
7676
  });
8341
7677
 
8342
7678
  // ../engine/dist/core/backends/tail-read.js
8343
- import { openSync as openSync4, readSync as readSync4, closeSync as closeSync4, statSync as statSync7 } from "node:fs";
7679
+ import { openSync as openSync4, readSync as readSync4, closeSync as closeSync4, statSync as statSync6 } from "node:fs";
8344
7680
  function readTailFromOffset(path, offset) {
8345
7681
  let size = 0;
8346
7682
  try {
8347
- size = statSync7(path).size;
7683
+ size = statSync6(path).size;
8348
7684
  } catch {
8349
7685
  return { text: "", newOffset: offset };
8350
7686
  }
@@ -8661,7 +7997,7 @@ var init_generic_engine = __esm({
8661
7997
  });
8662
7998
 
8663
7999
  // ../engine/dist/core/backends/claude.js
8664
- import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync5, statSync as statSync8, openSync as openSync5, readSync as readSync5, closeSync as closeSync5 } from "fs";
8000
+ import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync5, statSync as statSync7, openSync as openSync5, readSync as readSync5, closeSync as closeSync5 } from "fs";
8665
8001
  import { join as join10, basename as basename8 } from "path";
8666
8002
  function stripReminders(text) {
8667
8003
  return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
@@ -8674,6 +8010,7 @@ var UUID_RE, ClaudeBackend, claudeBackend;
8674
8010
  var init_claude2 = __esm({
8675
8011
  "../engine/dist/core/backends/claude.js"() {
8676
8012
  "use strict";
8013
+ init_live_session_scan();
8677
8014
  init_tool_paths();
8678
8015
  init_live_session_scan();
8679
8016
  init_session_outcome();
@@ -8790,7 +8127,7 @@ var init_claude2 = __esm({
8790
8127
  let mtime = 0;
8791
8128
  for (const c of copies) {
8792
8129
  try {
8793
- mtime = Math.max(mtime, statSync8(c.path).mtimeMs);
8130
+ mtime = Math.max(mtime, statSync7(c.path).mtimeMs);
8794
8131
  } catch {
8795
8132
  }
8796
8133
  }
@@ -8866,7 +8203,7 @@ var init_claude2 = __esm({
8866
8203
  const fullPath = join10(projPath, f);
8867
8204
  let stat3;
8868
8205
  try {
8869
- stat3 = statSync8(fullPath);
8206
+ stat3 = statSync7(fullPath);
8870
8207
  } catch {
8871
8208
  continue;
8872
8209
  }
@@ -8936,6 +8273,9 @@ var init_claude2 = __esm({
8936
8273
  * splits show up alongside the parent.
8937
8274
  */
8938
8275
  readEvents(rawId) {
8276
+ return memoInSessionScope(`events:claude:${rawId}`, () => this.#readEventsUncached(rawId));
8277
+ }
8278
+ #readEventsUncached(rawId) {
8939
8279
  const groups = resolveSessionContentGroups(rawId);
8940
8280
  if (groups.length === 0)
8941
8281
  return [];
@@ -9084,7 +8424,7 @@ var init_claude2 = __esm({
9084
8424
  const events = this.readEvents(rawId);
9085
8425
  let fileMtime = 0;
9086
8426
  try {
9087
- fileMtime = statSync8(located.path).mtimeMs;
8427
+ fileMtime = statSync7(located.path).mtimeMs;
9088
8428
  } catch {
9089
8429
  }
9090
8430
  return liveScanEditsFromEvents(events, this.fileToolMap, {
@@ -9139,13 +8479,13 @@ var init_claude2 = __esm({
9139
8479
  continue;
9140
8480
  const fp = join10(projPath, f);
9141
8481
  try {
9142
- const st = statSync8(fp);
8482
+ const st = statSync7(fp);
9143
8483
  let mtime = st.mtimeMs;
9144
8484
  const subDir = join10(projPath, f.slice(0, -6), "subagents");
9145
8485
  if (existsSync10(subDir)) {
9146
8486
  for (const sub of readdirSync7(subDir)) {
9147
8487
  try {
9148
- const subSt = statSync8(join10(subDir, sub));
8488
+ const subSt = statSync7(join10(subDir, sub));
9149
8489
  if (subSt.mtimeMs > mtime)
9150
8490
  mtime = subSt.mtimeMs;
9151
8491
  } catch {
@@ -9210,7 +8550,7 @@ var init_claude2 = __esm({
9210
8550
  let total = 0;
9211
8551
  for (const located of findSessionFiles(this.toRawId(prefixedId))) {
9212
8552
  try {
9213
- total += statSync8(located.path).size;
8553
+ total += statSync7(located.path).size;
9214
8554
  } catch {
9215
8555
  }
9216
8556
  }
@@ -9823,7 +9163,7 @@ var init_opencode = __esm({
9823
9163
  });
9824
9164
 
9825
9165
  // ../engine/dist/core/backends/codex.js
9826
- import { readdirSync as readdirSync8, readFileSync as readFileSync6, statSync as statSync9, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
9166
+ import { readdirSync as readdirSync8, readFileSync as readFileSync6, statSync as statSync8, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
9827
9167
  import { basename as basename9, dirname as dirname10, join as join12 } from "path";
9828
9168
  function parseShellWrites(cmd) {
9829
9169
  const out = [];
@@ -10031,7 +9371,7 @@ var init_codex2 = __esm({
10031
9371
  return null;
10032
9372
  let mtime = 0;
10033
9373
  try {
10034
- mtime = statSync9(located.path).mtimeMs;
9374
+ mtime = statSync8(located.path).mtimeMs;
10035
9375
  } catch {
10036
9376
  }
10037
9377
  return {
@@ -10051,7 +9391,7 @@ var init_codex2 = __esm({
10051
9391
  for (const filePath of walkRollouts(root)) {
10052
9392
  let stat3;
10053
9393
  try {
10054
- stat3 = statSync9(filePath);
9394
+ stat3 = statSync8(filePath);
10055
9395
  } catch {
10056
9396
  continue;
10057
9397
  }
@@ -10247,7 +9587,7 @@ var init_codex2 = __esm({
10247
9587
  let mtime = 0;
10248
9588
  try {
10249
9589
  raw = readFileSync6(path, "utf-8");
10250
- mtime = statSync9(path).mtimeMs;
9590
+ mtime = statSync8(path).mtimeMs;
10251
9591
  } catch {
10252
9592
  return [];
10253
9593
  }
@@ -10347,7 +9687,7 @@ var init_codex2 = __esm({
10347
9687
  const events = this.readEvents(this.toRawId(id));
10348
9688
  let fileMtime = 0;
10349
9689
  try {
10350
- fileMtime = statSync9(located.path).mtimeMs;
9690
+ fileMtime = statSync8(located.path).mtimeMs;
10351
9691
  } catch {
10352
9692
  }
10353
9693
  return liveScanEditsFromEvents(events, this.fileToolMap, {
@@ -10411,7 +9751,7 @@ var init_codex2 = __esm({
10411
9751
  const p = join12(dir, entry.name);
10412
9752
  let st;
10413
9753
  try {
10414
- st = statSync9(p);
9754
+ st = statSync8(p);
10415
9755
  } catch {
10416
9756
  continue;
10417
9757
  }
@@ -10478,7 +9818,7 @@ var init_codex2 = __esm({
10478
9818
  let mtime = 0;
10479
9819
  const push = (path, name) => {
10480
9820
  try {
10481
- const st = statSync9(path);
9821
+ const st = statSync8(path);
10482
9822
  files.push({ name, bytes: readFileSync6(path) });
10483
9823
  if (st.mtimeMs > mtime)
10484
9824
  mtime = st.mtimeMs;
@@ -10520,7 +9860,7 @@ var init_codex2 = __esm({
10520
9860
  if (!loc)
10521
9861
  return 0;
10522
9862
  try {
10523
- return statSync9(loc.path).size;
9863
+ return statSync8(loc.path).size;
10524
9864
  } catch {
10525
9865
  return 0;
10526
9866
  }
@@ -10537,7 +9877,7 @@ var init_codex2 = __esm({
10537
9877
  });
10538
9878
 
10539
9879
  // ../engine/dist/core/backends/agy.js
10540
- import { existsSync as existsSync12, readdirSync as readdirSync9, readFileSync as readFileSync7, statSync as statSync10 } from "fs";
9880
+ import { existsSync as existsSync12, readdirSync as readdirSync9, readFileSync as readFileSync7, statSync as statSync9 } from "fs";
10541
9881
  import { basename as basename10, dirname as dirname11, join as join13 } from "path";
10542
9882
  function agyTranscriptPath(logsDir) {
10543
9883
  const full = join13(logsDir, "transcript_full.jsonl");
@@ -10730,7 +10070,7 @@ var init_agy = __esm({
10730
10070
  return null;
10731
10071
  let mtime = 0;
10732
10072
  try {
10733
- mtime = statSync10(path).mtimeMs;
10073
+ mtime = statSync9(path).mtimeMs;
10734
10074
  } catch {
10735
10075
  }
10736
10076
  const projectPath = resolveAgyProject(dirname11(path), this.homeDir());
@@ -10758,7 +10098,7 @@ var init_agy = __esm({
10758
10098
  for (const rawId of sessionDirs) {
10759
10099
  const sessionPath = join13(brainDir, rawId);
10760
10100
  try {
10761
- if (!statSync10(sessionPath).isDirectory())
10101
+ if (!statSync9(sessionPath).isDirectory())
10762
10102
  continue;
10763
10103
  } catch {
10764
10104
  continue;
@@ -10768,7 +10108,7 @@ var init_agy = __esm({
10768
10108
  continue;
10769
10109
  let stat3;
10770
10110
  try {
10771
- stat3 = statSync10(filePath);
10111
+ stat3 = statSync9(filePath);
10772
10112
  } catch {
10773
10113
  continue;
10774
10114
  }
@@ -11033,7 +10373,7 @@ var init_agy = __esm({
11033
10373
  for (const rawId of sessionDirs) {
11034
10374
  const sessionPath = join13(brainDir, rawId);
11035
10375
  try {
11036
- if (!statSync10(sessionPath).isDirectory())
10376
+ if (!statSync9(sessionPath).isDirectory())
11037
10377
  continue;
11038
10378
  } catch {
11039
10379
  continue;
@@ -11042,7 +10382,7 @@ var init_agy = __esm({
11042
10382
  if (!filePath)
11043
10383
  continue;
11044
10384
  try {
11045
- const st = statSync10(filePath);
10385
+ const st = statSync9(filePath);
11046
10386
  if (st.mtimeMs < opts.sinceMs)
11047
10387
  continue;
11048
10388
  candidates2.push({ rawId, mtime: st.mtimeMs });
@@ -11070,7 +10410,7 @@ var init_agy = __esm({
11070
10410
  try {
11071
10411
  return {
11072
10412
  tool: "agy",
11073
- mtime: statSync10(loc.path).mtimeMs,
10413
+ mtime: statSync9(loc.path).mtimeMs,
11074
10414
  files: [{ name: basename10(loc.path), bytes: readFileSync7(loc.path) }]
11075
10415
  };
11076
10416
  } catch {
@@ -11085,7 +10425,7 @@ var init_agy = __esm({
11085
10425
  if (!loc)
11086
10426
  return 0;
11087
10427
  try {
11088
- return statSync10(loc.path).size;
10428
+ return statSync9(loc.path).size;
11089
10429
  } catch {
11090
10430
  return 0;
11091
10431
  }
@@ -11102,7 +10442,7 @@ var init_agy = __esm({
11102
10442
  });
11103
10443
 
11104
10444
  // ../engine/dist/core/backends/cursor-store.js
11105
- import { existsSync as existsSync13, readdirSync as readdirSync10, readFileSync as readFileSync8, statSync as statSync11 } from "fs";
10445
+ import { existsSync as existsSync13, readdirSync as readdirSync10, readFileSync as readFileSync8, statSync as statSync10 } from "fs";
11106
10446
  import { join as join14 } from "path";
11107
10447
  function cursorProjectSlug(absPath) {
11108
10448
  return absPath.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
@@ -11119,7 +10459,7 @@ function listChatsIn(chatsRoot) {
11119
10459
  const hashPath3 = join14(chatsRoot, workspaceHash);
11120
10460
  let chatIds;
11121
10461
  try {
11122
- if (!statSync11(hashPath3).isDirectory())
10462
+ if (!statSync10(hashPath3).isDirectory())
11123
10463
  continue;
11124
10464
  chatIds = readdirSync10(hashPath3);
11125
10465
  } catch {
@@ -11141,7 +10481,7 @@ function listChatsIn(chatsRoot) {
11141
10481
  let created = meta.createdAtMs ?? 0;
11142
10482
  if (!mtime || !created) {
11143
10483
  try {
11144
- const st = statSync11(dbPath);
10484
+ const st = statSync10(dbPath);
11145
10485
  mtime = mtime || st.mtimeMs;
11146
10486
  created = created || st.birthtimeMs || st.mtimeMs;
11147
10487
  } catch {
@@ -11184,7 +10524,7 @@ function findChat(chatsRoots, chatId) {
11184
10524
  let created = meta.createdAtMs ?? 0;
11185
10525
  if (!mtime || !created) {
11186
10526
  try {
11187
- const st = statSync11(dbPath);
10527
+ const st = statSync10(dbPath);
11188
10528
  mtime = mtime || st.mtimeMs;
11189
10529
  created = created || st.birthtimeMs || st.mtimeMs;
11190
10530
  } catch {
@@ -11487,7 +10827,7 @@ var init_cursor_store = __esm({
11487
10827
  });
11488
10828
 
11489
10829
  // ../engine/dist/core/backends/cursor-ide.js
11490
- import { existsSync as existsSync14, readFileSync as readFileSync9, readdirSync as readdirSync11, statSync as statSync12 } from "fs";
10830
+ import { existsSync as existsSync14, readFileSync as readFileSync9, readdirSync as readdirSync11, statSync as statSync11 } from "fs";
11491
10831
  import { dirname as dirname12, join as join15 } from "path";
11492
10832
  function jsonOf(value) {
11493
10833
  if (value == null)
@@ -11557,7 +10897,7 @@ function workspaceIndex(workspaceRoots) {
11557
10897
  for (const id of entries) {
11558
10898
  const dir = join15(root, id);
11559
10899
  try {
11560
- if (!statSync12(dir).isDirectory())
10900
+ if (!statSync11(dir).isDirectory())
11561
10901
  continue;
11562
10902
  } catch {
11563
10903
  continue;
@@ -11903,7 +11243,7 @@ var init_cursor_ide = __esm({
11903
11243
  });
11904
11244
 
11905
11245
  // ../engine/dist/core/backends/cursor.js
11906
- import { existsSync as existsSync15, readFileSync as readFileSync10, statSync as statSync13 } from "fs";
11246
+ import { existsSync as existsSync15, readFileSync as readFileSync10, statSync as statSync12 } from "fs";
11907
11247
  import { join as join16 } from "path";
11908
11248
  var PREFIX4, CursorBackend, cursorBackend;
11909
11249
  var init_cursor = __esm({
@@ -11956,7 +11296,7 @@ var init_cursor = __esm({
11956
11296
  if (r.surface === "cli") {
11957
11297
  let mtime2 = r.chat.updatedAtMs;
11958
11298
  try {
11959
- mtime2 = statSync13(r.chat.dbPath).mtimeMs || mtime2;
11299
+ mtime2 = statSync12(r.chat.dbPath).mtimeMs || mtime2;
11960
11300
  } catch {
11961
11301
  }
11962
11302
  return {
@@ -11969,7 +11309,7 @@ var init_cursor = __esm({
11969
11309
  }
11970
11310
  let mtime = r.composer.updatedAtMs;
11971
11311
  try {
11972
- mtime = statSync13(r.composer.globalDbPath).mtimeMs || mtime;
11312
+ mtime = statSync12(r.composer.globalDbPath).mtimeMs || mtime;
11973
11313
  } catch {
11974
11314
  }
11975
11315
  return {
@@ -12215,7 +11555,7 @@ var init_cursor = __esm({
12215
11555
  if (existsSync15(metaPath)) {
12216
11556
  files.push({ name: "meta.json", bytes: readFileSync10(metaPath) });
12217
11557
  }
12218
- return { tool: "cursor", mtime: statSync13(r.chat.dbPath).mtimeMs, files };
11558
+ return { tool: "cursor", mtime: statSync12(r.chat.dbPath).mtimeMs, files };
12219
11559
  }
12220
11560
  const events = readComposerEvents(r.composer);
12221
11561
  if (events.length === 0)
@@ -12223,7 +11563,7 @@ var init_cursor = __esm({
12223
11563
  const body = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
12224
11564
  return {
12225
11565
  tool: "cursor",
12226
- mtime: statSync13(r.composer.globalDbPath).mtimeMs,
11566
+ mtime: statSync12(r.composer.globalDbPath).mtimeMs,
12227
11567
  files: [{ name: `${r.composer.composerId}.jsonl`, bytes: Buffer.from(body, "utf8") }]
12228
11568
  };
12229
11569
  } catch {
@@ -12240,75 +11580,525 @@ var init_cursor = __esm({
12240
11580
  return "";
12241
11581
  }
12242
11582
  }
12243
- };
12244
- cursorBackend = new CursorBackend();
11583
+ };
11584
+ cursorBackend = new CursorBackend();
11585
+ }
11586
+ });
11587
+
11588
+ // ../engine/dist/core/backends/index.js
11589
+ var backends_exports = {};
11590
+ __export(backends_exports, {
11591
+ AgyBackend: () => AgyBackend,
11592
+ ClaudeBackend: () => ClaudeBackend,
11593
+ CodexBackend: () => CodexBackend,
11594
+ CursorBackend: () => CursorBackend,
11595
+ OpencodeBackend: () => OpencodeBackend,
11596
+ agyBackend: () => agyBackend,
11597
+ bootstrapBackends: () => bootstrapBackends,
11598
+ claudeBackend: () => claudeBackend,
11599
+ codexBackend: () => codexBackend,
11600
+ cursorBackend: () => cursorBackend,
11601
+ getBackend: () => getBackend,
11602
+ getBackendForId: () => getBackendForId,
11603
+ listAllBackends: () => listAllBackends,
11604
+ listAvailableBackends: () => listAvailableBackends,
11605
+ opencodeBackend: () => opencodeBackend,
11606
+ registerBackend: () => registerBackend,
11607
+ tryGetBackend: () => tryGetBackend
11608
+ });
11609
+ function bootstrapBackends() {
11610
+ registerBackend(claudeBackend);
11611
+ registerBackend(opencodeBackend);
11612
+ registerBackend(codexBackend);
11613
+ registerBackend(agyBackend);
11614
+ registerBackend(cursorBackend);
11615
+ }
11616
+ var init_backends = __esm({
11617
+ "../engine/dist/core/backends/index.js"() {
11618
+ "use strict";
11619
+ init_tool_backend();
11620
+ init_claude2();
11621
+ init_opencode();
11622
+ init_codex2();
11623
+ init_agy();
11624
+ init_cursor();
11625
+ init_tool_backend();
11626
+ _setRegistryBootstrapper(bootstrapBackends);
11627
+ }
11628
+ });
11629
+
11630
+ // ../engine/dist/core/collector-health.js
11631
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "node:fs";
11632
+ import { join as join17, dirname as dirname13 } from "node:path";
11633
+ function applySyncOutcome(prior, outcome, now = Date.now()) {
11634
+ const t = { lastOkAt: prior?.lastOkAt ?? null, failures: prior?.failures ?? 0 };
11635
+ if (prior?.lastError !== void 0)
11636
+ t.lastError = prior.lastError;
11637
+ if (outcome.ok) {
11638
+ t.lastOkAt = now;
11639
+ t.failures = 0;
11640
+ delete t.lastError;
11641
+ return t;
11642
+ }
11643
+ if ((outcome.accepted ?? 0) > 0)
11644
+ t.lastOkAt = now;
11645
+ t.failures += 1;
11646
+ t.lastError = (outcome.error ?? "unknown error").slice(0, 160);
11647
+ return t;
11648
+ }
11649
+ function collectorHealthPath() {
11650
+ return healthPath();
11651
+ }
11652
+ function readCollectorHealth() {
11653
+ try {
11654
+ const h = JSON.parse(readFileSync11(healthPath(), "utf-8"));
11655
+ return h && h.v === 1 && typeof h.updatedAt === "number" ? h : null;
11656
+ } catch {
11657
+ return null;
11658
+ }
11659
+ }
11660
+ function writeCollectorHealth(h) {
11661
+ try {
11662
+ const p = healthPath();
11663
+ mkdirSync4(dirname13(p), { recursive: true });
11664
+ writeFileSync3(p, JSON.stringify(h));
11665
+ } catch {
11666
+ }
11667
+ }
11668
+ function updateCollectorHealth(patch) {
11669
+ const prior = readCollectorHealth();
11670
+ writeCollectorHealth({
11671
+ ...prior,
11672
+ v: 1,
11673
+ updatedAt: Date.now(),
11674
+ startedAt: prior?.startedAt ?? Date.now(),
11675
+ restartsLastHour: prior?.restartsLastHour ?? 0,
11676
+ targets: prior?.targets ?? {},
11677
+ ...patch
11678
+ });
11679
+ }
11680
+ function reportWalkProgress(p) {
11681
+ const now = Date.now();
11682
+ const boundary = p.complete || p.done === 0;
11683
+ if (!boundary && now - lastProgressWrite < PROGRESS_WRITE_INTERVAL_MS)
11684
+ return;
11685
+ lastProgressWrite = now;
11686
+ updateCollectorHealth({ progress: p });
11687
+ }
11688
+ var healthPath, PROGRESS_WRITE_INTERVAL_MS, lastProgressWrite, STALE_AFTER_MS;
11689
+ var init_collector_health = __esm({
11690
+ "../engine/dist/core/collector-health.js"() {
11691
+ "use strict";
11692
+ init_paths();
11693
+ healthPath = () => join17(getDataDir(), "collector-health.json");
11694
+ PROGRESS_WRITE_INTERVAL_MS = 2e3;
11695
+ lastProgressWrite = 0;
11696
+ STALE_AFTER_MS = 30 * 6e4;
11697
+ }
11698
+ });
11699
+
11700
+ // ../engine/dist/core/credentials-env.js
11701
+ function envCredentials(env = process.env) {
11702
+ const token = (env.CHAT_RECALL_TOKEN || "").trim();
11703
+ const server = (env.CHAT_RECALL_SERVER || "").trim();
11704
+ if (!token || !server)
11705
+ return null;
11706
+ if (!/^https?:\/\//i.test(server))
11707
+ return null;
11708
+ return { serverUrl: normalise(server), token };
11709
+ }
11710
+ var normalise;
11711
+ var init_credentials_env = __esm({
11712
+ "../engine/dist/core/credentials-env.js"() {
11713
+ "use strict";
11714
+ normalise = (u) => u.trim().replace(/\/+$/, "");
11715
+ }
11716
+ });
11717
+
11718
+ // ../engine/dist/core/secret-redactor.js
11719
+ import { statSync as statSync13 } from "fs";
11720
+ function secretOf(rule, m) {
11721
+ const raw = rule.secret != null ? m[rule.secret] : m[0];
11722
+ if (raw == null)
11723
+ return null;
11724
+ if (rule.where && !rule.where(raw))
11725
+ return null;
11726
+ return raw;
11727
+ }
11728
+ function looksLikeSecretToken(t) {
11729
+ if (t.length < 32)
11730
+ return false;
11731
+ if (/^[a-f0-9]+$/i.test(t))
11732
+ return true;
11733
+ return /[A-Z]/.test(t) && /[a-z]/.test(t) && /[0-9]/.test(t);
11734
+ }
11735
+ function findContextualSecrets(text) {
11736
+ if (!text)
11737
+ return [];
11738
+ const ranges = [];
11739
+ let m;
11740
+ SECRET_CONTEXT_RE.lastIndex = 0;
11741
+ while ((m = SECRET_CONTEXT_RE.exec(text)) !== null) {
11742
+ const winStart = m.index + m[0].length;
11743
+ const window = text.slice(winStart, winStart + 80);
11744
+ HIGH_ENTROPY_TOKEN_RE.lastIndex = 0;
11745
+ let tm;
11746
+ while ((tm = HIGH_ENTROPY_TOKEN_RE.exec(window)) !== null) {
11747
+ if (!looksLikeSecretToken(tm[0]))
11748
+ continue;
11749
+ if (/^[a-f0-9]+$/i.test(tm[0]) && /\b(?:commit|sha-?\d*|hash|digest|checksum|uuid|guid|etag|blob|ref)\b/i.test(window.slice(0, tm.index))) {
11750
+ continue;
11751
+ }
11752
+ ranges.push({ start: winStart + tm.index, end: winStart + tm.index + tm[0].length });
11753
+ break;
11754
+ }
11755
+ }
11756
+ ranges.sort((a, b) => a.start - b.start);
11757
+ const out = [];
11758
+ for (const r of ranges) {
11759
+ const last = out[out.length - 1];
11760
+ if (last && r.start < last.end) {
11761
+ last.end = Math.max(last.end, r.end);
11762
+ continue;
11763
+ }
11764
+ out.push({ ...r });
11765
+ }
11766
+ return out;
11767
+ }
11768
+ function isEnvRedactionRequested() {
11769
+ const v = process.env.CHAT_RECALL_REDACT_INDEX;
11770
+ if (v === void 0)
11771
+ return null;
11772
+ return v === "1" || v === "true" || v === "yes";
11773
+ }
11774
+ function settingsView() {
11775
+ let mtimeMs = -1;
11776
+ try {
11777
+ mtimeMs = statSync13(settingsFilePath()).mtimeMs;
11778
+ } catch {
11779
+ }
11780
+ if (cache && cache.mtimeMs === mtimeMs)
11781
+ return cache;
11782
+ let enabled = false;
11783
+ const extra = [];
11784
+ try {
11785
+ const s = loadSettings();
11786
+ enabled = !!s.privacy?.redactIndex;
11787
+ for (const r of s.privacy?.redactionRules ?? []) {
11788
+ try {
11789
+ extra.push({ label: r.label, pattern: new RegExp(r.pattern, "g") });
11790
+ } catch {
11791
+ }
11792
+ }
11793
+ } catch {
12245
11794
  }
12246
- });
12247
-
12248
- // ../engine/dist/core/backends/index.js
12249
- var backends_exports = {};
12250
- __export(backends_exports, {
12251
- AgyBackend: () => AgyBackend,
12252
- ClaudeBackend: () => ClaudeBackend,
12253
- CodexBackend: () => CodexBackend,
12254
- CursorBackend: () => CursorBackend,
12255
- OpencodeBackend: () => OpencodeBackend,
12256
- agyBackend: () => agyBackend,
12257
- bootstrapBackends: () => bootstrapBackends,
12258
- claudeBackend: () => claudeBackend,
12259
- codexBackend: () => codexBackend,
12260
- cursorBackend: () => cursorBackend,
12261
- getBackend: () => getBackend,
12262
- getBackendForId: () => getBackendForId,
12263
- listAllBackends: () => listAllBackends,
12264
- listAvailableBackends: () => listAvailableBackends,
12265
- opencodeBackend: () => opencodeBackend,
12266
- registerBackend: () => registerBackend,
12267
- tryGetBackend: () => tryGetBackend
12268
- });
12269
- function bootstrapBackends() {
12270
- registerBackend(claudeBackend);
12271
- registerBackend(opencodeBackend);
12272
- registerBackend(codexBackend);
12273
- registerBackend(agyBackend);
12274
- registerBackend(cursorBackend);
11795
+ cache = { mtimeMs, enabled, rules: [...DEFAULT_REDACTION_RULES, ...extra] };
11796
+ return cache;
12275
11797
  }
12276
- var init_backends = __esm({
12277
- "../engine/dist/core/backends/index.js"() {
12278
- "use strict";
12279
- init_tool_backend();
12280
- init_claude2();
12281
- init_opencode();
12282
- init_codex2();
12283
- init_agy();
12284
- init_cursor();
12285
- init_tool_backend();
12286
- _setRegistryBootstrapper(bootstrapBackends);
11798
+ function compileServerRule(spec) {
11799
+ const name = spec.name || "(unnamed)";
11800
+ if (!spec.regex)
11801
+ return { name, reason: "empty pattern" };
11802
+ if (spec.regex.length > MAX_PATTERN_LEN)
11803
+ return { name, reason: `pattern longer than ${MAX_PATTERN_LEN} chars` };
11804
+ let re;
11805
+ try {
11806
+ const flags = "g" + (spec.flags || "").replace(/[^ims]/g, "");
11807
+ re = new RegExp(spec.regex, flags);
11808
+ } catch (e) {
11809
+ return { name, reason: `invalid regex: ${e instanceof Error ? e.message : e}` };
12287
11810
  }
12288
- });
12289
-
12290
- // ../engine/dist/core/credentials-env.js
12291
- function envCredentials(env = process.env) {
12292
- const token = (env.CHAT_RECALL_TOKEN || "").trim();
12293
- const server = (env.CHAT_RECALL_SERVER || "").trim();
12294
- if (!token || !server)
12295
- return null;
12296
- if (!/^https?:\/\//i.test(server))
12297
- return null;
12298
- return { serverUrl: normalise(server), token };
11811
+ if (re.test(""))
11812
+ return { name, reason: "matches the empty string (would redact everything)" };
11813
+ re.lastIndex = 0;
11814
+ if (re.test(CANARY))
11815
+ return { name, reason: "matches benign text \u2014 too broad" };
11816
+ re.lastIndex = 0;
11817
+ return { label: `${spec.source === "pack" ? "pack" : "tenant"}:${name}`, pattern: re };
12299
11818
  }
12300
- var normalise;
12301
- var init_credentials_env = __esm({
12302
- "../engine/dist/core/credentials-env.js"() {
11819
+ function installServerRulePack(pack) {
11820
+ const rejected = [];
11821
+ const rules = [];
11822
+ for (const spec of pack.rules || []) {
11823
+ if (rules.length >= MAX_PACK_RULES) {
11824
+ rejected.push({ name: spec.name || "(unnamed)", reason: `pack exceeds ${MAX_PACK_RULES} rules` });
11825
+ continue;
11826
+ }
11827
+ const out = compileServerRule(spec);
11828
+ if ("reason" in out)
11829
+ rejected.push(out);
11830
+ else
11831
+ rules.push(out);
11832
+ }
11833
+ const version = pack.version || "";
11834
+ serverPack = { version, rules };
11835
+ return { version, accepted: rules.length, rejected };
11836
+ }
11837
+ function serverRulePackVersion() {
11838
+ return serverPack ? serverPack.version || "unversioned" : null;
11839
+ }
11840
+ function activeRules() {
11841
+ const base2 = settingsView().rules;
11842
+ return serverPack && serverPack.rules.length > 0 ? [...base2, ...serverPack.rules] : base2;
11843
+ }
11844
+ function isRedactionEnabled() {
11845
+ const env = isEnvRedactionRequested();
11846
+ if (env !== null)
11847
+ return env;
11848
+ return settingsView().enabled;
11849
+ }
11850
+ function redactSecrets(text, opts = {}) {
11851
+ if (!opts.force && !isRedactionEnabled())
11852
+ return text;
11853
+ if (!text)
11854
+ return text;
11855
+ const rules = opts.rules || activeRules();
11856
+ let out = text;
11857
+ for (const r of rules) {
11858
+ out = out.replace(r.pattern, (...args) => {
11859
+ const whole = args[0];
11860
+ const m = args.slice(0, args.length);
11861
+ const secret = secretOf(r, m);
11862
+ if (secret == null)
11863
+ return whole;
11864
+ if (opts.count)
11865
+ opts.count.redactions++;
11866
+ return whole.slice(0, whole.length - secret.length) + `[REDACTED:${r.label}]`;
11867
+ });
11868
+ }
11869
+ const ctx = findContextualSecrets(out);
11870
+ for (let i = ctx.length - 1; i >= 0; i--) {
11871
+ const { start, end } = ctx[i];
11872
+ if (opts.count)
11873
+ opts.count.redactions++;
11874
+ out = out.slice(0, start) + "[REDACTED:secret-context]" + out.slice(end);
11875
+ }
11876
+ return out;
11877
+ }
11878
+ function maskSecret(s) {
11879
+ if (!s)
11880
+ return "";
11881
+ if (s.length <= 4)
11882
+ return "*".repeat(s.length);
11883
+ return "*".repeat(Math.min(s.length - 4, 60)) + s.slice(-4);
11884
+ }
11885
+ function globalRegexFor(pattern) {
11886
+ const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
11887
+ const key2 = pattern.source + "\0" + flags;
11888
+ let re = globalRegexCache.get(key2);
11889
+ if (!re) {
11890
+ re = new RegExp(pattern.source, flags);
11891
+ globalRegexCache.set(key2, re);
11892
+ }
11893
+ return re;
11894
+ }
11895
+ function makeLineResolver(text) {
11896
+ const starts = [];
11897
+ for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1))
11898
+ starts.push(i);
11899
+ return (index) => {
11900
+ let lo = 0, hi = starts.length;
11901
+ while (lo < hi) {
11902
+ const mid = lo + hi >> 1;
11903
+ if (starts[mid] < index)
11904
+ lo = mid + 1;
11905
+ else
11906
+ hi = mid;
11907
+ }
11908
+ return lo + 1;
11909
+ };
11910
+ }
11911
+ function scanTextForFindings(text, opts = {}) {
11912
+ if (!text)
11913
+ return [];
11914
+ const rules = opts.rules || activeRules();
11915
+ const findings = [];
11916
+ const seenSpans = /* @__PURE__ */ new Set();
11917
+ const lineAt = makeLineResolver(text);
11918
+ for (const r of rules) {
11919
+ const re = globalRegexFor(r.pattern);
11920
+ re.lastIndex = 0;
11921
+ let m;
11922
+ while ((m = re.exec(text)) !== null) {
11923
+ const secret = secretOf(r, m);
11924
+ if (secret == null) {
11925
+ if (re.lastIndex === m.index)
11926
+ re.lastIndex++;
11927
+ continue;
11928
+ }
11929
+ const at = m.index + (m[0].length - secret.length);
11930
+ const span = `${at}:${secret.length}`;
11931
+ if (!seenSpans.has(span)) {
11932
+ seenSpans.add(span);
11933
+ findings.push({ rule: r.label, line: lineAt(at), preview: maskSecret(secret) });
11934
+ }
11935
+ if (re.lastIndex === m.index)
11936
+ re.lastIndex++;
11937
+ }
11938
+ }
11939
+ for (const { start, end } of findContextualSecrets(text)) {
11940
+ const line = lineAt(start);
11941
+ findings.push({ rule: "secret-context", line, preview: maskSecret(text.slice(start, end)) });
11942
+ }
11943
+ return findings;
11944
+ }
11945
+ var DEFAULT_REDACTION_RULES, SECRET_CONTEXT_RE, HIGH_ENTROPY_TOKEN_RE, cache, CANARY, MAX_PACK_RULES, MAX_PATTERN_LEN, serverPack, globalRegexCache;
11946
+ var init_secret_redactor = __esm({
11947
+ "../engine/dist/core/secret-redactor.js"() {
12303
11948
  "use strict";
12304
- normalise = (u) => u.trim().replace(/\/+$/, "");
11949
+ init_settings();
11950
+ DEFAULT_REDACTION_RULES = [
11951
+ // AWS access tokens. Prefix-anchored: AKIA, ASIA, ABIA (long-term,
11952
+ // STS, EC2). Always exactly 16 hex/alphanumeric after the prefix.
11953
+ // NOTE: this matches only the access-key-ID, NOT the secret key value —
11954
+ // the `env-secret` rule below covers AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN.
11955
+ { label: "aws-access-token", pattern: /\b(?:AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}\b/g },
11956
+ // AWS STS session tokens. These are long base64 blobs with NO `NAME=` context
11957
+ // when pasted bare into prose/code, so `env-secret` misses them — but they
11958
+ // reliably start with one of a handful of base64 prefixes (the encoded
11959
+ // `{"...` JSON header). Prefix-anchored → low false-positive. This is the
11960
+ // bare-value form that leaked (`search('IQoJEXAMPLEfakeSESSIONtokenNOTreal01234567890abcDEF==...')`).
11961
+ { label: "aws-session-token", pattern: /\b(?:IQoJ|FwoG|FwoH|FQoG|FQoH|FwoD)[A-Za-z0-9/+]{40,}={0,2}/g },
11962
+ // Env/shell-assignment secrets — a VAR whose name ends in KEY/SECRET/TOKEN/
11963
+ // PASSWORD/CREDENTIAL, then `=`/`:`, then the value. Zero-width lookbehind so
11964
+ // only the VALUE is redacted (the var name stays for context). This is the
11965
+ // dominant real-world leak vector (`export` dumps, .env files, `curl -H`,
11966
+ // tool output) and is exactly what leaked AWS_SECRET_ACCESS_KEY and
11967
+ // AWS_SESSION_TOKEN in cleartext while the access-key-ID rule above caught
11968
+ // only the (least-sensitive) ID.
11969
+ // Suffix list deliberately broad: DB_PASS (our own helm chart's name!),
11970
+ // NPM_AUTH, SENTRY_DSN, SESSION/COOKIE secrets and BEARER vars all leaked
11971
+ // past the original KEY/SECRET/TOKEN/PASSWORD-only list. Values under 12
11972
+ // chars don't redact, so flag-ish vars (BYPASS=true) stay readable.
11973
+ // Context CONSUMED, secret in group 1 — see RedactionRule.secret. As a
11974
+ // lookbehind this was the second most expensive rule in the collector.
11975
+ { label: "env-secret", secret: 1, pattern: /\b[A-Z][A-Z0-9_]*(?:KEY|SECRET|TOKEN|PASSWORD|PASSWD|PASS|PWD|CREDENTIAL|AUTH|DSN|SESSION|COOKIE|BEARER|SIGNATURE)S?["']?[ \t]{0,3}[:=][ \t]{0,3}["']?([A-Za-z0-9/+_.~=:@-]{12,})/g },
11976
+ // Credentials embedded in a connection URL: scheme://user:PASSWORD@host.
11977
+ // Redacts only the password segment (keeps scheme/user/host for context).
11978
+ //
11979
+ // Scheme-agnostic on purpose. The old rule allowlisted eight DB schemes, so
11980
+ // `https://user:pass@host`, `ssh://`, `ftp://`, `smtp://` and every other
11981
+ // userinfo URL walked through in cleartext — and a `jdbc:postgresql://…`
11982
+ // prefix broke the \b anchor for the one family it did cover. Any scheme
11983
+ // followed by userinfo is a credential, so match the shape, not a list.
11984
+ { label: "url-password", secret: 1, pattern: /\b[a-zA-Z][a-zA-Z0-9+.-]{1,31}:\/\/[^:@\s/]{1,64}:([^@\s/]{1,256})(?=@)/g },
11985
+ // Bare AWS secret-access-key VALUE: EXACTLY 40 base64 chars, word-bounded,
11986
+ // containing upper+lower+digit. This is the gitleaks heuristic — the mixed-case
11987
+ // requirement excludes 40-char lowercase-hex git SHAs, and the exact-40 bound
11988
+ // excludes longer blobs/tokens. It catches the value when it appears bare in
11989
+ // prose with NO `NAME=` and NO nearby context word (e.g. inside a markdown
11990
+ // table or quoted in analysis) — the residual leak path the env-secret and
11991
+ // contextual rules both miss. Placed AFTER env-secret/url-password so the
11992
+ // keyed/URL forms keep their more-specific labels.
11993
+ // The three stacked lookaheads each re-scanned the same 40 characters, which
11994
+ // made this the MOST expensive rule of all 106 the collector runs (13.1% of a
11995
+ // 29.5 MB scan). The mixed-case-plus-digit test is now one JS pass.
11996
+ {
11997
+ label: "aws-secret-key",
11998
+ secret: 1,
11999
+ pattern: /(?<![A-Za-z0-9/+])([A-Za-z0-9/+]{40})(?![A-Za-z0-9/+])/g,
12000
+ where: (v) => /[a-z]/.test(v) && /[A-Z]/.test(v) && /[0-9]/.test(v)
12001
+ },
12002
+ // GitHub personal access tokens.
12003
+ { label: "github-pat", pattern: /\bghp_[A-Za-z0-9]{36}\b/g },
12004
+ // GitHub fine-grained PATs.
12005
+ { label: "github-fine", pattern: /\bgithub_pat_[A-Za-z0-9_]{82}\b/g },
12006
+ // GitHub OAuth.
12007
+ { label: "github-oauth", pattern: /\bgho_[A-Za-z0-9]{36}\b/g },
12008
+ // GitHub app + refresh.
12009
+ { label: "github-app", pattern: /\b(?:ghs|ghu|ghr)_[A-Za-z0-9]{36}\b/g },
12010
+ // Slack bot/user/app tokens.
12011
+ { label: "slack-token", pattern: /\bxox[abopr]-[A-Za-z0-9-]{10,}\b/g },
12012
+ // Slack incoming webhook URL.
12013
+ { label: "slack-webhook", pattern: /\bhttps:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+\b/g },
12014
+ // Stripe live + test keys.
12015
+ { label: "stripe-key", pattern: /\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{24,}\b/g },
12016
+ // Anthropic.
12017
+ { label: "anthropic-key", pattern: /\bsk-ant-[a-zA-Z0-9_-]{40,}\b/g },
12018
+ // OpenAI. Two shapes, deliberately kept apart. The classic key is
12019
+ // `sk-` + base62 with NO hyphens; the modern scoped keys carry an explicit
12020
+ // proj/svcacct/admin segment first. Allowing hyphens after a bare `sk-`
12021
+ // (which is what admitted sk-proj- in the first place) also matches ordinary
12022
+ // kebab-case text — `sk-fix-the-login-redirect-bug`, `.sk-button-primary`,
12023
+ // `sk-2024-01-01-backup` — and this corpus is full of branch names and CSS
12024
+ // classes. Requiring the segment keeps sk-proj- coverage without that.
12025
+ { label: "openai-key", pattern: /\bsk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,}\b|\bsk-[A-Za-z0-9]{20,}\b/g },
12026
+ // Google API key (AIza…).
12027
+ { label: "google-api-key", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
12028
+ // Generic JWT — three base64url segments.
12029
+ { label: "jwt", pattern: /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g },
12030
+ // Private key blocks (PEM). The label is a free word run rather than a fixed
12031
+ // list, so ENCRYPTED PRIVATE KEY and PGP PRIVATE KEY BLOCK are covered
12032
+ // without another enumeration, and \1 forces END to name the same kind as
12033
+ // BEGIN.
12034
+ //
12035
+ // The body is [^-], NOT [\s\S]*?. That is a performance boundary, not a
12036
+ // style choice. An unanchored prefix with an unbounded lazy gap re-scans to
12037
+ // end-of-input from every candidate start, so text carrying many BEGIN
12038
+ // markers and no matching END costs O(n^2): 500KB of that measured 810ms
12039
+ // here, and this runs synchronously on the server's event loop over
12040
+ // tenant-supplied text (services/secret-rescan.ts). A PEM body is base64
12041
+ // plus headers and contains no '-', so excluding it makes the scan stop at
12042
+ // the next delimiter instead of at EOF — same matches, linear cost.
12043
+ { label: "private-key", pattern: /-----BEGIN ([A-Z0-9 ]{0,32})PRIVATE KEY(?: BLOCK)?-----[^-]{0,10000}?-----END \1PRIVATE KEY(?: BLOCK)?-----/g },
12044
+ // PuTTY keys are not PEM. Match the Private-Lines payload — the actual key
12045
+ // material — rather than spanning the whole file, which would need the same
12046
+ // unbounded gap the rule above just removed. Line-anchored and base64-only,
12047
+ // so it cannot run away.
12048
+ { label: "putty-key", pattern: /^Private-Lines:[ \t]*\d{1,4}[\r\n]+(?:[A-Za-z0-9+/=]{8,120}[\r\n]+){1,400}/gm },
12049
+ // npm tokens (uuid-shaped — overlap with our UUID filter; intentional).
12050
+ { label: "npm-token", pattern: /\bnpm_[A-Za-z0-9]{36}\b/g },
12051
+ // .npmrc auth lines: //registry.npmjs.org/:_authToken=VALUE (the key is not
12052
+ // ALL-CAPS, so the env-secret rule above never matched it). The leading `:`
12053
+ // is required — it is what makes this npmrc syntax rather than any shell var
12054
+ // ending in _AUTH, which env-secret already owns and labels better.
12055
+ // [^\s"'`]+ so the closing quote is not swallowed, and a ${VAR} placeholder
12056
+ // is left alone — redacting it destroys a config example and hides nothing.
12057
+ // The rule the perf profile caught the wedged daemon inside: 98.94% of samples.
12058
+ { label: "npmrc-auth", secret: 1, pattern: /:_(?:authToken|password|auth)[ \t]*=[ \t]*(?!\$\{)([^\s"'`]{12,})/gi },
12059
+ // HTTP Authorization headers. env-secret requires an ALL-CAPS name, and
12060
+ // `Authorization` is mixed case, so both Bearer and Basic walked straight
12061
+ // through — including short opaque tokens the contextual pass also misses
12062
+ // (it needs 32+ chars AND mixed-case-plus-digit).
12063
+ // Same two guards as npmrc-auth: stop at a quote, and skip the obvious
12064
+ // placeholders that fill documentation (<YOUR_TOKEN>, ${TOKEN}, xxx…).
12065
+ { label: "auth-header", secret: 1, pattern: /\bauthorization[ \t]*:[ \t]*(?:bearer|basic|token)[ \t]+(?![<$])([^\s"'`<]{8,})/gi },
12066
+ // Vendor tokens with unambiguous prefixes. Each is a fixed literal, so a
12067
+ // false positive is essentially impossible and the cost of listing is low.
12068
+ { label: "gitlab-pat", pattern: /\bglpat-[A-Za-z0-9_-]{20,}\b/g },
12069
+ { label: "slack-app-token", pattern: /\bxapp-\d-[A-Z0-9]{8,}-\d{10,}-[a-f0-9]{32,}\b/g },
12070
+ { label: "sendgrid-key", pattern: /\bSG\.[A-Za-z0-9_-]{20,24}\.[A-Za-z0-9_-]{40,50}\b/g },
12071
+ // (?<![-\w]) so `cache-key-<md5>` is not read as a mailgun key.
12072
+ { label: "mailgun-key", pattern: /(?<![-\w])key-[a-f0-9]{32}\b/g },
12073
+ { label: "digitalocean-pat", pattern: /\bdop_v1_[a-f0-9]{64}\b/g },
12074
+ { label: "huggingface-token", pattern: /\bhf_[A-Za-z0-9]{30,}\b/g },
12075
+ { label: "shopify-token", pattern: /\bshp(?:at|ca|pa|ss)_[a-fA-F0-9]{32}\b/g },
12076
+ { label: "pypi-token", pattern: /\bpypi-AgEIcHlwaS5vcmc[A-Za-z0-9_-]{50,}\b/g },
12077
+ // No twilio-sid rule: AC…/SK… are Twilio SIDs, which are public account and
12078
+ // key identifiers, not the auth token. Redacting them hid nothing and
12079
+ // collided with 32-hex etags and MD5 sums.
12080
+ // No discord-bot-token rule either: the shape (word.word.word) matched
12081
+ // ordinary dotted symbols such as a.b.ApplicationContextProviderImpl.
12082
+ { label: "telegram-bot-token", pattern: /\b\d{8,10}:AA[A-Za-z0-9_-]{32,}\b/g },
12083
+ { label: "doppler-token", pattern: /\bdp\.(?:pt|st|sa|ct)\.[A-Za-z0-9_-]{20,}\b/g },
12084
+ // Azure Storage SAS signature — the `sig=` parameter is the actual secret.
12085
+ { label: "azure-sas", secret: 1, pattern: /[?&]sig=([A-Za-z0-9%/+=]{20,})/g }
12086
+ ];
12087
+ SECRET_CONTEXT_RE = /\b(?:secret|password|passwd|credential|access[ _-]?key|private[ _-]?key|api[ _-]?key|auth(?:oriz\w*)?|bearer|token)\b/gi;
12088
+ HIGH_ENTROPY_TOKEN_RE = /[A-Za-z0-9/+_-]{32,}/g;
12089
+ cache = null;
12090
+ CANARY = "the quick brown fox jumps over the lazy dog 0123456789 commit 8ba1f109551bd432803012645ac136ddd475d0fb src/index.ts:42";
12091
+ MAX_PACK_RULES = 200;
12092
+ MAX_PATTERN_LEN = 512;
12093
+ serverPack = null;
12094
+ globalRegexCache = /* @__PURE__ */ new Map();
12305
12095
  }
12306
12096
  });
12307
12097
 
12308
12098
  // ../engine/dist/core/source-discovery.js
12309
12099
  import { createHash as createHash3 } from "crypto";
12310
12100
  import { existsSync as existsSync16, readdirSync as readdirSync12, statSync as statSync14 } from "fs";
12311
- import { join as join17 } from "path";
12101
+ import { join as join18 } from "path";
12312
12102
  function sourceId(path) {
12313
12103
  return "src_" + createHash3("sha256").update(path).digest("hex").slice(0, 12);
12314
12104
  }
@@ -12347,7 +12137,7 @@ function countTranscripts(root, tool) {
12347
12137
  return;
12348
12138
  }
12349
12139
  for (const e of entries) {
12350
- const full = join17(dir, e.name);
12140
+ const full = join18(dir, e.name);
12351
12141
  if (e.isDirectory()) {
12352
12142
  walk(full, depth + 1);
12353
12143
  continue;
@@ -12411,9 +12201,9 @@ var init_source_discovery = __esm({
12411
12201
 
12412
12202
  // ../engine/dist/core/home-discovery.js
12413
12203
  import { execFileSync as execFileSync2 } from "child_process";
12414
- import { existsSync as existsSync17, readdirSync as readdirSync13, readFileSync as readFileSync11, realpathSync as realpathSync2, statSync as statSync15 } from "fs";
12204
+ import { existsSync as existsSync17, readdirSync as readdirSync13, readFileSync as readFileSync12, realpathSync as realpathSync2, statSync as statSync15 } from "fs";
12415
12205
  import { homedir as homedir5, platform as osPlatform } from "os";
12416
- import { join as join18 } from "path";
12206
+ import { join as join19 } from "path";
12417
12207
  function candidateRoots(plat = osPlatform(), env = process.env) {
12418
12208
  const home = homedir5();
12419
12209
  const roots = [home];
@@ -12426,23 +12216,23 @@ function candidateRoots(plat = osPlatform(), env = process.env) {
12426
12216
  add(env.LOCALAPPDATA);
12427
12217
  add(env.USERPROFILE);
12428
12218
  } else if (plat === "darwin") {
12429
- add(join18(home, "Library", "Application Support"));
12219
+ add(join19(home, "Library", "Application Support"));
12430
12220
  add(env.XDG_CONFIG_HOME);
12431
12221
  } else {
12432
- add(env.XDG_CONFIG_HOME || join18(home, ".config"));
12433
- add(env.XDG_DATA_HOME || join18(home, ".local", "share"));
12222
+ add(env.XDG_CONFIG_HOME || join19(home, ".config"));
12223
+ add(env.XDG_DATA_HOME || join19(home, ".local", "share"));
12434
12224
  }
12435
12225
  return roots.filter((r) => existsSync17(r));
12436
12226
  }
12437
12227
  function identifyHome(dir) {
12438
- const has = (...parts) => existsSync17(join18(dir, ...parts));
12439
- if (has("projects") && dirHasTranscript(join18(dir, "projects"), (f) => f.endsWith(".jsonl"), 2))
12228
+ const has = (...parts) => existsSync17(join19(dir, ...parts));
12229
+ if (has("projects") && dirHasTranscript(join19(dir, "projects"), (f) => f.endsWith(".jsonl"), 2))
12440
12230
  return "claude";
12441
- if (has("sessions") && dirHasTranscript(join18(dir, "sessions"), (f) => f.startsWith("rollout-") && f.endsWith(".jsonl"), 4))
12231
+ if (has("sessions") && dirHasTranscript(join19(dir, "sessions"), (f) => f.startsWith("rollout-") && f.endsWith(".jsonl"), 4))
12442
12232
  return "codex";
12443
- if (has("brain") && dirHasTranscript(join18(dir, "brain"), (f) => f.endsWith(".jsonl"), 4))
12233
+ if (has("brain") && dirHasTranscript(join19(dir, "brain"), (f) => f.endsWith(".jsonl"), 4))
12444
12234
  return "agy";
12445
- if (has("chats") && dirHasTranscript(join18(dir, "chats"), (f) => f === "store.db", 3))
12235
+ if (has("chats") && dirHasTranscript(join19(dir, "chats"), (f) => f === "store.db", 3))
12446
12236
  return "cursor";
12447
12237
  if (has("opencode.db"))
12448
12238
  return "opencode";
@@ -12462,7 +12252,7 @@ function dirHasTranscript(root, match, depth) {
12462
12252
  continue;
12463
12253
  if (e.isFile() && match(e.name))
12464
12254
  return true;
12465
- if (e.isDirectory() && dirHasTranscript(join18(root, e.name), match, depth - 1))
12255
+ if (e.isDirectory() && dirHasTranscript(join19(root, e.name), match, depth - 1))
12466
12256
  return true;
12467
12257
  }
12468
12258
  return false;
@@ -12492,12 +12282,12 @@ function countSessions(dir, tool) {
12492
12282
  if (SKIP.has(e.name))
12493
12283
  continue;
12494
12284
  if (e.isDirectory())
12495
- walk(join18(d, e.name), depth - 1);
12285
+ walk(join19(d, e.name), depth - 1);
12496
12286
  else if (s.match(e.name))
12497
12287
  n++;
12498
12288
  }
12499
12289
  };
12500
- walk(join18(dir, s.sub), s.depth);
12290
+ walk(join19(dir, s.sub), s.depth);
12501
12291
  return n;
12502
12292
  }
12503
12293
  function declaredHomes(env = process.env, plat = osPlatform()) {
@@ -12531,11 +12321,11 @@ function declaredHomes(env = process.env, plat = osPlatform()) {
12531
12321
  return out;
12532
12322
  }
12533
12323
  const home = homedir5();
12534
- const rcs = [".bashrc", ".bash_profile", ".zshrc", ".zprofile", ".profile"].map((f) => join18(home, f)).concat([join18(home, ".config", "fish", "config.fish")]);
12324
+ const rcs = [".bashrc", ".bash_profile", ".zshrc", ".zprofile", ".profile"].map((f) => join19(home, f)).concat([join19(home, ".config", "fish", "config.fish")]);
12535
12325
  for (const rc of rcs) {
12536
12326
  let text;
12537
12327
  try {
12538
- text = readFileSync11(rc, "utf-8");
12328
+ text = readFileSync12(rc, "utf-8");
12539
12329
  } catch {
12540
12330
  continue;
12541
12331
  }
@@ -12546,7 +12336,7 @@ function declaredHomes(env = process.env, plat = osPlatform()) {
12546
12336
  add(d);
12547
12337
  }
12548
12338
  }
12549
- return out.map((p) => p.startsWith("~") ? join18(home, p.slice(1)) : p);
12339
+ return out.map((p) => p.startsWith("~") ? join19(home, p.slice(1)) : p);
12550
12340
  }
12551
12341
  function runningToolHomes(plat = osPlatform()) {
12552
12342
  const out = [];
@@ -12560,7 +12350,7 @@ function runningToolHomes(plat = osPlatform()) {
12560
12350
  for (const pid of pids) {
12561
12351
  let env;
12562
12352
  try {
12563
- env = readFileSync11(`/proc/${pid}/environ`, "utf-8");
12353
+ env = readFileSync12(`/proc/${pid}/environ`, "utf-8");
12564
12354
  } catch {
12565
12355
  continue;
12566
12356
  }
@@ -12640,7 +12430,7 @@ function walkForHomes(dir, depth, visit) {
12640
12430
  for (const e of entries) {
12641
12431
  if (!e.isDirectory() || SKIP.has(e.name))
12642
12432
  continue;
12643
- const child = join18(dir, e.name);
12433
+ const child = join19(dir, e.name);
12644
12434
  visit(child);
12645
12435
  walkForHomes(child, depth - 1, visit);
12646
12436
  }
@@ -12681,20 +12471,20 @@ var init_home_discovery = __esm({
12681
12471
 
12682
12472
  // ../engine/dist/core/detector-install.js
12683
12473
  import { createHash as createHash4 } from "crypto";
12684
- import { existsSync as existsSync18, mkdirSync as mkdirSync4, mkdtempSync, chmodSync as chmodSync2, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync12, statSync as statSync16, copyFileSync as copyFileSync2 } from "fs";
12685
- import { join as join19 } from "path";
12474
+ import { existsSync as existsSync18, mkdirSync as mkdirSync5, mkdtempSync, chmodSync as chmodSync2, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync4, readFileSync as readFileSync13, statSync as statSync16, copyFileSync as copyFileSync2 } from "fs";
12475
+ import { join as join20 } from "path";
12686
12476
  function managedDetectorPath(name) {
12687
- return join19(getDetectorBinDir(), name + exeSuffix());
12477
+ return join20(getDetectorBinDir(), name + exeSuffix());
12688
12478
  }
12689
12479
  function receiptPath(name) {
12690
- return join19(getDetectorBinDir(), `${name}.receipt.json`);
12480
+ return join20(getDetectorBinDir(), `${name}.receipt.json`);
12691
12481
  }
12692
12482
  function sha256File(path) {
12693
- return createHash4("sha256").update(readFileSync12(path)).digest("hex");
12483
+ return createHash4("sha256").update(readFileSync13(path)).digest("hex");
12694
12484
  }
12695
12485
  function readReceipt(name) {
12696
12486
  try {
12697
- return JSON.parse(readFileSync12(receiptPath(name), "utf-8"));
12487
+ return JSON.parse(readFileSync13(receiptPath(name), "utf-8"));
12698
12488
  } catch {
12699
12489
  return null;
12700
12490
  }
@@ -12720,7 +12510,7 @@ function configuredDir() {
12720
12510
  function resolveDetector(name) {
12721
12511
  const dir = configuredDir();
12722
12512
  if (dir) {
12723
- const p2 = join19(dir, name + exeSuffix());
12513
+ const p2 = join20(dir, name + exeSuffix());
12724
12514
  if (existsSync18(p2))
12725
12515
  return { path: p2, version: "operator-supplied", source: "configured" };
12726
12516
  return { error: "not-installed" };
@@ -12785,9 +12575,9 @@ var init_detector_install = __esm({
12785
12575
 
12786
12576
  // ../engine/dist/core/secret-scanner.js
12787
12577
  import { spawnSync } from "child_process";
12788
- import { existsSync as existsSync19, unlinkSync, readFileSync as readFileSync13, mkdtempSync as mkdtempSync2, rmSync as rmSync3 } from "fs";
12578
+ import { existsSync as existsSync19, unlinkSync, readFileSync as readFileSync14, mkdtempSync as mkdtempSync2, rmSync as rmSync3 } from "fs";
12789
12579
  import { tmpdir } from "os";
12790
- import { join as join20 } from "path";
12580
+ import { join as join21 } from "path";
12791
12581
  function mask(s) {
12792
12582
  if (!s)
12793
12583
  return "";
@@ -12815,7 +12605,7 @@ function whichDetector(bin) {
12815
12605
  return null;
12816
12606
  }
12817
12607
  function spawnScoped(binary, args, opts) {
12818
- const scratch = mkdtempSync2(join20(tmpdir(), "cr-scan-tmp-"));
12608
+ const scratch = mkdtempSync2(join21(tmpdir(), "cr-scan-tmp-"));
12819
12609
  try {
12820
12610
  return spawnSync(binary, args, {
12821
12611
  ...opts,
@@ -12846,7 +12636,7 @@ function runGitleaks(binary, filePath) {
12846
12636
  return [];
12847
12637
  let parsed = [];
12848
12638
  try {
12849
- parsed = JSON.parse(readFileSync13(reportPath, "utf-8")) || [];
12639
+ parsed = JSON.parse(readFileSync14(reportPath, "utf-8")) || [];
12850
12640
  } catch {
12851
12641
  }
12852
12642
  try {
@@ -12910,7 +12700,7 @@ function runGitleaksDir(binary, dir) {
12910
12700
  }
12911
12701
  let parsed = [];
12912
12702
  try {
12913
- parsed = JSON.parse(readFileSync13(reportPath, "utf-8")) || [];
12703
+ parsed = JSON.parse(readFileSync14(reportPath, "utf-8")) || [];
12914
12704
  } catch {
12915
12705
  }
12916
12706
  try {
@@ -13001,7 +12791,7 @@ function scanFileForSecrets(filePath, opts = {}) {
13001
12791
  out.push({ detector: "trufflehog", ...f });
13002
12792
  if (opts.tenantRules && opts.tenantRules.length > 0) {
13003
12793
  try {
13004
- const text = readFileSync13(filePath, "utf-8");
12794
+ const text = readFileSync14(filePath, "utf-8");
13005
12795
  out.push(...scanTenantRules(text, opts.tenantRules));
13006
12796
  } catch {
13007
12797
  }
@@ -13377,10 +13167,10 @@ var init_transcript = __esm({
13377
13167
  // ../engine/dist/core/project-resolver.js
13378
13168
  import { execSync } from "child_process";
13379
13169
  import { createHash as createHash5 } from "crypto";
13380
- import { existsSync as existsSync20, readFileSync as readFileSync14, realpathSync as realpathSync3, readdirSync as readdirSync14, statSync as statSync18 } from "fs";
13381
- import { basename as basename11, dirname as dirname13, isAbsolute, join as join21, resolve, sep } from "path";
13170
+ import { existsSync as existsSync20, readFileSync as readFileSync15, realpathSync as realpathSync3, readdirSync as readdirSync14, statSync as statSync18 } from "fs";
13171
+ import { basename as basename11, dirname as dirname14, isAbsolute, join as join22, resolve, sep } from "path";
13382
13172
  function getProjectsConfigPath() {
13383
- return join21(getDataDir(), "projects.json");
13173
+ return join22(getDataDir(), "projects.json");
13384
13174
  }
13385
13175
  function loadProjectsConfig() {
13386
13176
  if (cachedConfig && cachedConfig.mtime === Number.MAX_SAFE_INTEGER) {
@@ -13395,7 +13185,7 @@ function loadProjectsConfig() {
13395
13185
  if (cachedConfig && cachedConfig.mtime === mtime)
13396
13186
  return cachedConfig.cfg;
13397
13187
  try {
13398
- const raw = readFileSync14(path, "utf-8");
13188
+ const raw = readFileSync15(path, "utf-8");
13399
13189
  const cfg = JSON.parse(raw);
13400
13190
  cachedConfig = { mtime, cfg };
13401
13191
  return cfg;
@@ -13491,7 +13281,7 @@ function matchDeclaredProject(realPath, projects) {
13491
13281
  function matchChild(realPath, parentRoot, children) {
13492
13282
  const sorted = [...children].sort((a, b) => b.path.length - a.path.length);
13493
13283
  for (const c of sorted) {
13494
- const childRoot = safeRealpath(join21(parentRoot, c.path));
13284
+ const childRoot = safeRealpath(join22(parentRoot, c.path));
13495
13285
  if (isInside(realPath, childRoot))
13496
13286
  return c;
13497
13287
  }
@@ -13526,10 +13316,10 @@ function findGitToplevel(realPath) {
13526
13316
  let cur = realPath;
13527
13317
  let last = "";
13528
13318
  while (cur && cur !== last) {
13529
- if (existsSync20(join21(cur, ".git")))
13319
+ if (existsSync20(join22(cur, ".git")))
13530
13320
  return cur;
13531
13321
  last = cur;
13532
- cur = dirname13(cur);
13322
+ cur = dirname14(cur);
13533
13323
  }
13534
13324
  return null;
13535
13325
  }
@@ -13593,7 +13383,7 @@ function resolveAutoWorkspace(realPath, minRepos) {
13593
13383
  let last = "";
13594
13384
  let depth = 0;
13595
13385
  while (cur && cur !== last && depth < AUTO_WORKSPACE_MAX_DEPTH) {
13596
- if (!existsSync20(join21(cur, ".git"))) {
13386
+ if (!existsSync20(join22(cur, ".git"))) {
13597
13387
  const cached = workspaceCache.get(cur);
13598
13388
  let qualifies;
13599
13389
  if (cached === void 0) {
@@ -13608,7 +13398,7 @@ function resolveAutoWorkspace(realPath, minRepos) {
13608
13398
  }
13609
13399
  }
13610
13400
  last = cur;
13611
- cur = dirname13(cur);
13401
+ cur = dirname14(cur);
13612
13402
  depth++;
13613
13403
  }
13614
13404
  return null;
@@ -13624,12 +13414,12 @@ function countGitChildren(dir) {
13624
13414
  for (const name of entries) {
13625
13415
  if (name.startsWith("."))
13626
13416
  continue;
13627
- const child = join21(dir, name);
13417
+ const child = join22(dir, name);
13628
13418
  try {
13629
13419
  const st = statSync18(child);
13630
13420
  if (!st.isDirectory())
13631
13421
  continue;
13632
- if (existsSync20(join21(child, ".git")))
13422
+ if (existsSync20(join22(child, ".git")))
13633
13423
  count++;
13634
13424
  } catch {
13635
13425
  }
@@ -14335,115 +14125,10 @@ var init_source_registry = __esm({
14335
14125
  }
14336
14126
  });
14337
14127
 
14338
- // ../engine/dist/parsers/session-source.js
14339
- var SessionSource;
14340
- var init_session_source = __esm({
14341
- "../engine/dist/parsers/session-source.js"() {
14342
- "use strict";
14343
- init_session();
14344
- init_chunker();
14345
- init_settings();
14346
- SessionSource = class {
14347
- sourceType = "session";
14348
- claudeDir;
14349
- constructor(claudeDir) {
14350
- this.claudeDir = claudeDir;
14351
- }
14352
- async *discover() {
14353
- if (!isSourceEnabled("claude", "sessions"))
14354
- return;
14355
- for (const [entry, sessionPath] of getAllSessions(this.claudeDir)) {
14356
- yield this.entryToItem(entry, sessionPath);
14357
- }
14358
- }
14359
- async parse(item) {
14360
- const content = await parseSessionFile(item.filePath);
14361
- const entry = this.itemToEntry(item);
14362
- const sessionChunks = chunkSession(entry, content);
14363
- item.extra = {
14364
- ...item.extra || {},
14365
- ...content.metadata
14366
- };
14367
- return sessionChunks.map((sc) => ({
14368
- chunkId: sc.chunkId,
14369
- itemId: sc.sessionId,
14370
- sourceType: "session",
14371
- // Empty title is preferable to "Session abc12345" placeholder text:
14372
- // the latter pollutes search results and gets surfaced in the UI as
14373
- // a misleading heading. Downstream renderers fall back to
14374
- // project path / time when title is empty.
14375
- title: sc.firstPrompt.slice(0, 100),
14376
- text: sc.text,
14377
- chunkType: sc.chunkType,
14378
- projectPath: sc.projectPath,
14379
- filePath: sc.sourceFile,
14380
- mtime: sc.mtime
14381
- }));
14382
- }
14383
- async extractLinks(item) {
14384
- if (item.filePath) {
14385
- try {
14386
- const { detectForkPredecessor: detectForkPredecessor2 } = await Promise.resolve().then(() => (init_raw(), raw_exports));
14387
- const predecessor = detectForkPredecessor2(item.filePath);
14388
- if (predecessor) {
14389
- return [{
14390
- sourceType: "session",
14391
- sourceId: item.id,
14392
- targetType: "session",
14393
- targetId: predecessor,
14394
- linkType: "forked_from",
14395
- confidence: 1
14396
- }];
14397
- }
14398
- } catch {
14399
- }
14400
- }
14401
- return [];
14402
- }
14403
- entryToItem(entry, sessionPath) {
14404
- return {
14405
- id: entry.sessionId,
14406
- sourceType: "session",
14407
- // Empty title when there's no first prompt — the UI then knows to
14408
- // fall back to summary or project path instead of rendering the
14409
- // useless "Session abc12345" placeholder as a heading.
14410
- title: entry.firstPrompt ? entry.firstPrompt.replace(/\n/g, " ").trim().slice(0, 100) : "",
14411
- projectPath: entry.projectPath,
14412
- filePath: sessionPath,
14413
- mtime: entry.fileMtime,
14414
- contentPreview: entry.firstPrompt?.slice(0, 200),
14415
- extra: {
14416
- messageCount: entry.messageCount,
14417
- gitBranch: entry.gitBranch,
14418
- created: entry.created,
14419
- modified: entry.modified,
14420
- isSidechain: entry.isSidechain
14421
- }
14422
- };
14423
- }
14424
- itemToEntry(item) {
14425
- const extra = item.extra || {};
14426
- return {
14427
- sessionId: item.id,
14428
- fullPath: item.filePath,
14429
- fileMtime: item.mtime,
14430
- firstPrompt: item.contentPreview || "",
14431
- messageCount: extra.messageCount || 0,
14432
- created: extra.created || "",
14433
- modified: extra.modified || "",
14434
- gitBranch: extra.gitBranch || "",
14435
- projectPath: item.projectPath,
14436
- isSidechain: extra.isSidechain || false
14437
- };
14438
- }
14439
- };
14440
- }
14441
- });
14442
-
14443
14128
  // ../engine/dist/core/utils.js
14444
14129
  import { existsSync as existsSync21, readdirSync as readdirSync15 } from "fs";
14445
14130
  import { homedir as homedir6 } from "os";
14446
- import { join as join22 } from "path";
14131
+ import { join as join23 } from "path";
14447
14132
  function splitByHeaders(markdown) {
14448
14133
  const lines = markdown.split("\n");
14449
14134
  const sections = [];
@@ -14486,17 +14171,17 @@ function getClaudeDirs() {
14486
14171
  for (const d of process.env.CLAUDE_DIRS.split(",")) {
14487
14172
  const trimmed = d.trim();
14488
14173
  if (trimmed) {
14489
- add(trimmed.startsWith("~/") ? join22(home, trimmed.slice(2)) : trimmed);
14174
+ add(trimmed.startsWith("~/") ? join23(home, trimmed.slice(2)) : trimmed);
14490
14175
  }
14491
14176
  }
14492
14177
  }
14493
- add(join22(home, ".claude"));
14178
+ add(join23(home, ".claude"));
14494
14179
  try {
14495
14180
  const entries = readdirSync15(home, { withFileTypes: true });
14496
14181
  for (const entry of entries) {
14497
14182
  if (entry.isDirectory() && entry.name.startsWith(".claude-") && entry.name !== ".claude-code") {
14498
- const candidate = join22(home, entry.name);
14499
- if (existsSync21(join22(candidate, "projects"))) {
14183
+ const candidate = join23(home, entry.name);
14184
+ if (existsSync21(join23(candidate, "projects"))) {
14500
14185
  add(candidate);
14501
14186
  }
14502
14187
  }
@@ -14508,12 +14193,12 @@ function getClaudeDirs() {
14508
14193
  function discoverSubdirs(subdir) {
14509
14194
  const result = [];
14510
14195
  for (const claudeDir of getClaudeDirs()) {
14511
- const path = join22(claudeDir, subdir);
14196
+ const path = join23(claudeDir, subdir);
14512
14197
  if (existsSync21(path)) {
14513
14198
  result.push(path);
14514
14199
  }
14515
14200
  }
14516
- return result.length > 0 ? result : [join22(homedir6(), ".claude", subdir)];
14201
+ return result.length > 0 ? result : [join23(homedir6(), ".claude", subdir)];
14517
14202
  }
14518
14203
  var init_utils = __esm({
14519
14204
  "../engine/dist/core/utils.js"() {
@@ -14522,9 +14207,9 @@ var init_utils = __esm({
14522
14207
  });
14523
14208
 
14524
14209
  // ../engine/dist/parsers/plan-source.js
14525
- import { existsSync as existsSync22, readdirSync as readdirSync16, readFileSync as readFileSync15, statSync as statSync20 } from "fs";
14210
+ import { existsSync as existsSync22, readdirSync as readdirSync16, readFileSync as readFileSync16, statSync as statSync20 } from "fs";
14526
14211
  import { homedir as homedir7 } from "os";
14527
- import { join as join23, basename as basename12 } from "path";
14212
+ import { join as join24, basename as basename12 } from "path";
14528
14213
  function parseFrontmatter(content) {
14529
14214
  if (!content.startsWith("---"))
14530
14215
  return { body: content };
@@ -14619,12 +14304,12 @@ var init_plan_source = __esm({
14619
14304
  if (seenClaude.has(file))
14620
14305
  continue;
14621
14306
  seenClaude.add(file);
14622
- const filePath = join23(plansDir, file);
14307
+ const filePath = join24(plansDir, file);
14623
14308
  try {
14624
14309
  const stat3 = statSync20(filePath);
14625
14310
  const planName = basename12(file, ".md");
14626
14311
  const isAgentPlan = planName.includes("-agent-");
14627
- const content = readFileSync15(filePath, "utf-8");
14312
+ const content = readFileSync16(filePath, "utf-8");
14628
14313
  const fm = parseFrontmatter(content);
14629
14314
  const body = fm.body;
14630
14315
  const firstLine = body.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || planName;
@@ -14663,10 +14348,10 @@ var init_plan_source = __esm({
14663
14348
  return;
14664
14349
  }
14665
14350
  for (const file of files) {
14666
- const filePath = join23(plansDir, file);
14351
+ const filePath = join24(plansDir, file);
14667
14352
  try {
14668
14353
  const stat3 = statSync20(filePath);
14669
- const content = readFileSync15(filePath, "utf-8");
14354
+ const content = readFileSync16(filePath, "utf-8");
14670
14355
  const firstLine = content.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || file;
14671
14356
  const planName = `${opencodeBackend.idPrefix}plan_${basename12(file, ".md")}`;
14672
14357
  const projectPath = this.extractProjectPath(content);
@@ -14694,7 +14379,7 @@ var init_plan_source = __esm({
14694
14379
  for (const match of content.matchAll(fullPathPattern)) {
14695
14380
  let path = (match[1] || "").trim();
14696
14381
  if (path.startsWith("~/")) {
14697
- path = join23(homedir7(), path.slice(2));
14382
+ path = join24(homedir7(), path.slice(2));
14698
14383
  }
14699
14384
  for (const projectDir of knownProjects) {
14700
14385
  if (path.startsWith(projectDir)) {
@@ -14743,7 +14428,7 @@ var init_plan_source = __esm({
14743
14428
  async parse(item) {
14744
14429
  if (!existsSync22(item.filePath))
14745
14430
  return [];
14746
- const content = readFileSync15(item.filePath, "utf-8");
14431
+ const content = readFileSync16(item.filePath, "utf-8");
14747
14432
  const chunks = [];
14748
14433
  const { body } = parseFrontmatter(content);
14749
14434
  const sections = splitByHeaders(body);
@@ -14779,9 +14464,9 @@ var init_plan_source = __esm({
14779
14464
  const map = /* @__PURE__ */ new Map();
14780
14465
  const root = claudeBackend.projectsDir();
14781
14466
  for (const proj of safeReaddir(root)) {
14782
- const projPath = join23(root, proj);
14467
+ const projPath = join24(root, proj);
14783
14468
  for (const sessionEntry of safeReaddir(projPath)) {
14784
- const subDir = join23(projPath, sessionEntry, "subagents");
14469
+ const subDir = join24(projPath, sessionEntry, "subagents");
14785
14470
  if (!existsSync22(subDir))
14786
14471
  continue;
14787
14472
  for (const f of safeReaddir(subDir)) {
@@ -14856,7 +14541,7 @@ var init_plan_source = __esm({
14856
14541
  return links;
14857
14542
  }
14858
14543
  async *discoverAgyPlans() {
14859
- const brainDir = join23(agyBackend.homeDir(), "brain");
14544
+ const brainDir = join24(agyBackend.homeDir(), "brain");
14860
14545
  if (!existsSync22(brainDir))
14861
14546
  return;
14862
14547
  let sessionDirs;
@@ -14866,7 +14551,7 @@ var init_plan_source = __esm({
14866
14551
  return;
14867
14552
  }
14868
14553
  for (const rawId of sessionDirs) {
14869
- const sessionPath = join23(brainDir, rawId);
14554
+ const sessionPath = join24(brainDir, rawId);
14870
14555
  try {
14871
14556
  if (!statSync20(sessionPath).isDirectory())
14872
14557
  continue;
@@ -14880,10 +14565,10 @@ var init_plan_source = __esm({
14880
14565
  continue;
14881
14566
  }
14882
14567
  for (const file of files) {
14883
- const filePath = join23(sessionPath, file);
14568
+ const filePath = join24(sessionPath, file);
14884
14569
  try {
14885
14570
  const stat3 = statSync20(filePath);
14886
- const content = readFileSync15(filePath, "utf-8");
14571
+ const content = readFileSync16(filePath, "utf-8");
14887
14572
  const firstLine = content.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "").trim() || file;
14888
14573
  const sessionLoc = agyBackend.findSession(rawId);
14889
14574
  const projectPath = sessionLoc?.projectPath || this.extractProjectPath(content);
@@ -14912,8 +14597,8 @@ var init_plan_source = __esm({
14912
14597
  });
14913
14598
 
14914
14599
  // ../engine/dist/parsers/task-source.js
14915
- import { existsSync as existsSync23, readdirSync as readdirSync17, readFileSync as readFileSync16, statSync as statSync21 } from "fs";
14916
- import { join as join24 } from "path";
14600
+ import { existsSync as existsSync23, readdirSync as readdirSync17, readFileSync as readFileSync17, statSync as statSync21 } from "fs";
14601
+ import { join as join25 } from "path";
14917
14602
  var MAX_CHUNK_CHARS2, TaskSource;
14918
14603
  var init_task_source = __esm({
14919
14604
  "../engine/dist/parsers/task-source.js"() {
@@ -14951,7 +14636,7 @@ var init_task_source = __esm({
14951
14636
  return;
14952
14637
  }
14953
14638
  for (const file of files) {
14954
- const filePath = join24(todosDir, file);
14639
+ const filePath = join25(todosDir, file);
14955
14640
  let stat3;
14956
14641
  try {
14957
14642
  stat3 = statSync21(filePath);
@@ -14962,7 +14647,7 @@ var init_task_source = __esm({
14962
14647
  continue;
14963
14648
  let parsed;
14964
14649
  try {
14965
- const raw = readFileSync16(filePath, "utf-8");
14650
+ const raw = readFileSync17(filePath, "utf-8");
14966
14651
  parsed = JSON.parse(raw);
14967
14652
  if (!Array.isArray(parsed) || parsed.length === 0)
14968
14653
  continue;
@@ -15000,7 +14685,7 @@ var init_task_source = __esm({
15000
14685
  if (!entry.isDirectory())
15001
14686
  continue;
15002
14687
  const sessionUuid = entry.name;
15003
- const taskDir = join24(tasksDir, sessionUuid);
14688
+ const taskDir = join25(tasksDir, sessionUuid);
15004
14689
  try {
15005
14690
  const stat3 = statSync21(taskDir);
15006
14691
  const taskFiles = readdirSync17(taskDir).filter((f) => f.endsWith(".json"));
@@ -15075,7 +14760,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
15075
14760
  text,
15076
14761
  chunkType: "task_detail",
15077
14762
  projectPath: item.projectPath,
15078
- filePath: join24(taskDir, `${task.id}.json`),
14763
+ filePath: join25(taskDir, `${task.id}.json`),
15079
14764
  mtime: item.mtime
15080
14765
  });
15081
14766
  }
@@ -15100,7 +14785,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
15100
14785
  return [];
15101
14786
  let parsed;
15102
14787
  try {
15103
- parsed = JSON.parse(readFileSync16(item.filePath, "utf-8"));
14788
+ parsed = JSON.parse(readFileSync17(item.filePath, "utf-8"));
15104
14789
  } catch {
15105
14790
  return [];
15106
14791
  }
@@ -15132,7 +14817,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
15132
14817
  });
15133
14818
  for (const file of sorted) {
15134
14819
  try {
15135
- const content = readFileSync16(join24(taskDir, file), "utf-8");
14820
+ const content = readFileSync17(join25(taskDir, file), "utf-8");
15136
14821
  const task = JSON.parse(content);
15137
14822
  if (task.subject) {
15138
14823
  tasks.push(task);
@@ -15148,9 +14833,9 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
15148
14833
  });
15149
14834
 
15150
14835
  // ../engine/dist/parsers/claude-md-source.js
15151
- import { existsSync as existsSync24, readFileSync as readFileSync17, readdirSync as readdirSync18, statSync as statSync22 } from "fs";
14836
+ import { existsSync as existsSync24, readFileSync as readFileSync18, readdirSync as readdirSync18, statSync as statSync22 } from "fs";
15152
14837
  import { homedir as homedir8 } from "os";
15153
- import { join as join25, basename as basename13, dirname as dirname14 } from "path";
14838
+ import { join as join26, basename as basename13, dirname as dirname15 } from "path";
15154
14839
  import { createHash as createHash6 } from "crypto";
15155
14840
  function discoverProjectDirs(claudeDir) {
15156
14841
  const dirs = [];
@@ -15176,7 +14861,7 @@ function discoverProjectDirs(claudeDir) {
15176
14861
  }
15177
14862
  };
15178
14863
  if (claudeDir) {
15179
- scanProjectsDir(join25(claudeDir, "projects"));
14864
+ scanProjectsDir(join26(claudeDir, "projects"));
15180
14865
  return dirs;
15181
14866
  }
15182
14867
  const home = homedir8();
@@ -15184,7 +14869,7 @@ function discoverProjectDirs(claudeDir) {
15184
14869
  try {
15185
14870
  for (const entry of readdirSync18(home, { withFileTypes: true })) {
15186
14871
  if (entry.isDirectory() && entry.name.startsWith(".claude-") && entry.name !== ".claude-code") {
15187
- scanProjectsDir(join25(home, entry.name, "projects"));
14872
+ scanProjectsDir(join26(home, entry.name, "projects"));
15188
14873
  }
15189
14874
  }
15190
14875
  } catch {
@@ -15192,7 +14877,7 @@ function discoverProjectDirs(claudeDir) {
15192
14877
  const gemProjects = agyBackend.sharedProjectsJson();
15193
14878
  if (existsSync24(gemProjects)) {
15194
14879
  try {
15195
- const data = JSON.parse(readFileSync17(gemProjects, "utf-8"));
14880
+ const data = JSON.parse(readFileSync18(gemProjects, "utf-8"));
15196
14881
  for (const path of Object.keys(data.projects || {}))
15197
14882
  addProject(path);
15198
14883
  } catch {
@@ -15248,7 +14933,7 @@ var init_claude_md_source = __esm({
15248
14933
  const seen = /* @__PURE__ */ new Set();
15249
14934
  const seenKey = (path, tool) => `${tool}:${path}`;
15250
14935
  for (const note of NOTE_FILES) {
15251
- const p = join25(homedir8(), note.filename);
14936
+ const p = join26(homedir8(), note.filename);
15252
14937
  const key2 = seenKey(p, note.tool);
15253
14938
  if (existsSync24(p) && !seen.has(key2)) {
15254
14939
  seen.add(key2);
@@ -15260,7 +14945,7 @@ var init_claude_md_source = __esm({
15260
14945
  const projectDirs = discoverProjectDirs(this.claudeDir);
15261
14946
  for (const projectDir of projectDirs) {
15262
14947
  for (const note of NOTE_FILES) {
15263
- const p = join25(projectDir, note.filename);
14948
+ const p = join26(projectDir, note.filename);
15264
14949
  const key2 = seenKey(p, note.tool);
15265
14950
  if (existsSync24(p) && !seen.has(key2)) {
15266
14951
  seen.add(key2);
@@ -15274,7 +14959,7 @@ var init_claude_md_source = __esm({
15274
14959
  if (!existsSync24(searchDir))
15275
14960
  continue;
15276
14961
  for (const note of NOTE_FILES) {
15277
- const directPath = join25(searchDir, note.filename);
14962
+ const directPath = join26(searchDir, note.filename);
15278
14963
  const key2 = seenKey(directPath, note.tool);
15279
14964
  if (existsSync24(directPath) && !seen.has(key2)) {
15280
14965
  seen.add(key2);
@@ -15289,7 +14974,7 @@ var init_claude_md_source = __esm({
15289
14974
  if (!entry.isDirectory())
15290
14975
  continue;
15291
14976
  for (const note of NOTE_FILES) {
15292
- const p = join25(searchDir, entry.name, note.filename);
14977
+ const p = join26(searchDir, entry.name, note.filename);
15293
14978
  const key2 = seenKey(p, note.tool);
15294
14979
  if (existsSync24(p) && !seen.has(key2)) {
15295
14980
  seen.add(key2);
@@ -15307,7 +14992,7 @@ var init_claude_md_source = __esm({
15307
14992
  async parse(item) {
15308
14993
  if (!existsSync24(item.filePath))
15309
14994
  return [];
15310
- const content = readFileSync17(item.filePath, "utf-8");
14995
+ const content = readFileSync18(item.filePath, "utf-8");
15311
14996
  const chunks = [];
15312
14997
  const sections = splitByHeaders(content);
15313
14998
  for (let i = 0; i < sections.length; i++) {
@@ -15346,10 +15031,10 @@ var init_claude_md_source = __esm({
15346
15031
  fileToItem(filePath, filename, tool) {
15347
15032
  try {
15348
15033
  const stat3 = statSync22(filePath);
15349
- const projectDir = dirname14(filePath);
15034
+ const projectDir = dirname15(filePath);
15350
15035
  const projectName = basename13(projectDir);
15351
15036
  const projectPath = projectDir;
15352
- const content = readFileSync17(filePath, "utf-8");
15037
+ const content = readFileSync18(filePath, "utf-8");
15353
15038
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"))?.trim()?.slice(0, 200) || `${filename} for ${projectName}`;
15354
15039
  const id = tool === "claude" ? projectName : `${projectName}_${tool}`;
15355
15040
  return {
@@ -15374,9 +15059,9 @@ var init_claude_md_source = __esm({
15374
15059
  });
15375
15060
 
15376
15061
  // ../engine/dist/parsers/agent-memory-source.js
15377
- import { existsSync as existsSync25, readFileSync as readFileSync18, readdirSync as readdirSync19, statSync as statSync23 } from "fs";
15062
+ import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync19, statSync as statSync23 } from "fs";
15378
15063
  import { homedir as homedir9 } from "os";
15379
- import { join as join26, basename as basename14 } from "path";
15064
+ import { join as join27, basename as basename14 } from "path";
15380
15065
  function parseFrontmatter2(content) {
15381
15066
  const fm = {};
15382
15067
  let body = content;
@@ -15418,7 +15103,7 @@ function discoverMemoryDirs() {
15418
15103
  try {
15419
15104
  for (const entry of readdirSync19(home, { withFileTypes: true })) {
15420
15105
  if (entry.isDirectory() && entry.name.startsWith(".claude-") && entry.name !== ".claude-code") {
15421
- claudeRoots.push(join26(home, entry.name, "projects"));
15106
+ claudeRoots.push(join27(home, entry.name, "projects"));
15422
15107
  }
15423
15108
  }
15424
15109
  } catch {
@@ -15430,7 +15115,7 @@ function discoverMemoryDirs() {
15430
15115
  for (const entry of readdirSync19(projectsDir, { withFileTypes: true })) {
15431
15116
  if (!entry.isDirectory() || entry.name.startsWith("."))
15432
15117
  continue;
15433
- add(join26(projectsDir, entry.name, "memory"), entry.name);
15118
+ add(join27(projectsDir, entry.name, "memory"), entry.name);
15434
15119
  }
15435
15120
  } catch {
15436
15121
  }
@@ -15459,7 +15144,7 @@ var init_agent_memory_source = __esm({
15459
15144
  for (const entry of readdirSync19(memoryDir, { withFileTypes: true })) {
15460
15145
  if (!entry.isFile() || !entry.name.endsWith(".md"))
15461
15146
  continue;
15462
- const filePath = join26(memoryDir, entry.name);
15147
+ const filePath = join27(memoryDir, entry.name);
15463
15148
  if (seen.has(filePath))
15464
15149
  continue;
15465
15150
  seen.add(filePath);
@@ -15475,7 +15160,7 @@ var init_agent_memory_source = __esm({
15475
15160
  async parse(item) {
15476
15161
  if (!existsSync25(item.filePath))
15477
15162
  return [];
15478
- const content = readFileSync18(item.filePath, "utf-8");
15163
+ const content = readFileSync19(item.filePath, "utf-8");
15479
15164
  const { body } = parseFrontmatter2(content);
15480
15165
  const chunks = [];
15481
15166
  const sections = splitByHeaders(body);
@@ -15528,7 +15213,7 @@ var init_agent_memory_source = __esm({
15528
15213
  fileToItem(filePath, filename, projectPath, _projectHash) {
15529
15214
  try {
15530
15215
  const stat3 = statSync23(filePath);
15531
- const content = readFileSync18(filePath, "utf-8");
15216
+ const content = readFileSync19(filePath, "utf-8");
15532
15217
  const { fm } = parseFrontmatter2(content);
15533
15218
  const stem = filename.replace(/\.md$/, "");
15534
15219
  const idBase = fm.name || stem;
@@ -15563,7 +15248,7 @@ import { existsSync as existsSync26, readdirSync as readdirSync20, statSync as s
15563
15248
  import { createReadStream as createReadStream3 } from "fs";
15564
15249
  import { createInterface as createInterface2 } from "readline";
15565
15250
  import { homedir as homedir10 } from "os";
15566
- import { join as join27 } from "path";
15251
+ import { join as join28 } from "path";
15567
15252
  var MAX_CHUNK_CHARS5, HistorySource;
15568
15253
  var init_history_source = __esm({
15569
15254
  "../engine/dist/parsers/history-source.js"() {
@@ -15588,12 +15273,12 @@ var init_history_source = __esm({
15588
15273
  try {
15589
15274
  for (const entry of readdirSync20(home, { withFileTypes: true })) {
15590
15275
  if (entry.isDirectory() && entry.name.startsWith(".claude-") && entry.name !== ".claude-code") {
15591
- tryAdd(join27(home, entry.name, "history.jsonl"));
15276
+ tryAdd(join28(home, entry.name, "history.jsonl"));
15592
15277
  }
15593
15278
  }
15594
15279
  } catch {
15595
15280
  }
15596
- this.historyPaths = paths.length > 0 ? paths : [join27(home, ".claude", "history.jsonl")];
15281
+ this.historyPaths = paths.length > 0 ? paths : [join28(home, ".claude", "history.jsonl")];
15597
15282
  }
15598
15283
  }
15599
15284
  async *discover() {
@@ -15743,9 +15428,9 @@ var init_history_source = __esm({
15743
15428
  });
15744
15429
 
15745
15430
  // ../engine/dist/parsers/paste-source.js
15746
- import { existsSync as existsSync27, readdirSync as readdirSync21, readFileSync as readFileSync19, statSync as statSync25 } from "fs";
15431
+ import { existsSync as existsSync27, readdirSync as readdirSync21, readFileSync as readFileSync20, statSync as statSync25 } from "fs";
15747
15432
  import { homedir as homedir11 } from "os";
15748
- import { join as join28, basename as basename15 } from "path";
15433
+ import { join as join29, basename as basename15 } from "path";
15749
15434
  var MAX_CHUNK_CHARS6, MAX_FILE_SIZE, PasteSource;
15750
15435
  var init_paste_source = __esm({
15751
15436
  "../engine/dist/parsers/paste-source.js"() {
@@ -15771,7 +15456,7 @@ var init_paste_source = __esm({
15771
15456
  try {
15772
15457
  for (const entry of readdirSync21(home, { withFileTypes: true })) {
15773
15458
  if (entry.isDirectory() && entry.name.startsWith(".claude-") && entry.name !== ".claude-code") {
15774
- tryAdd(join28(home, entry.name, "paste-cache"));
15459
+ tryAdd(join29(home, entry.name, "paste-cache"));
15775
15460
  }
15776
15461
  }
15777
15462
  } catch {
@@ -15791,7 +15476,7 @@ var init_paste_source = __esm({
15791
15476
  if (seen.has(file))
15792
15477
  continue;
15793
15478
  seen.add(file);
15794
- const filePath = join28(cacheDir, file);
15479
+ const filePath = join29(cacheDir, file);
15795
15480
  try {
15796
15481
  const stat3 = statSync25(filePath);
15797
15482
  if (stat3.size > MAX_FILE_SIZE)
@@ -15799,7 +15484,7 @@ var init_paste_source = __esm({
15799
15484
  if (stat3.size < 10)
15800
15485
  continue;
15801
15486
  const hash = basename15(file, ".txt");
15802
- const content = readFileSync19(filePath, "utf-8");
15487
+ const content = readFileSync20(filePath, "utf-8");
15803
15488
  const firstLine = content.split("\n").find((l) => l.trim().length > 5)?.trim()?.slice(0, 100) || `Paste ${hash.slice(0, 8)}`;
15804
15489
  yield {
15805
15490
  id: hash,
@@ -15820,7 +15505,7 @@ var init_paste_source = __esm({
15820
15505
  async parse(item) {
15821
15506
  if (!existsSync27(item.filePath))
15822
15507
  return [];
15823
- const content = readFileSync19(item.filePath, "utf-8");
15508
+ const content = readFileSync20(item.filePath, "utf-8");
15824
15509
  if (content.trim().length < 20)
15825
15510
  return [];
15826
15511
  return [{
@@ -15843,8 +15528,8 @@ var init_paste_source = __esm({
15843
15528
  });
15844
15529
 
15845
15530
  // ../engine/dist/parsers/antigravity-brain-source.js
15846
- import { existsSync as existsSync28, readdirSync as readdirSync22, readFileSync as readFileSync20, statSync as statSync26 } from "fs";
15847
- import { join as join29 } from "path";
15531
+ import { existsSync as existsSync28, readdirSync as readdirSync22, readFileSync as readFileSync21, statSync as statSync26 } from "fs";
15532
+ import { join as join30 } from "path";
15848
15533
  var MAX_CHUNK_CHARS7, AntigravityBrainSource;
15849
15534
  var init_antigravity_brain_source = __esm({
15850
15535
  "../engine/dist/parsers/antigravity-brain-source.js"() {
@@ -15868,22 +15553,22 @@ var init_antigravity_brain_source = __esm({
15868
15553
  for (const entry of sessions) {
15869
15554
  if (!entry.isDirectory())
15870
15555
  continue;
15871
- const sessionDir = join29(this.brainDir, entry.name);
15556
+ const sessionDir = join30(this.brainDir, entry.name);
15872
15557
  const files = readdirSync22(sessionDir).filter((f) => f.endsWith(".md") && !f.includes(".resolved") && !f.includes(".metadata"));
15873
15558
  if (files.length === 0)
15874
15559
  continue;
15875
15560
  for (const file of files) {
15876
- const filePath = join29(sessionDir, file);
15561
+ const filePath = join30(sessionDir, file);
15877
15562
  try {
15878
15563
  const stat3 = statSync26(filePath);
15879
- const content = readFileSync20(filePath, "utf-8");
15564
+ const content = readFileSync21(filePath, "utf-8");
15880
15565
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"))?.trim() || "";
15881
15566
  const heading = content.split("\n").find((l) => l.startsWith("#"))?.replace(/^#+\s*/, "").trim() || file;
15882
15567
  const metaPath = `${filePath}.metadata.json`;
15883
15568
  let metaSummary = "";
15884
15569
  if (existsSync28(metaPath)) {
15885
15570
  try {
15886
- const meta = JSON.parse(readFileSync20(metaPath, "utf-8"));
15571
+ const meta = JSON.parse(readFileSync21(metaPath, "utf-8"));
15887
15572
  metaSummary = meta.summary || "";
15888
15573
  } catch {
15889
15574
  }
@@ -15914,7 +15599,7 @@ var init_antigravity_brain_source = __esm({
15914
15599
  async parse(item) {
15915
15600
  if (!existsSync28(item.filePath))
15916
15601
  return [];
15917
- const content = readFileSync20(item.filePath, "utf-8");
15602
+ const content = readFileSync21(item.filePath, "utf-8");
15918
15603
  const chunks = [];
15919
15604
  const sections = splitByHeaders(content);
15920
15605
  for (let i = 0; i < sections.length; i++) {
@@ -16257,8 +15942,8 @@ var init_opencode_todo_source = __esm({
16257
15942
  });
16258
15943
 
16259
15944
  // ../engine/dist/parsers/codex-session-source.js
16260
- import { existsSync as existsSync31, readdirSync as readdirSync23, readFileSync as readFileSync21, statSync as statSync27 } from "fs";
16261
- import { join as join30, basename as basename16 } from "path";
15945
+ import { existsSync as existsSync31, readdirSync as readdirSync23, readFileSync as readFileSync22, statSync as statSync27 } from "fs";
15946
+ import { join as join31, basename as basename16 } from "path";
16262
15947
  function getCodexSessionsDir() {
16263
15948
  return codexBackend.sessionsDir();
16264
15949
  }
@@ -16285,7 +15970,7 @@ function extractReasoningText(event) {
16285
15970
  return "";
16286
15971
  }
16287
15972
  function readJsonlLines(filePath) {
16288
- const text = readFileSync21(filePath, "utf-8");
15973
+ const text = readFileSync22(filePath, "utf-8");
16289
15974
  const lines = text.split("\n");
16290
15975
  const events = [];
16291
15976
  for (const line of lines) {
@@ -16320,7 +16005,7 @@ var init_codex_session_source = __esm({
16320
16005
  for (const filePath of jsonlFiles) {
16321
16006
  try {
16322
16007
  const stat3 = statSync27(filePath);
16323
- const lines = readFileSync21(filePath, "utf-8").split("\n");
16008
+ const lines = readFileSync22(filePath, "utf-8").split("\n");
16324
16009
  const firstLine = lines.find((l) => l.trim());
16325
16010
  if (!firstLine)
16326
16011
  continue;
@@ -16479,7 +16164,7 @@ var init_codex_session_source = __esm({
16479
16164
  if (!existsSync31(dir))
16480
16165
  return results;
16481
16166
  for (const entry of readdirSync23(dir, { withFileTypes: true })) {
16482
- const fullPath = join30(dir, entry.name);
16167
+ const fullPath = join31(dir, entry.name);
16483
16168
  if (entry.isDirectory()) {
16484
16169
  results.push(...this.collectJsonlFiles(fullPath));
16485
16170
  } else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
@@ -16493,8 +16178,8 @@ var init_codex_session_source = __esm({
16493
16178
  });
16494
16179
 
16495
16180
  // ../engine/dist/parsers/diary-source.js
16496
- import { existsSync as existsSync32, mkdirSync as mkdirSync5, readdirSync as readdirSync24, readFileSync as readFileSync22, writeFileSync as writeFileSync4, statSync as statSync28 } from "fs";
16497
- import { join as join31, basename as basename17 } from "path";
16181
+ import { existsSync as existsSync32, mkdirSync as mkdirSync6, readdirSync as readdirSync24, readFileSync as readFileSync23, writeFileSync as writeFileSync5, statSync as statSync28 } from "fs";
16182
+ import { join as join32, basename as basename17 } from "path";
16498
16183
  var MAX_CHUNK_CHARS10, DiarySource;
16499
16184
  var init_diary_source = __esm({
16500
16185
  "../engine/dist/parsers/diary-source.js"() {
@@ -16513,13 +16198,13 @@ var init_diary_source = __esm({
16513
16198
  return;
16514
16199
  const agents = readdirSync24(this.diaryDir, { withFileTypes: true }).filter((d) => d.isDirectory());
16515
16200
  for (const agentDir of agents) {
16516
- const agentPath2 = join31(this.diaryDir, agentDir.name);
16201
+ const agentPath2 = join32(this.diaryDir, agentDir.name);
16517
16202
  const files = readdirSync24(agentPath2).filter((f) => f.endsWith(".json"));
16518
16203
  for (const file of files) {
16519
- const filePath = join31(agentPath2, file);
16204
+ const filePath = join32(agentPath2, file);
16520
16205
  try {
16521
16206
  const stat3 = statSync28(filePath);
16522
- const raw = readFileSync22(filePath, "utf-8");
16207
+ const raw = readFileSync23(filePath, "utf-8");
16523
16208
  const entry = JSON.parse(raw);
16524
16209
  const entryId = basename17(file, ".json");
16525
16210
  const title = `[${entry.agent}] ${entry.topic}: ${entry.content.slice(0, 80)}`;
@@ -16546,7 +16231,7 @@ var init_diary_source = __esm({
16546
16231
  async parse(item) {
16547
16232
  if (!existsSync32(item.filePath))
16548
16233
  return [];
16549
- const raw = readFileSync22(item.filePath, "utf-8");
16234
+ const raw = readFileSync23(item.filePath, "utf-8");
16550
16235
  const entry = JSON.parse(raw);
16551
16236
  if (!entry.content || entry.content.trim().length < 10)
16552
16237
  return [];
@@ -16583,18 +16268,18 @@ var init_diary_source = __esm({
16583
16268
  */
16584
16269
  static write(entry, diaryDir) {
16585
16270
  const dir = diaryDir || getDiaryDir();
16586
- const agentDir = join31(dir, entry.agent.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
16271
+ const agentDir = join32(dir, entry.agent.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
16587
16272
  if (!existsSync32(agentDir)) {
16588
- mkdirSync5(agentDir, { recursive: true });
16273
+ mkdirSync6(agentDir, { recursive: true });
16589
16274
  }
16590
16275
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
16591
16276
  const entryId = `diary_${entry.agent.toLowerCase()}_${ts}`;
16592
- const filePath = join31(agentDir, `${entryId}.json`);
16277
+ const filePath = join32(agentDir, `${entryId}.json`);
16593
16278
  const full = {
16594
16279
  ...entry,
16595
16280
  timestamp: entry.timestamp || (/* @__PURE__ */ new Date()).toISOString()
16596
16281
  };
16597
- writeFileSync4(filePath, JSON.stringify(full, null, 2), "utf-8");
16282
+ writeFileSync5(filePath, JSON.stringify(full, null, 2), "utf-8");
16598
16283
  return entryId;
16599
16284
  }
16600
16285
  /**
@@ -16602,14 +16287,14 @@ var init_diary_source = __esm({
16602
16287
  */
16603
16288
  static read(agentName, lastN = 10, diaryDir) {
16604
16289
  const dir = diaryDir || getDiaryDir();
16605
- const agentDir = join31(dir, agentName.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
16290
+ const agentDir = join32(dir, agentName.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
16606
16291
  if (!existsSync32(agentDir))
16607
16292
  return [];
16608
16293
  const files = readdirSync24(agentDir).filter((f) => f.endsWith(".json")).sort().reverse().slice(0, lastN);
16609
16294
  const entries = [];
16610
16295
  for (const file of files) {
16611
16296
  try {
16612
- const raw = readFileSync22(join31(agentDir, file), "utf-8");
16297
+ const raw = readFileSync23(join32(agentDir, file), "utf-8");
16613
16298
  entries.push(JSON.parse(raw));
16614
16299
  } catch {
16615
16300
  continue;
@@ -16654,9 +16339,9 @@ var init_redact_inline = __esm({
16654
16339
  });
16655
16340
 
16656
16341
  // ../engine/dist/parsers/skills-source.js
16657
- import { existsSync as existsSync33, readdirSync as readdirSync25, readFileSync as readFileSync23, statSync as statSync29 } from "fs";
16342
+ import { existsSync as existsSync33, readdirSync as readdirSync25, readFileSync as readFileSync24, statSync as statSync29 } from "fs";
16658
16343
  import { homedir as homedir12 } from "os";
16659
- import { join as join32 } from "path";
16344
+ import { join as join33 } from "path";
16660
16345
  function rebuildBody(content) {
16661
16346
  const { text, redacted } = redactInlineSecrets(content);
16662
16347
  const out = {};
@@ -16674,25 +16359,25 @@ function resolveRoots() {
16674
16359
  const home = homedir12();
16675
16360
  const all = [];
16676
16361
  if (isSourceEnabled("shared", "skills")) {
16677
- all.push({ path: join32(home, ".agents", "skills"), tool: "shared" });
16362
+ all.push({ path: join33(home, ".agents", "skills"), tool: "shared" });
16678
16363
  }
16679
16364
  if (isSourceEnabled("claude", "skills")) {
16680
16365
  all.push({ path: claudeBackend.skillsDir(), tool: "claude" });
16681
16366
  }
16682
16367
  if (isSourceEnabled("agy", "skills")) {
16683
- all.push({ path: join32(agyBackend.homeDir(), "skills"), tool: "agy" });
16368
+ all.push({ path: join33(agyBackend.homeDir(), "skills"), tool: "agy" });
16684
16369
  all.push({ path: agyBackend.sharedSkillsDir(), tool: "agy" });
16685
16370
  }
16686
16371
  if (isSourceEnabled("opencode", "skills")) {
16687
- all.push({ path: join32(home, ".config", "opencode", "skills"), tool: "opencode" }, { path: join32(home, ".config", "opencode", "skill"), tool: "opencode" }, { path: join32(home, ".opencode", "skill"), tool: "opencode" }, { path: join32(home, ".opencode", "skills"), tool: "opencode" });
16372
+ all.push({ path: join33(home, ".config", "opencode", "skills"), tool: "opencode" }, { path: join33(home, ".config", "opencode", "skill"), tool: "opencode" }, { path: join33(home, ".opencode", "skill"), tool: "opencode" }, { path: join33(home, ".opencode", "skills"), tool: "opencode" });
16688
16373
  }
16689
16374
  if (isSourceEnabled("codex", "skills")) {
16690
16375
  all.push({ path: codexBackend.skillsDir(), tool: "codex", skipChildren: [".system"] });
16691
16376
  all.push({ path: codexBackend.skillsSystemDir(), tool: "codex", readonly: true });
16692
16377
  }
16693
16378
  if (isSourceEnabled("cursor", "skills")) {
16694
- all.push({ path: join32(cursorHomeDir(), "skills"), tool: "cursor" });
16695
- all.push({ path: join32(cursorHomeDir(), "skills-cursor"), tool: "cursor", readonly: true });
16379
+ all.push({ path: join33(cursorHomeDir(), "skills"), tool: "cursor" });
16380
+ all.push({ path: join33(cursorHomeDir(), "skills-cursor"), tool: "cursor", readonly: true });
16696
16381
  }
16697
16382
  return all.filter((r) => existsSync33(r.path));
16698
16383
  }
@@ -16737,7 +16422,7 @@ var init_skills_source = __esm({
16737
16422
  const codexPluginsDir = codexBackend.pluginsDir();
16738
16423
  if (existsSync33(codexPluginsDir)) {
16739
16424
  for (const pluginName of readdirSync25(codexPluginsDir)) {
16740
- const skillsDir = join32(codexPluginsDir, pluginName, "skills");
16425
+ const skillsDir = join33(codexPluginsDir, pluginName, "skills");
16741
16426
  if (!existsSync33(skillsDir))
16742
16427
  continue;
16743
16428
  yield* this.fromSkillRoot({ path: skillsDir, tool: "codex" }, pluginName);
@@ -16754,7 +16439,7 @@ var init_skills_source = __esm({
16754
16439
  for (const name of entries) {
16755
16440
  if (root.skipChildren?.includes(name))
16756
16441
  continue;
16757
- const skillDir = join32(root.path, name);
16442
+ const skillDir = join33(root.path, name);
16758
16443
  let st;
16759
16444
  try {
16760
16445
  st = statSync29(skillDir);
@@ -16763,12 +16448,12 @@ var init_skills_source = __esm({
16763
16448
  }
16764
16449
  if (!st.isDirectory())
16765
16450
  continue;
16766
- const skillMd = join32(skillDir, "SKILL.md");
16451
+ const skillMd = join33(skillDir, "SKILL.md");
16767
16452
  if (!existsSync33(skillMd))
16768
16453
  continue;
16769
16454
  let content;
16770
16455
  try {
16771
- content = readFileSync23(skillMd, "utf-8");
16456
+ content = readFileSync24(skillMd, "utf-8");
16772
16457
  } catch {
16773
16458
  continue;
16774
16459
  }
@@ -16782,7 +16467,7 @@ var init_skills_source = __esm({
16782
16467
  const subdirs = {};
16783
16468
  try {
16784
16469
  for (const sub of readdirSync25(skillDir)) {
16785
- const subPath = join32(skillDir, sub);
16470
+ const subPath = join33(skillDir, sub);
16786
16471
  try {
16787
16472
  const ss = statSync29(subPath);
16788
16473
  if (ss.isDirectory()) {
@@ -16831,7 +16516,7 @@ var init_skills_source = __esm({
16831
16516
  async parse(item) {
16832
16517
  if (!existsSync33(item.filePath))
16833
16518
  return [];
16834
- const content = readFileSync23(item.filePath, "utf-8");
16519
+ const content = readFileSync24(item.filePath, "utf-8");
16835
16520
  if (!content.trim())
16836
16521
  return [];
16837
16522
  return [{
@@ -16854,14 +16539,14 @@ var init_skills_source = __esm({
16854
16539
  });
16855
16540
 
16856
16541
  // ../engine/dist/parsers/mcps-source.js
16857
- import { existsSync as existsSync34, readdirSync as readdirSync26, readFileSync as readFileSync24, statSync as statSync30 } from "fs";
16542
+ import { existsSync as existsSync34, readdirSync as readdirSync26, readFileSync as readFileSync25, statSync as statSync30 } from "fs";
16858
16543
  import { homedir as homedir13 } from "os";
16859
- import { join as join33 } from "path";
16544
+ import { join as join34 } from "path";
16860
16545
  function readJson(path) {
16861
16546
  if (!existsSync34(path))
16862
16547
  return null;
16863
16548
  try {
16864
- const raw = readFileSync24(path, "utf-8");
16549
+ const raw = readFileSync25(path, "utf-8");
16865
16550
  const stripped = path.endsWith(".jsonc") ? raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/,(\s*[}\]])/g, "$1") : raw;
16866
16551
  return JSON.parse(stripped);
16867
16552
  } catch {
@@ -16922,23 +16607,23 @@ var init_mcps_source = __esm({
16922
16607
  if (!isSourceEnabled("common", "mcps"))
16923
16608
  return;
16924
16609
  const home = homedir13();
16925
- yield* this.fromObject(readJson(join33(home, ".mcp.json"))?.mcpServers, {
16610
+ yield* this.fromObject(readJson(join34(home, ".mcp.json"))?.mcpServers, {
16926
16611
  tool: "claude",
16927
- filePath: join33(home, ".mcp.json"),
16612
+ filePath: join34(home, ".mcp.json"),
16928
16613
  scope: "global"
16929
16614
  });
16930
- yield* this.fromObject(readJson(join33(home, ".claude.json"))?.mcpServers, {
16615
+ yield* this.fromObject(readJson(join34(home, ".claude.json"))?.mcpServers, {
16931
16616
  tool: "claude",
16932
- filePath: join33(home, ".claude.json"),
16617
+ filePath: join34(home, ".claude.json"),
16933
16618
  scope: "user"
16934
16619
  });
16935
16620
  for (const path of [
16936
- join33(home, ".config", "opencode", "opencode.json"),
16937
- join33(home, ".config", "opencode", "opencode.jsonc"),
16938
- join33(home, ".config", "opencode", "config.json"),
16939
- join33(home, ".opencode", "opencode.json"),
16940
- join33(home, ".opencode", "opencode.jsonc"),
16941
- join33(home, ".opencode", "config.json")
16621
+ join34(home, ".config", "opencode", "opencode.json"),
16622
+ join34(home, ".config", "opencode", "opencode.jsonc"),
16623
+ join34(home, ".config", "opencode", "config.json"),
16624
+ join34(home, ".opencode", "opencode.json"),
16625
+ join34(home, ".opencode", "opencode.jsonc"),
16626
+ join34(home, ".opencode", "config.json")
16942
16627
  ]) {
16943
16628
  yield* this.fromObject(readJson(path)?.mcp, {
16944
16629
  tool: "opencode",
@@ -16953,13 +16638,13 @@ var init_mcps_source = __esm({
16953
16638
  filePath: geminiSettings,
16954
16639
  scope: "user"
16955
16640
  });
16956
- const cursorMcp = join33(cursorHomeDir(), "mcp.json");
16641
+ const cursorMcp = join34(cursorHomeDir(), "mcp.json");
16957
16642
  yield* this.fromObject(readJson(cursorMcp)?.mcpServers, {
16958
16643
  tool: "cursor",
16959
16644
  filePath: cursorMcp,
16960
16645
  scope: "user"
16961
16646
  });
16962
- const agyMcp = join33(home, ".gemini", "config", "mcp_config.json");
16647
+ const agyMcp = join34(home, ".gemini", "config", "mcp_config.json");
16963
16648
  yield* this.fromObject(readJson(agyMcp)?.mcpServers, {
16964
16649
  tool: "agy",
16965
16650
  filePath: agyMcp,
@@ -16969,7 +16654,7 @@ var init_mcps_source = __esm({
16969
16654
  const pluginsDir = codexBackend.pluginsDir();
16970
16655
  if (existsSync34(pluginsDir)) {
16971
16656
  for (const plugin of readdirSync26(pluginsDir)) {
16972
- const mcpPath = join33(pluginsDir, plugin, ".mcp.json");
16657
+ const mcpPath = join34(pluginsDir, plugin, ".mcp.json");
16973
16658
  yield* this.fromObject(readJson(mcpPath)?.mcpServers, {
16974
16659
  tool: "codex",
16975
16660
  filePath: mcpPath,
@@ -17023,7 +16708,7 @@ var init_mcps_source = __esm({
17023
16708
  return;
17024
16709
  let content;
17025
16710
  try {
17026
- content = readFileSync24(path, "utf-8");
16711
+ content = readFileSync25(path, "utf-8");
17027
16712
  } catch {
17028
16713
  return;
17029
16714
  }
@@ -17192,8 +16877,8 @@ var init_toolkit_format = __esm({
17192
16877
  });
17193
16878
 
17194
16879
  // ../engine/dist/parsers/slash-commands-source.js
17195
- import { existsSync as existsSync35, readdirSync as readdirSync27, readFileSync as readFileSync25, statSync as statSync31 } from "fs";
17196
- import { join as join34, basename as basename18 } from "path";
16880
+ import { existsSync as existsSync35, readdirSync as readdirSync27, readFileSync as readFileSync26, statSync as statSync31 } from "fs";
16881
+ import { join as join35, basename as basename18 } from "path";
17197
16882
  function rebuildBody2(content) {
17198
16883
  const { text, redacted } = redactInlineSecrets(content);
17199
16884
  const out = {};
@@ -17251,7 +16936,7 @@ var init_slash_commands_source = __esm({
17251
16936
  if (!entry.isDirectory() || entry.name.startsWith("."))
17252
16937
  continue;
17253
16938
  const projectPath = resolveProjectDirName(entry.name);
17254
- const cmdDir = join34(projectPath, ".claude", "commands");
16939
+ const cmdDir = join35(projectPath, ".claude", "commands");
17255
16940
  if (existsSync35(cmdDir))
17256
16941
  roots.push({ path: cmdDir, tool: "claude", scope: "project", projectPath, format: "md" });
17257
16942
  }
@@ -17260,7 +16945,7 @@ var init_slash_commands_source = __esm({
17260
16945
  }
17261
16946
  }
17262
16947
  if (isSourceEnabled("agy", "commands")) {
17263
- roots.push({ path: join34(agyBackend.homeDir(), "commands"), tool: "agy", scope: "user", projectPath: "", format: "toml" });
16948
+ roots.push({ path: join35(agyBackend.homeDir(), "commands"), tool: "agy", scope: "user", projectPath: "", format: "toml" });
17264
16949
  roots.push({ path: agyBackend.sharedCommandsDir(), tool: "agy", scope: "user", projectPath: "", format: "toml" });
17265
16950
  }
17266
16951
  if (isSourceEnabled("opencode", "commands")) {
@@ -17270,7 +16955,7 @@ var init_slash_commands_source = __esm({
17270
16955
  roots.push({ path: codexBackend.promptsDir(), tool: "codex", scope: "user", projectPath: "", format: "md" });
17271
16956
  }
17272
16957
  if (isSourceEnabled("cursor", "commands")) {
17273
- roots.push({ path: join34(cursorHomeDir(), "commands"), tool: "cursor", scope: "user", projectPath: "", format: "md" });
16958
+ roots.push({ path: join35(cursorHomeDir(), "commands"), tool: "cursor", scope: "user", projectPath: "", format: "md" });
17274
16959
  }
17275
16960
  return roots;
17276
16961
  }
@@ -17284,10 +16969,10 @@ var init_slash_commands_source = __esm({
17284
16969
  continue;
17285
16970
  }
17286
16971
  for (const file of files) {
17287
- const filePath = join34(root.path, file);
16972
+ const filePath = join35(root.path, file);
17288
16973
  try {
17289
16974
  const stat3 = statSync31(filePath);
17290
- const content = readFileSync25(filePath, "utf-8");
16975
+ const content = readFileSync26(filePath, "utf-8");
17291
16976
  const parsed = parseCommand(content, basename18(file, ext), root.format);
17292
16977
  yield {
17293
16978
  id: `${root.tool}_cmd_${root.scope}_${parsed.name}`,
@@ -17316,7 +17001,7 @@ var init_slash_commands_source = __esm({
17316
17001
  async parse(item) {
17317
17002
  if (!existsSync35(item.filePath))
17318
17003
  return [];
17319
- const content = readFileSync25(item.filePath, "utf-8");
17004
+ const content = readFileSync26(item.filePath, "utf-8");
17320
17005
  if (!content.trim())
17321
17006
  return [];
17322
17007
  return [{
@@ -17339,8 +17024,8 @@ var init_slash_commands_source = __esm({
17339
17024
  });
17340
17025
 
17341
17026
  // ../engine/dist/parsers/subagents-source.js
17342
- import { existsSync as existsSync36, readdirSync as readdirSync28, readFileSync as readFileSync26, statSync as statSync32 } from "fs";
17343
- import { join as join35, basename as basename19 } from "path";
17027
+ import { existsSync as existsSync36, readdirSync as readdirSync28, readFileSync as readFileSync27, statSync as statSync32 } from "fs";
17028
+ import { join as join36, basename as basename19 } from "path";
17344
17029
  function rebuildBody3(content) {
17345
17030
  const { text, redacted } = redactInlineSecrets(content);
17346
17031
  const out = {};
@@ -17400,7 +17085,7 @@ var init_subagents_source = __esm({
17400
17085
  if (!entry.isDirectory() || entry.name.startsWith("."))
17401
17086
  continue;
17402
17087
  const projectPath = resolveProjectDirName(entry.name);
17403
- const agentDir = join35(projectPath, ".claude", "agents");
17088
+ const agentDir = join36(projectPath, ".claude", "agents");
17404
17089
  if (existsSync36(agentDir))
17405
17090
  roots.push({ path: agentDir, tool: "claude", scope: "project", projectPath, format: "md" });
17406
17091
  }
@@ -17409,7 +17094,7 @@ var init_subagents_source = __esm({
17409
17094
  }
17410
17095
  }
17411
17096
  if (isSourceEnabled("agy", "agents")) {
17412
- roots.push({ path: join35(agyBackend.homeDir(), "agents"), tool: "agy", scope: "user", projectPath: "", format: "md" });
17097
+ roots.push({ path: join36(agyBackend.homeDir(), "agents"), tool: "agy", scope: "user", projectPath: "", format: "md" });
17413
17098
  roots.push({ path: agyBackend.sharedAgentsDir(), tool: "agy", scope: "user", projectPath: "", format: "md" });
17414
17099
  }
17415
17100
  if (isSourceEnabled("opencode", "agents")) {
@@ -17419,7 +17104,7 @@ var init_subagents_source = __esm({
17419
17104
  roots.push({ path: codexBackend.agentsDir(), tool: "codex", scope: "user", projectPath: "", format: "toml" });
17420
17105
  }
17421
17106
  if (isSourceEnabled("cursor", "agents")) {
17422
- roots.push({ path: join35(cursorHomeDir(), "agents"), tool: "cursor", scope: "user", projectPath: "", format: "md" });
17107
+ roots.push({ path: join36(cursorHomeDir(), "agents"), tool: "cursor", scope: "user", projectPath: "", format: "md" });
17423
17108
  }
17424
17109
  return roots;
17425
17110
  }
@@ -17433,10 +17118,10 @@ var init_subagents_source = __esm({
17433
17118
  continue;
17434
17119
  }
17435
17120
  for (const file of files) {
17436
- const filePath = join35(root.path, file);
17121
+ const filePath = join36(root.path, file);
17437
17122
  try {
17438
17123
  const stat3 = statSync32(filePath);
17439
- const content = readFileSync26(filePath, "utf-8");
17124
+ const content = readFileSync27(filePath, "utf-8");
17440
17125
  const parsed = parseAgent(content, basename19(file, ext), root.format);
17441
17126
  yield {
17442
17127
  id: `${root.tool}_agent_${root.scope}_${parsed.name}`,
@@ -17466,7 +17151,7 @@ var init_subagents_source = __esm({
17466
17151
  async parse(item) {
17467
17152
  if (!existsSync36(item.filePath))
17468
17153
  return [];
17469
- const content = readFileSync26(item.filePath, "utf-8");
17154
+ const content = readFileSync27(item.filePath, "utf-8");
17470
17155
  if (!content.trim())
17471
17156
  return [];
17472
17157
  return [{
@@ -17489,13 +17174,13 @@ var init_subagents_source = __esm({
17489
17174
  });
17490
17175
 
17491
17176
  // ../engine/dist/parsers/hooks-source.js
17492
- import { existsSync as existsSync37, readFileSync as readFileSync27, readdirSync as readdirSync29, statSync as statSync33 } from "fs";
17493
- import { join as join36 } from "path";
17177
+ import { existsSync as existsSync37, readFileSync as readFileSync28, readdirSync as readdirSync29, statSync as statSync33 } from "fs";
17178
+ import { join as join37 } from "path";
17494
17179
  function readJson2(path) {
17495
17180
  if (!existsSync37(path))
17496
17181
  return null;
17497
17182
  try {
17498
- return JSON.parse(readFileSync27(path, "utf-8"));
17183
+ return JSON.parse(readFileSync28(path, "utf-8"));
17499
17184
  } catch {
17500
17185
  return null;
17501
17186
  }
@@ -17520,7 +17205,7 @@ var init_hooks_source = __esm({
17520
17205
  if (!entry.isDirectory() || entry.name.startsWith("."))
17521
17206
  continue;
17522
17207
  const projectPath = resolveProjectDirName(entry.name);
17523
- const settingsPath2 = join36(projectPath, ".claude", "settings.json");
17208
+ const settingsPath2 = join37(projectPath, ".claude", "settings.json");
17524
17209
  if (existsSync37(settingsPath2)) {
17525
17210
  yield* this.fromSettings(settingsPath2, "project", projectPath);
17526
17211
  }
@@ -17612,14 +17297,14 @@ var init_hooks_source = __esm({
17612
17297
  });
17613
17298
 
17614
17299
  // ../engine/dist/parsers/plugins-source.js
17615
- import { existsSync as existsSync38, readFileSync as readFileSync28, readdirSync as readdirSync30, statSync as statSync34 } from "fs";
17300
+ import { existsSync as existsSync38, readFileSync as readFileSync29, readdirSync as readdirSync30, statSync as statSync34 } from "fs";
17616
17301
  import { homedir as homedir14 } from "os";
17617
- import { join as join37 } from "path";
17302
+ import { join as join38 } from "path";
17618
17303
  function readJson3(path) {
17619
17304
  if (!existsSync38(path))
17620
17305
  return null;
17621
17306
  try {
17622
- return JSON.parse(readFileSync28(path, "utf-8"));
17307
+ return JSON.parse(readFileSync29(path, "utf-8"));
17623
17308
  } catch {
17624
17309
  return null;
17625
17310
  }
@@ -17679,7 +17364,7 @@ var init_plugins_source = __esm({
17679
17364
  entries = [];
17680
17365
  }
17681
17366
  for (const name of entries) {
17682
- const dir = join37(gemRoot, name);
17367
+ const dir = join38(gemRoot, name);
17683
17368
  let st;
17684
17369
  try {
17685
17370
  st = statSync34(dir);
@@ -17688,7 +17373,7 @@ var init_plugins_source = __esm({
17688
17373
  }
17689
17374
  if (!st.isDirectory())
17690
17375
  continue;
17691
- const manifestPath = join37(dir, "gemini-extension.json");
17376
+ const manifestPath = join38(dir, "gemini-extension.json");
17692
17377
  const manifest = readJson3(manifestPath);
17693
17378
  if (!manifest || typeof manifest !== "object")
17694
17379
  continue;
@@ -17727,7 +17412,7 @@ var init_plugins_source = __esm({
17727
17412
  entries = [];
17728
17413
  }
17729
17414
  for (const name of entries) {
17730
- const pluginDir = join37(codexPluginsRoot, name);
17415
+ const pluginDir = join38(codexPluginsRoot, name);
17731
17416
  let st;
17732
17417
  try {
17733
17418
  st = statSync34(pluginDir);
@@ -17736,11 +17421,11 @@ var init_plugins_source = __esm({
17736
17421
  }
17737
17422
  if (!st.isDirectory())
17738
17423
  continue;
17739
- const appJson = readJson3(join37(pluginDir, ".codex-plugin", ".app.json"));
17740
- const pluginJson = readJson3(join37(pluginDir, ".codex-plugin", "plugin.json"));
17424
+ const appJson = readJson3(join38(pluginDir, ".codex-plugin", ".app.json"));
17425
+ const pluginJson = readJson3(join38(pluginDir, ".codex-plugin", "plugin.json"));
17741
17426
  const manifest = pluginJson || appJson || {};
17742
17427
  let mcpServers = [];
17743
- const mcpJson = readJson3(join37(pluginDir, ".mcp.json"));
17428
+ const mcpJson = readJson3(join38(pluginDir, ".mcp.json"));
17744
17429
  if (mcpJson?.mcpServers)
17745
17430
  mcpServers = Object.keys(mcpJson.mcpServers);
17746
17431
  yield {
@@ -17748,7 +17433,7 @@ var init_plugins_source = __esm({
17748
17433
  sourceType: "plugin",
17749
17434
  title: manifest.name || name,
17750
17435
  projectPath: "",
17751
- filePath: join37(pluginDir, ".codex-plugin"),
17436
+ filePath: join38(pluginDir, ".codex-plugin"),
17752
17437
  mtime: st.mtimeMs,
17753
17438
  contentPreview: (manifest.description || "").slice(0, 300),
17754
17439
  extra: {
@@ -17796,7 +17481,6 @@ var init_plugins_source = __esm({
17796
17481
 
17797
17482
  // ../engine/dist/parsers/all-sources.js
17798
17483
  function buildSourceRegistry(registry = new SourceRegistry()) {
17799
- registry.register(new SessionSource());
17800
17484
  registry.register(new PlanSource());
17801
17485
  registry.register(new TaskSource());
17802
17486
  registry.register(new ClaudeMdSource());
@@ -17820,7 +17504,6 @@ var init_all_sources = __esm({
17820
17504
  "../engine/dist/parsers/all-sources.js"() {
17821
17505
  "use strict";
17822
17506
  init_source_registry();
17823
- init_session_source();
17824
17507
  init_plan_source();
17825
17508
  init_task_source();
17826
17509
  init_claude_md_source();
@@ -17908,8 +17591,8 @@ __export(sync_ledger_exports, {
17908
17591
  syncMode: () => syncMode,
17909
17592
  unackedCursorAttemptCount: () => unackedCursorAttemptCount
17910
17593
  });
17911
- import { readFileSync as readFileSync29, writeFileSync as writeFileSync5, renameSync as renameSync4, mkdirSync as mkdirSync6 } from "node:fs";
17912
- import { join as join38, dirname as dirname15 } from "node:path";
17594
+ import { readFileSync as readFileSync30, writeFileSync as writeFileSync6, renameSync as renameSync4, mkdirSync as mkdirSync7 } from "node:fs";
17595
+ import { join as join39, dirname as dirname16 } from "node:path";
17913
17596
  function toRow(entry) {
17914
17597
  if (entry === void 0) return void 0;
17915
17598
  return typeof entry === "number" ? { m: entry, v: 0 } : entry;
@@ -17920,7 +17603,7 @@ function ledgerFilePath() {
17920
17603
  function load() {
17921
17604
  if (cache3) return cache3;
17922
17605
  try {
17923
- const parsed = JSON.parse(readFileSync29(ledgerPath(), "utf-8"));
17606
+ const parsed = JSON.parse(readFileSync30(ledgerPath(), "utf-8"));
17924
17607
  cache3 = parsed && typeof parsed === "object" ? parsed : {};
17925
17608
  } catch {
17926
17609
  cache3 = {};
@@ -17929,9 +17612,9 @@ function load() {
17929
17612
  }
17930
17613
  function persistNow(data) {
17931
17614
  const path = ledgerPath();
17932
- mkdirSync6(dirname15(path), { recursive: true });
17615
+ mkdirSync7(dirname16(path), { recursive: true });
17933
17616
  const tmp = `${path}.tmp`;
17934
- writeFileSync5(tmp, JSON.stringify(data));
17617
+ writeFileSync6(tmp, JSON.stringify(data));
17935
17618
  renameSync4(tmp, path);
17936
17619
  pendingWrite = false;
17937
17620
  }
@@ -17966,10 +17649,10 @@ function pruneLedgerTargets(configuredServers) {
17966
17649
  try {
17967
17650
  let prior = {};
17968
17651
  try {
17969
- prior = JSON.parse(readFileSync29(side, "utf-8"));
17652
+ prior = JSON.parse(readFileSync30(side, "utf-8"));
17970
17653
  } catch {
17971
17654
  }
17972
- writeFileSync5(side, JSON.stringify({ ...prior, ...orphans }));
17655
+ writeFileSync6(side, JSON.stringify({ ...prior, ...orphans }));
17973
17656
  } catch {
17974
17657
  for (const n of names) data[n] = orphans[n];
17975
17658
  return 0;
@@ -17990,7 +17673,7 @@ function flushLedger() {
17990
17673
  function loadItemVersionsAll() {
17991
17674
  if (itemVersionsCache) return itemVersionsCache;
17992
17675
  try {
17993
- const parsed = JSON.parse(readFileSync29(itemVersionsPath(), "utf-8"));
17676
+ const parsed = JSON.parse(readFileSync30(itemVersionsPath(), "utf-8"));
17994
17677
  itemVersionsCache = parsed && typeof parsed === "object" ? parsed : {};
17995
17678
  } catch {
17996
17679
  itemVersionsCache = {};
@@ -18004,9 +17687,9 @@ function saveItemVersions(server, versions) {
18004
17687
  const all = loadItemVersionsAll();
18005
17688
  all[server] = { ...all[server] || {}, ...versions };
18006
17689
  const path = itemVersionsPath();
18007
- mkdirSync6(dirname15(path), { recursive: true });
17690
+ mkdirSync7(dirname16(path), { recursive: true });
18008
17691
  const tmp = `${path}.tmp`;
18009
- writeFileSync5(tmp, JSON.stringify(all));
17692
+ writeFileSync6(tmp, JSON.stringify(all));
18010
17693
  renameSync4(tmp, path);
18011
17694
  }
18012
17695
  function getSyncedMtimes(server) {
@@ -18084,7 +17767,7 @@ function markFieldCoverage(server, field, rows) {
18084
17767
  function loadReconcile() {
18085
17768
  if (reconcileCache) return reconcileCache;
18086
17769
  try {
18087
- const parsed = JSON.parse(readFileSync29(reconcilePath(), "utf-8"));
17770
+ const parsed = JSON.parse(readFileSync30(reconcilePath(), "utf-8"));
18088
17771
  reconcileCache = parsed && typeof parsed === "object" ? parsed : {};
18089
17772
  } catch {
18090
17773
  reconcileCache = {};
@@ -18093,9 +17776,9 @@ function loadReconcile() {
18093
17776
  }
18094
17777
  function persistReconcile(data) {
18095
17778
  const path = reconcilePath();
18096
- mkdirSync6(dirname15(path), { recursive: true });
17779
+ mkdirSync7(dirname16(path), { recursive: true });
18097
17780
  const tmp = `${path}.tmp`;
18098
- writeFileSync5(tmp, JSON.stringify(data));
17781
+ writeFileSync6(tmp, JSON.stringify(data));
18099
17782
  renameSync4(tmp, path);
18100
17783
  }
18101
17784
  function stateFor(server) {
@@ -18153,15 +17836,15 @@ var init_sync_ledger = __esm({
18153
17836
  "use strict";
18154
17837
  init_paths();
18155
17838
  init_extractor_version();
18156
- ledgerPath = () => join38(getDataDir(), "sync-ledger.json");
17839
+ ledgerPath = () => join39(getDataDir(), "sync-ledger.json");
18157
17840
  cache3 = null;
18158
17841
  LEDGER_FLUSH_MS = 2e3;
18159
17842
  pendingWrite = false;
18160
17843
  flushTimer = null;
18161
- itemVersionsPath = () => join38(getDataDir(), "item-versions.json");
17844
+ itemVersionsPath = () => join39(getDataDir(), "item-versions.json");
18162
17845
  itemVersionsCache = null;
18163
17846
  unackedCursorAttempts = 0;
18164
- reconcilePath = () => join38(getDataDir(), "sync-field-reconcile.json");
17847
+ reconcilePath = () => join39(getDataDir(), "sync-field-reconcile.json");
18165
17848
  reconcileCache = null;
18166
17849
  }
18167
17850
  });
@@ -18192,11 +17875,11 @@ var init_sync_fields = __esm({
18192
17875
  });
18193
17876
 
18194
17877
  // ../engine/dist/core/index-lock.js
18195
- import { existsSync as existsSync39, mkdirSync as mkdirSync7, openSync as openSync7, writeFileSync as writeFileSync6, readFileSync as readFileSync30, rmSync as rmSync4, statSync as statSync35, closeSync as closeSync7 } from "fs";
18196
- import { dirname as dirname16, join as join39 } from "path";
17878
+ import { existsSync as existsSync39, mkdirSync as mkdirSync8, openSync as openSync7, writeFileSync as writeFileSync7, readFileSync as readFileSync31, rmSync as rmSync4, statSync as statSync35, closeSync as closeSync7 } from "fs";
17879
+ import { dirname as dirname17, join as join40 } from "path";
18197
17880
  function acquireIndexLock(opts) {
18198
17881
  const lockPath = lockFilePath();
18199
- mkdirSync7(dirname16(lockPath), { recursive: true });
17882
+ mkdirSync8(dirname17(lockPath), { recursive: true });
18200
17883
  if (existsSync39(lockPath)) {
18201
17884
  if (!stealStaleLock(lockPath, opts.staleAfterMs ?? 10 * 6e4)) {
18202
17885
  return null;
@@ -18212,7 +17895,7 @@ function acquireIndexLock(opts) {
18212
17895
  }
18213
17896
  const holder = { pid: process.pid, kind: opts.kind, startedAt: Date.now() };
18214
17897
  try {
18215
- writeFileSync6(fd, JSON.stringify(holder));
17898
+ writeFileSync7(fd, JSON.stringify(holder));
18216
17899
  } finally {
18217
17900
  closeSync7(fd);
18218
17901
  }
@@ -18224,7 +17907,7 @@ function acquireIndexLock(opts) {
18224
17907
  return;
18225
17908
  released = true;
18226
17909
  try {
18227
- const current = JSON.parse(readFileSync30(lockPath, "utf-8"));
17910
+ const current = JSON.parse(readFileSync31(lockPath, "utf-8"));
18228
17911
  if (current.pid !== holder.pid || current.startedAt !== holder.startedAt)
18229
17912
  return;
18230
17913
  } catch {
@@ -18238,7 +17921,7 @@ function acquireIndexLock(opts) {
18238
17921
  };
18239
17922
  }
18240
17923
  function lockFilePath() {
18241
- return join39(getIndexDir(), ".compact.lock");
17924
+ return join40(getIndexDir(), ".compact.lock");
18242
17925
  }
18243
17926
  function stealStaleLock(lockPath, staleAfterMs) {
18244
17927
  try {
@@ -18246,7 +17929,7 @@ function stealStaleLock(lockPath, staleAfterMs) {
18246
17929
  const age = Date.now() - s.mtimeMs;
18247
17930
  let payload;
18248
17931
  try {
18249
- payload = JSON.parse(readFileSync30(lockPath, "utf-8"));
17932
+ payload = JSON.parse(readFileSync31(lockPath, "utf-8"));
18250
17933
  } catch {
18251
17934
  rmSync4(lockPath, { force: true });
18252
17935
  return true;
@@ -18284,7 +17967,7 @@ var init_index_lock = __esm({
18284
17967
  });
18285
17968
 
18286
17969
  // src/http.ts
18287
- import { readFileSync as readFileSync31 } from "node:fs";
17970
+ import { readFileSync as readFileSync32 } from "node:fs";
18288
17971
  function clientHeaders() {
18289
17972
  return {
18290
17973
  "x-chat-recall-cli": CLI_VERSION,
@@ -18302,10 +17985,10 @@ var init_http = __esm({
18302
17985
  "use strict";
18303
17986
  CLI_VERSION = "0.0.0";
18304
17987
  try {
18305
- CLI_VERSION = JSON.parse(readFileSync31(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
17988
+ CLI_VERSION = JSON.parse(readFileSync32(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
18306
17989
  } catch {
18307
17990
  try {
18308
- if (true) CLI_VERSION = "0.6.8";
17991
+ if (true) CLI_VERSION = "0.7.0";
18309
17992
  } catch {
18310
17993
  }
18311
17994
  }
@@ -18330,27 +18013,6 @@ var init_redact_deep = __esm({
18330
18013
  }
18331
18014
  });
18332
18015
 
18333
- // ../engine/dist/core/session-multi-tool.js
18334
- function extractTurnsAny(sessionId, opts = {}) {
18335
- const backend = getBackendForId(sessionId);
18336
- if (!backend)
18337
- return { sessionId, found: false, turns: [], startMs: 0, endMs: 0 };
18338
- return backend.extractTurns(sessionId, opts);
18339
- }
18340
- function replaySessionAny(sessionId) {
18341
- const backend = getBackendForId(sessionId);
18342
- if (!backend)
18343
- return { sessionId, found: false, projectPath: "", files: [], totalLinesAdded: 0, totalLinesRemoved: 0 };
18344
- return backend.replay(sessionId);
18345
- }
18346
- var init_session_multi_tool = __esm({
18347
- "../engine/dist/core/session-multi-tool.js"() {
18348
- "use strict";
18349
- init_tool_backend();
18350
- init_backends();
18351
- }
18352
- });
18353
-
18354
18016
  // src/derived.ts
18355
18017
  function collectDerivedRows(ref, mtime, count, maxRowBytes) {
18356
18018
  const id = ref.prefixedId;
@@ -18821,7 +18483,7 @@ var init_telemetry = __esm({
18821
18483
  init_sync_client();
18822
18484
  cliVersion = () => {
18823
18485
  try {
18824
- return true ? "0.6.8" : "dev";
18486
+ return true ? "0.7.0" : "dev";
18825
18487
  } catch {
18826
18488
  return "dev";
18827
18489
  }
@@ -18831,60 +18493,6 @@ var init_telemetry = __esm({
18831
18493
  }
18832
18494
  });
18833
18495
 
18834
- // ../engine/dist/core/collector-health.js
18835
- import { readFileSync as readFileSync32, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8 } from "node:fs";
18836
- import { join as join40, dirname as dirname17 } from "node:path";
18837
- function collectorHealthPath() {
18838
- return healthPath();
18839
- }
18840
- function readCollectorHealth() {
18841
- try {
18842
- const h = JSON.parse(readFileSync32(healthPath(), "utf-8"));
18843
- return h && h.v === 1 && typeof h.updatedAt === "number" ? h : null;
18844
- } catch {
18845
- return null;
18846
- }
18847
- }
18848
- function writeCollectorHealth(h) {
18849
- try {
18850
- const p = healthPath();
18851
- mkdirSync8(dirname17(p), { recursive: true });
18852
- writeFileSync7(p, JSON.stringify(h));
18853
- } catch {
18854
- }
18855
- }
18856
- function updateCollectorHealth(patch) {
18857
- const prior = readCollectorHealth();
18858
- writeCollectorHealth({
18859
- ...prior,
18860
- v: 1,
18861
- updatedAt: Date.now(),
18862
- startedAt: prior?.startedAt ?? Date.now(),
18863
- restartsLastHour: prior?.restartsLastHour ?? 0,
18864
- targets: prior?.targets ?? {},
18865
- ...patch
18866
- });
18867
- }
18868
- function reportWalkProgress(p) {
18869
- const now = Date.now();
18870
- const boundary = p.complete || p.done === 0;
18871
- if (!boundary && now - lastProgressWrite < PROGRESS_WRITE_INTERVAL_MS)
18872
- return;
18873
- lastProgressWrite = now;
18874
- updateCollectorHealth({ progress: p });
18875
- }
18876
- var healthPath, PROGRESS_WRITE_INTERVAL_MS, lastProgressWrite, STALE_AFTER_MS;
18877
- var init_collector_health = __esm({
18878
- "../engine/dist/core/collector-health.js"() {
18879
- "use strict";
18880
- init_paths();
18881
- healthPath = () => join40(getDataDir(), "collector-health.json");
18882
- PROGRESS_WRITE_INTERVAL_MS = 2e3;
18883
- lastProgressWrite = 0;
18884
- STALE_AFTER_MS = 30 * 6e4;
18885
- }
18886
- });
18887
-
18888
18496
  // src/server-probe.ts
18889
18497
  var server_probe_exports = {};
18890
18498
  __export(server_probe_exports, {
@@ -19571,10 +19179,11 @@ async function syncSessions(opts = {}) {
19571
19179
  };
19572
19180
  continue;
19573
19181
  }
19182
+ acceptedUploads.set(cred.serverUrl, 0);
19574
19183
  try {
19575
19184
  const r = await syncToTarget(cred, opts);
19576
19185
  noteTargetSuccess(cred.serverUrl);
19577
- perTarget[cred.serverUrl] = { ok: true };
19186
+ perTarget[cred.serverUrl] = { ok: true, accepted: acceptedUploads.get(cred.serverUrl) ?? 0 };
19578
19187
  agg = agg ? {
19579
19188
  uploaded: agg.uploaded + r.uploaded,
19580
19189
  skipped: agg.skipped + r.skipped,
@@ -19591,7 +19200,7 @@ async function syncSessions(opts = {}) {
19591
19200
  } catch (e) {
19592
19201
  const msg = e instanceof Error ? e.message : String(e);
19593
19202
  errors.push(`${cred.serverUrl}: ${msg}`);
19594
- perTarget[cred.serverUrl] = { ok: false, error: msg };
19203
+ perTarget[cred.serverUrl] = { ok: false, error: msg, accepted: acceptedUploads.get(cred.serverUrl) ?? 0 };
19595
19204
  const next = noteTargetFailure(cred.serverUrl, msg);
19596
19205
  const notice = breakerNotice(cred.serverUrl, next);
19597
19206
  if (notice) console.error(`[sync] ${notice}`);
@@ -19830,7 +19439,7 @@ async function syncToTargetInScope(cred, opts = {}) {
19830
19439
  return [];
19831
19440
  }
19832
19441
  });
19833
- const MAX_BATCH_BYTES = 4 * 1024 * 1024;
19442
+ const MAX_BATCH_BYTES = Number(process.env.CHAT_RECALL_MAX_BATCH_BYTES) || (isLocalHost(cred.serverUrl) ? 4 * 1024 * 1024 : 1024 * 1024);
19834
19443
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
19835
19444
  const explicitThrottle = typeof opts.throttleMs === "number" && opts.throttleMs > 0;
19836
19445
  const throttleMs = explicitThrottle ? opts.throttleMs : 0;
@@ -19865,6 +19474,7 @@ async function syncToTargetInScope(cred, opts = {}) {
19865
19474
  signal: ac.signal
19866
19475
  });
19867
19476
  if (res.ok) {
19477
+ acceptedUploads.set(cred.serverUrl, (acceptedUploads.get(cred.serverUrl) ?? 0) + 1);
19868
19478
  const body2 = await res.json().catch(() => ({}));
19869
19479
  if (typeof body2.telemetry === "boolean") {
19870
19480
  setTelemetryEligible(base2, body2.telemetry);
@@ -19915,7 +19525,8 @@ async function syncToTargetInScope(cred, opts = {}) {
19915
19525
  if (attempt >= RETRY_DELAYS_MS.length) throw new Error(`sync failed after ${attempt + 1} attempts: HTTP ${res.status} ${text}`);
19916
19526
  const retryAfterS = Number(res.headers.get("retry-after"));
19917
19527
  const waitMs = res.status === 429 && retryAfterS > 0 ? retryAfterS * 1e3 : RETRY_DELAYS_MS[attempt];
19918
- console.error(`[sync] HTTP ${res.status} from ${base2} \u2014 backing off ${Math.round(waitMs / 1e3)}s (attempt ${attempt + 1})`);
19528
+ const why = res.status === 524 ? ` \u2014 the proxy cut the request at its time limit; the batch is too big for one request` : "";
19529
+ console.error(`[sync] HTTP ${res.status} from ${base2}${why} \u2014 backing off ${Math.round(waitMs / 1e3)}s (attempt ${attempt + 1})`);
19919
19530
  await sleep(waitMs);
19920
19531
  continue;
19921
19532
  } catch (err) {
@@ -20774,7 +20385,7 @@ async function syncIncremental(opts = {}) {
20774
20385
  lock.release();
20775
20386
  }
20776
20387
  }
20777
- var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, SYNC_RULES_REFUSAL, hashPath, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
20388
+ var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, SYNC_RULES_REFUSAL, hashPath, acceptedUploads, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
20778
20389
  var init_sync_client = __esm({
20779
20390
  "src/sync-client.ts"() {
20780
20391
  "use strict";
@@ -20819,7 +20430,7 @@ var init_sync_client = __esm({
20819
20430
  ownVersion = JSON.parse(readFileSync35(join42(dir, "../package.json"), "utf-8")).version || ownVersion;
20820
20431
  } catch {
20821
20432
  try {
20822
- if (true) ownVersion = "0.6.8";
20433
+ if (true) ownVersion = "0.7.0";
20823
20434
  } catch {
20824
20435
  }
20825
20436
  }
@@ -20838,6 +20449,7 @@ var init_sync_client = __esm({
20838
20449
  EMPTY_SYNC_CONFIG = { excludeTools: [], excludeProjects: [], excludeSources: [], approveSources: [] };
20839
20450
  SYNC_RULES_REFUSAL = /* @__PURE__ */ Symbol.for("chat-recall.syncRulesRefusal");
20840
20451
  hashPath = (p) => p ? "p_" + createHash8("sha256").update(p).digest("hex").slice(0, 12) : "";
20452
+ acceptedUploads = /* @__PURE__ */ new Map();
20841
20453
  MAX_DERIVED_ROW_BYTES = 2 * 1024 * 1024;
20842
20454
  isLocalHost = isPrivateHost;
20843
20455
  CAPS_TTL_MS = 60 * 6e4;
@@ -23582,6 +23194,7 @@ var init_service_installer = __esm({
23582
23194
  // auto-indexer/indexer.ts
23583
23195
  init_backends();
23584
23196
  init_paths();
23197
+ init_collector_health();
23585
23198
  init_settings();
23586
23199
  init_sync_client();
23587
23200
  init_http();
@@ -23903,7 +23516,7 @@ try {
23903
23516
  ownVersion2 = JSON.parse(readFileSync39(join48(dir, "../package.json"), "utf-8")).version || ownVersion2;
23904
23517
  } catch {
23905
23518
  try {
23906
- if (true) ownVersion2 = "0.6.8";
23519
+ if (true) ownVersion2 = "0.7.0";
23907
23520
  } catch {
23908
23521
  }
23909
23522
  }
@@ -24251,7 +23864,7 @@ async function runCollectorMigration(opts) {
24251
23864
  init_sync_ledger();
24252
23865
  init_collector_health();
24253
23866
  import { existsSync as existsSync49, readFileSync as readFileSync45, statSync as statSync44 } from "fs";
24254
- var CLI_VERSION3 = true ? "0.6.8" : "0.0.0";
23867
+ var CLI_VERSION3 = true ? "0.7.0" : "0.0.0";
24255
23868
  var DEBOUNCE_MS = 5e3;
24256
23869
  var CLAUDE_DIR = claudeBackend.projectsDir();
24257
23870
  var PLANS_DIR = claudeBackend.plansDir();
@@ -24288,7 +23901,7 @@ async function shipToServer(trigger) {
24288
23901
  );
24289
23902
  }
24290
23903
  for (const [server, outcome] of Object.entries(result.perTarget ?? {})) {
24291
- noteSyncOutcome(server, outcome.ok, outcome.error);
23904
+ noteSyncOutcome(server, outcome.ok, outcome.error, outcome.accepted ?? 0);
24292
23905
  }
24293
23906
  } catch (err) {
24294
23907
  const msg = err instanceof Error ? err.message : String(err);
@@ -24556,6 +24169,9 @@ setTimeout(() => {
24556
24169
  setInterval(() => {
24557
24170
  ticks.request("housekeeping", TICK_PRIORITY.housekeeping, shadowPruneTick);
24558
24171
  }, SHADOW_PRUNE_MS).unref();
24172
+ function _targetHealth() {
24173
+ return targetHealth;
24174
+ }
24559
24175
  function syncTargetUrls() {
24560
24176
  try {
24561
24177
  return loadAllCredentials().map((c) => c.serverUrl);
@@ -24563,17 +24179,8 @@ function syncTargetUrls() {
24563
24179
  return [];
24564
24180
  }
24565
24181
  }
24566
- function noteSyncOutcome(server, ok, err) {
24567
- const t = targetHealth[server] ?? { lastOkAt: null, failures: 0 };
24568
- if (ok) {
24569
- t.lastOkAt = Date.now();
24570
- t.failures = 0;
24571
- delete t.lastError;
24572
- } else {
24573
- t.failures += 1;
24574
- t.lastError = (err ?? "unknown error").slice(0, 160);
24575
- }
24576
- targetHealth[server] = t;
24182
+ function noteSyncOutcome(server, ok, err, accepted = 0) {
24183
+ targetHealth[server] = applySyncOutcome(targetHealth[server], { ok, accepted, error: err });
24577
24184
  publishHealth();
24578
24185
  }
24579
24186
  publishHealth();
@@ -24834,3 +24441,7 @@ process.on("exit", () => {
24834
24441
  } catch {
24835
24442
  }
24836
24443
  });
24444
+ export {
24445
+ _targetHealth,
24446
+ noteSyncOutcome
24447
+ };