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/README.md +2 -2
- package/dist/cli.js +1054 -1397
- package/dist/cli.js.map +1 -1
- package/dist/escalate.js +2 -2
- package/dist/escalate.js.map +1 -1
- package/dist/mcp-relay.js +1 -1
- package/dist/mcp.js +2283 -1940
- package/dist/scan-worker.js +329 -30
- package/dist/scan-worker.js.map +1 -1
- package/dist/sync-client.d.ts +13 -0
- package/dist/sync-client.d.ts.map +1 -1
- package/dist/sync-client.js +38 -4
- package/dist/sync-client.js.map +1 -1
- package/dist/watch.js +1110 -1499
- package/package.json +1 -1
- package/skills/chat-recall/SKILL.md +2 -1
- package/skills/chat-recall-memory/SKILL.md +15 -0
package/dist/cli.js
CHANGED
|
@@ -4549,6 +4549,25 @@ var init_paths = __esm({
|
|
|
4549
4549
|
}
|
|
4550
4550
|
});
|
|
4551
4551
|
|
|
4552
|
+
// ../engine/dist/parsers/banners.js
|
|
4553
|
+
function stripInjectedBanners(text) {
|
|
4554
|
+
let result = text;
|
|
4555
|
+
for (const re of INJECTED_BANNERS)
|
|
4556
|
+
result = result.replace(re, " ");
|
|
4557
|
+
return result;
|
|
4558
|
+
}
|
|
4559
|
+
var INJECTED_BANNERS;
|
|
4560
|
+
var init_banners = __esm({
|
|
4561
|
+
"../engine/dist/parsers/banners.js"() {
|
|
4562
|
+
"use strict";
|
|
4563
|
+
INJECTED_BANNERS = [
|
|
4564
|
+
/MCP issues detected\. ?Run \/mcp list for status\.?/g,
|
|
4565
|
+
/Context low[^\n]*Run \/compact[^\n]*/g,
|
|
4566
|
+
/API Error:[^\n]{0,120}/g
|
|
4567
|
+
];
|
|
4568
|
+
}
|
|
4569
|
+
});
|
|
4570
|
+
|
|
4552
4571
|
// ../engine/dist/core/project-path-match.js
|
|
4553
4572
|
function canonicalProjectPath(p) {
|
|
4554
4573
|
return (p || "").trim().replace(/\\/g, "/").replace(/[-_]/g, "/").replace(/\/{2,}/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
@@ -5439,718 +5458,99 @@ var init_tool_paths = __esm({
|
|
|
5439
5458
|
}
|
|
5440
5459
|
});
|
|
5441
5460
|
|
|
5442
|
-
// ../engine/dist/core/
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
_clearServerRulePack: () => _clearServerRulePack,
|
|
5447
|
-
_resetPrivacyCache: () => _resetPrivacyCache,
|
|
5448
|
-
_resetRedactorCache: () => _resetRedactorCache,
|
|
5449
|
-
applyChunkPrivacy: () => applyChunkPrivacy,
|
|
5450
|
-
findContextualSecrets: () => findContextualSecrets,
|
|
5451
|
-
installServerRulePack: () => installServerRulePack,
|
|
5452
|
-
isFilePathRedactionEnabled: () => isFilePathRedactionEnabled,
|
|
5453
|
-
isRedactionEnabled: () => isRedactionEnabled,
|
|
5454
|
-
isToolOutputRedactionEnabled: () => isToolOutputRedactionEnabled,
|
|
5455
|
-
redactFilePaths: () => redactFilePaths,
|
|
5456
|
-
redactSecrets: () => redactSecrets,
|
|
5457
|
-
redactToolResultBody: () => redactToolResultBody,
|
|
5458
|
-
scanTextForFindings: () => scanTextForFindings,
|
|
5459
|
-
serverRulePackVersion: () => serverRulePackVersion,
|
|
5460
|
-
validateRedactionRule: () => validateRedactionRule
|
|
5461
|
-
});
|
|
5462
|
-
import { statSync as statSync3 } from "fs";
|
|
5463
|
-
import { createHash } from "crypto";
|
|
5464
|
-
function secretOf(rule, m) {
|
|
5465
|
-
const raw = rule.secret != null ? m[rule.secret] : m[0];
|
|
5466
|
-
if (raw == null)
|
|
5467
|
-
return null;
|
|
5468
|
-
if (rule.where && !rule.where(raw))
|
|
5469
|
-
return null;
|
|
5470
|
-
return raw;
|
|
5461
|
+
// ../engine/dist/core/project-dir-name.js
|
|
5462
|
+
import { readdirSync as readdirSync4 } from "node:fs";
|
|
5463
|
+
function looksWindowsEncoded(dirName) {
|
|
5464
|
+
return /^[A-Za-z]--/.test(dirName);
|
|
5471
5465
|
}
|
|
5472
|
-
function
|
|
5473
|
-
if (
|
|
5474
|
-
return
|
|
5475
|
-
if (
|
|
5476
|
-
|
|
5477
|
-
|
|
5466
|
+
function decodeProjectDirName(dirName) {
|
|
5467
|
+
if (!dirName)
|
|
5468
|
+
return "";
|
|
5469
|
+
if (looksWindowsEncoded(dirName)) {
|
|
5470
|
+
const drive = dirName[0].toUpperCase();
|
|
5471
|
+
const rest = dirName.slice(3).replace(/-/g, "\\");
|
|
5472
|
+
return `${drive}:\\${rest}`;
|
|
5473
|
+
}
|
|
5474
|
+
return "/" + dirName.replace(/^-/, "").replace(/-/g, "/");
|
|
5478
5475
|
}
|
|
5479
|
-
function
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
continue;
|
|
5495
|
-
}
|
|
5496
|
-
ranges.push({ start: winStart + tm.index, end: winStart + tm.index + tm[0].length });
|
|
5476
|
+
function resolveProjectDirName(dirName, deps = {}) {
|
|
5477
|
+
const structural = decodeProjectDirName(dirName);
|
|
5478
|
+
if (!dirName)
|
|
5479
|
+
return structural;
|
|
5480
|
+
if (looksWindowsEncoded(dirName))
|
|
5481
|
+
return structural;
|
|
5482
|
+
const readdir3 = deps.readdir ?? ((p) => readdirSync4(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name));
|
|
5483
|
+
const encodeName = (name) => name.replace(/[-_.]/g, "-");
|
|
5484
|
+
let remaining = dirName.replace(/^-/, "");
|
|
5485
|
+
let current = "/";
|
|
5486
|
+
while (remaining) {
|
|
5487
|
+
let children;
|
|
5488
|
+
try {
|
|
5489
|
+
children = readdir3(current);
|
|
5490
|
+
} catch {
|
|
5497
5491
|
break;
|
|
5498
5492
|
}
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5493
|
+
let best = "";
|
|
5494
|
+
for (const child of children) {
|
|
5495
|
+
const encoded = encodeName(child);
|
|
5496
|
+
if (!encoded || encoded.length < best.length)
|
|
5497
|
+
continue;
|
|
5498
|
+
if (!remaining.startsWith(encoded))
|
|
5499
|
+
continue;
|
|
5500
|
+
const next = remaining[encoded.length];
|
|
5501
|
+
if (next !== void 0 && next !== "-")
|
|
5502
|
+
continue;
|
|
5503
|
+
if (encoded.length > best.length)
|
|
5504
|
+
best = child;
|
|
5507
5505
|
}
|
|
5508
|
-
|
|
5506
|
+
if (!best)
|
|
5507
|
+
break;
|
|
5508
|
+
current = current === "/" ? `/${best}` : `${current}/${best}`;
|
|
5509
|
+
remaining = remaining.slice(encodeName(best).length).replace(/^-/, "");
|
|
5509
5510
|
}
|
|
5510
|
-
return
|
|
5511
|
-
}
|
|
5512
|
-
function isEnvRedactionRequested() {
|
|
5513
|
-
const v = process.env.CHAT_RECALL_REDACT_INDEX;
|
|
5514
|
-
if (v === void 0)
|
|
5515
|
-
return null;
|
|
5516
|
-
return v === "1" || v === "true" || v === "yes";
|
|
5511
|
+
return remaining ? structural : current;
|
|
5517
5512
|
}
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
mtimeMs = statSync3(settingsFilePath()).mtimeMs;
|
|
5522
|
-
} catch {
|
|
5513
|
+
var init_project_dir_name = __esm({
|
|
5514
|
+
"../engine/dist/core/project-dir-name.js"() {
|
|
5515
|
+
"use strict";
|
|
5523
5516
|
}
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5517
|
+
});
|
|
5518
|
+
|
|
5519
|
+
// ../engine/dist/core/model-pricing.js
|
|
5520
|
+
function pricingFor(modelId) {
|
|
5521
|
+
if (!modelId)
|
|
5522
|
+
return null;
|
|
5523
|
+
const lower = modelId.toLowerCase();
|
|
5524
|
+
let best = null;
|
|
5525
|
+
let bestLen = -1;
|
|
5526
|
+
for (const [prefix, entry] of Object.entries(TABLE)) {
|
|
5527
|
+
if (lower.startsWith(prefix.toLowerCase()) && prefix.length > bestLen) {
|
|
5528
|
+
best = entry;
|
|
5529
|
+
bestLen = prefix.length;
|
|
5536
5530
|
}
|
|
5537
|
-
} catch {
|
|
5538
|
-
}
|
|
5539
|
-
cache = { mtimeMs, enabled, rules: [...DEFAULT_REDACTION_RULES, ...extra] };
|
|
5540
|
-
return cache;
|
|
5541
|
-
}
|
|
5542
|
-
function compileServerRule(spec) {
|
|
5543
|
-
const name = spec.name || "(unnamed)";
|
|
5544
|
-
if (!spec.regex)
|
|
5545
|
-
return { name, reason: "empty pattern" };
|
|
5546
|
-
if (spec.regex.length > MAX_PATTERN_LEN)
|
|
5547
|
-
return { name, reason: `pattern longer than ${MAX_PATTERN_LEN} chars` };
|
|
5548
|
-
let re;
|
|
5549
|
-
try {
|
|
5550
|
-
const flags = "g" + (spec.flags || "").replace(/[^ims]/g, "");
|
|
5551
|
-
re = new RegExp(spec.regex, flags);
|
|
5552
|
-
} catch (e) {
|
|
5553
|
-
return { name, reason: `invalid regex: ${e instanceof Error ? e.message : e}` };
|
|
5554
5531
|
}
|
|
5555
|
-
|
|
5556
|
-
return { name, reason: "matches the empty string (would redact everything)" };
|
|
5557
|
-
re.lastIndex = 0;
|
|
5558
|
-
if (re.test(CANARY))
|
|
5559
|
-
return { name, reason: "matches benign text \u2014 too broad" };
|
|
5560
|
-
re.lastIndex = 0;
|
|
5561
|
-
return { label: `${spec.source === "pack" ? "pack" : "tenant"}:${name}`, pattern: re };
|
|
5562
|
-
}
|
|
5563
|
-
function validateRedactionRule(spec) {
|
|
5564
|
-
const out = compileServerRule(spec);
|
|
5565
|
-
return "reason" in out ? { ok: false, reason: out.reason } : { ok: true };
|
|
5532
|
+
return best;
|
|
5566
5533
|
}
|
|
5567
|
-
function
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
for (const
|
|
5571
|
-
if (
|
|
5572
|
-
rejected.push({ name: spec.name || "(unnamed)", reason: `pack exceeds ${MAX_PACK_RULES} rules` });
|
|
5534
|
+
function estimateCostUsdOrNull(modelsUsed, tokens) {
|
|
5535
|
+
let best = null;
|
|
5536
|
+
let bestRate = -1;
|
|
5537
|
+
for (const m of modelsUsed) {
|
|
5538
|
+
if (!m || m === "<synthetic>")
|
|
5573
5539
|
continue;
|
|
5540
|
+
const p = pricingFor(m);
|
|
5541
|
+
if (p && p.output > bestRate) {
|
|
5542
|
+
best = p;
|
|
5543
|
+
bestRate = p.output;
|
|
5574
5544
|
}
|
|
5575
|
-
const out = compileServerRule(spec);
|
|
5576
|
-
if ("reason" in out)
|
|
5577
|
-
rejected.push(out);
|
|
5578
|
-
else
|
|
5579
|
-
rules.push(out);
|
|
5580
5545
|
}
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
return serverPack ? serverPack.version || "unversioned" : null;
|
|
5587
|
-
}
|
|
5588
|
-
function _clearServerRulePack() {
|
|
5589
|
-
serverPack = null;
|
|
5590
|
-
}
|
|
5591
|
-
function activeRules() {
|
|
5592
|
-
const base3 = settingsView().rules;
|
|
5593
|
-
return serverPack && serverPack.rules.length > 0 ? [...base3, ...serverPack.rules] : base3;
|
|
5594
|
-
}
|
|
5595
|
-
function isRedactionEnabled() {
|
|
5596
|
-
const env = isEnvRedactionRequested();
|
|
5597
|
-
if (env !== null)
|
|
5598
|
-
return env;
|
|
5599
|
-
return settingsView().enabled;
|
|
5546
|
+
if (!best)
|
|
5547
|
+
return null;
|
|
5548
|
+
const nonCachedInput = Math.max(0, tokens.inputTokens - tokens.cacheReadTokens - tokens.cacheCreationTokens);
|
|
5549
|
+
const cost = nonCachedInput / 1e6 * best.input + tokens.outputTokens / 1e6 * best.output + tokens.cacheReadTokens / 1e6 * best.cacheRead + tokens.cacheCreationTokens / 1e6 * best.cacheCreate;
|
|
5550
|
+
return Math.round(cost * 100) / 100;
|
|
5600
5551
|
}
|
|
5601
|
-
function
|
|
5602
|
-
|
|
5603
|
-
}
|
|
5604
|
-
function redactSecrets(text, opts = {}) {
|
|
5605
|
-
if (!opts.force && !isRedactionEnabled())
|
|
5606
|
-
return text;
|
|
5607
|
-
if (!text)
|
|
5608
|
-
return text;
|
|
5609
|
-
const rules = opts.rules || activeRules();
|
|
5610
|
-
let out = text;
|
|
5611
|
-
for (const r of rules) {
|
|
5612
|
-
out = out.replace(r.pattern, (...args) => {
|
|
5613
|
-
const whole = args[0];
|
|
5614
|
-
const m = args.slice(0, args.length);
|
|
5615
|
-
const secret = secretOf(r, m);
|
|
5616
|
-
if (secret == null)
|
|
5617
|
-
return whole;
|
|
5618
|
-
if (opts.count)
|
|
5619
|
-
opts.count.redactions++;
|
|
5620
|
-
return whole.slice(0, whole.length - secret.length) + `[REDACTED:${r.label}]`;
|
|
5621
|
-
});
|
|
5622
|
-
}
|
|
5623
|
-
const ctx = findContextualSecrets(out);
|
|
5624
|
-
for (let i = ctx.length - 1; i >= 0; i--) {
|
|
5625
|
-
const { start, end } = ctx[i];
|
|
5626
|
-
if (opts.count)
|
|
5627
|
-
opts.count.redactions++;
|
|
5628
|
-
out = out.slice(0, start) + "[REDACTED:secret-context]" + out.slice(end);
|
|
5629
|
-
}
|
|
5630
|
-
return out;
|
|
5631
|
-
}
|
|
5632
|
-
function maskSecret(s) {
|
|
5633
|
-
if (!s)
|
|
5634
|
-
return "";
|
|
5635
|
-
if (s.length <= 4)
|
|
5636
|
-
return "*".repeat(s.length);
|
|
5637
|
-
return "*".repeat(Math.min(s.length - 4, 60)) + s.slice(-4);
|
|
5638
|
-
}
|
|
5639
|
-
function globalRegexFor(pattern) {
|
|
5640
|
-
const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
|
|
5641
|
-
const key2 = pattern.source + "\0" + flags;
|
|
5642
|
-
let re = globalRegexCache.get(key2);
|
|
5643
|
-
if (!re) {
|
|
5644
|
-
re = new RegExp(pattern.source, flags);
|
|
5645
|
-
globalRegexCache.set(key2, re);
|
|
5646
|
-
}
|
|
5647
|
-
return re;
|
|
5648
|
-
}
|
|
5649
|
-
function makeLineResolver(text) {
|
|
5650
|
-
const starts = [];
|
|
5651
|
-
for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1))
|
|
5652
|
-
starts.push(i);
|
|
5653
|
-
return (index) => {
|
|
5654
|
-
let lo = 0, hi = starts.length;
|
|
5655
|
-
while (lo < hi) {
|
|
5656
|
-
const mid = lo + hi >> 1;
|
|
5657
|
-
if (starts[mid] < index)
|
|
5658
|
-
lo = mid + 1;
|
|
5659
|
-
else
|
|
5660
|
-
hi = mid;
|
|
5661
|
-
}
|
|
5662
|
-
return lo + 1;
|
|
5663
|
-
};
|
|
5664
|
-
}
|
|
5665
|
-
function scanTextForFindings(text, opts = {}) {
|
|
5666
|
-
if (!text)
|
|
5667
|
-
return [];
|
|
5668
|
-
const rules = opts.rules || activeRules();
|
|
5669
|
-
const findings = [];
|
|
5670
|
-
const seenSpans = /* @__PURE__ */ new Set();
|
|
5671
|
-
const lineAt = makeLineResolver(text);
|
|
5672
|
-
for (const r of rules) {
|
|
5673
|
-
const re = globalRegexFor(r.pattern);
|
|
5674
|
-
re.lastIndex = 0;
|
|
5675
|
-
let m;
|
|
5676
|
-
while ((m = re.exec(text)) !== null) {
|
|
5677
|
-
const secret = secretOf(r, m);
|
|
5678
|
-
if (secret == null) {
|
|
5679
|
-
if (re.lastIndex === m.index)
|
|
5680
|
-
re.lastIndex++;
|
|
5681
|
-
continue;
|
|
5682
|
-
}
|
|
5683
|
-
const at = m.index + (m[0].length - secret.length);
|
|
5684
|
-
const span = `${at}:${secret.length}`;
|
|
5685
|
-
if (!seenSpans.has(span)) {
|
|
5686
|
-
seenSpans.add(span);
|
|
5687
|
-
findings.push({ rule: r.label, line: lineAt(at), preview: maskSecret(secret) });
|
|
5688
|
-
}
|
|
5689
|
-
if (re.lastIndex === m.index)
|
|
5690
|
-
re.lastIndex++;
|
|
5691
|
-
}
|
|
5692
|
-
}
|
|
5693
|
-
for (const { start, end } of findContextualSecrets(text)) {
|
|
5694
|
-
const line = lineAt(start);
|
|
5695
|
-
findings.push({ rule: "secret-context", line, preview: maskSecret(text.slice(start, end)) });
|
|
5696
|
-
}
|
|
5697
|
-
return findings;
|
|
5698
|
-
}
|
|
5699
|
-
function privacyView() {
|
|
5700
|
-
let mtimeMs = -1;
|
|
5701
|
-
try {
|
|
5702
|
-
mtimeMs = statSync3(settingsFilePath()).mtimeMs;
|
|
5703
|
-
} catch {
|
|
5704
|
-
}
|
|
5705
|
-
if (privacyCache && privacyCache.mtimeMs === mtimeMs)
|
|
5706
|
-
return privacyCache;
|
|
5707
|
-
let redactToolOutputs = false, redactFilePaths2 = false;
|
|
5708
|
-
try {
|
|
5709
|
-
const s = loadSettings();
|
|
5710
|
-
redactToolOutputs = !!s.privacy?.redactToolOutputs;
|
|
5711
|
-
redactFilePaths2 = !!s.privacy?.redactFilePaths;
|
|
5712
|
-
} catch {
|
|
5713
|
-
}
|
|
5714
|
-
privacyCache = { mtimeMs, redactToolOutputs, redactFilePaths: redactFilePaths2 };
|
|
5715
|
-
return privacyCache;
|
|
5716
|
-
}
|
|
5717
|
-
function _resetPrivacyCache() {
|
|
5718
|
-
privacyCache = null;
|
|
5719
|
-
}
|
|
5720
|
-
function isToolOutputRedactionEnabled() {
|
|
5721
|
-
return privacyView().redactToolOutputs;
|
|
5722
|
-
}
|
|
5723
|
-
function isFilePathRedactionEnabled() {
|
|
5724
|
-
return privacyView().redactFilePaths;
|
|
5725
|
-
}
|
|
5726
|
-
function redactFilePaths(text) {
|
|
5727
|
-
if (!text)
|
|
5728
|
-
return text;
|
|
5729
|
-
return text.replace(PATH_REGEX, (m) => {
|
|
5730
|
-
if (m.startsWith("[REDACTED:"))
|
|
5731
|
-
return m;
|
|
5732
|
-
const h = createHash("sha256").update(m).digest("hex").slice(0, 12);
|
|
5733
|
-
return `[path:${h}]`;
|
|
5734
|
-
});
|
|
5735
|
-
}
|
|
5736
|
-
function applyChunkPrivacy(text) {
|
|
5737
|
-
let out = redactSecrets(text);
|
|
5738
|
-
if (privacyView().redactFilePaths)
|
|
5739
|
-
out = redactFilePaths(out);
|
|
5740
|
-
return out;
|
|
5741
|
-
}
|
|
5742
|
-
function redactToolResultBody(toolName, body) {
|
|
5743
|
-
if (!body)
|
|
5744
|
-
return body;
|
|
5745
|
-
return `[redacted: ${toolName} output, ${body.length} chars]`;
|
|
5746
|
-
}
|
|
5747
|
-
var DEFAULT_REDACTION_RULES, SECRET_CONTEXT_RE, HIGH_ENTROPY_TOKEN_RE, cache, CANARY, MAX_PACK_RULES, MAX_PATTERN_LEN, serverPack, globalRegexCache, privacyCache, PATH_REGEX;
|
|
5748
|
-
var init_secret_redactor = __esm({
|
|
5749
|
-
"../engine/dist/core/secret-redactor.js"() {
|
|
5750
|
-
"use strict";
|
|
5751
|
-
init_settings();
|
|
5752
|
-
DEFAULT_REDACTION_RULES = [
|
|
5753
|
-
// AWS access tokens. Prefix-anchored: AKIA, ASIA, ABIA (long-term,
|
|
5754
|
-
// STS, EC2). Always exactly 16 hex/alphanumeric after the prefix.
|
|
5755
|
-
// NOTE: this matches only the access-key-ID, NOT the secret key value —
|
|
5756
|
-
// the `env-secret` rule below covers AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN.
|
|
5757
|
-
{ label: "aws-access-token", pattern: /\b(?:AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}\b/g },
|
|
5758
|
-
// AWS STS session tokens. These are long base64 blobs with NO `NAME=` context
|
|
5759
|
-
// when pasted bare into prose/code, so `env-secret` misses them — but they
|
|
5760
|
-
// reliably start with one of a handful of base64 prefixes (the encoded
|
|
5761
|
-
// `{"...` JSON header). Prefix-anchored → low false-positive. This is the
|
|
5762
|
-
// bare-value form that leaked (`search('IQoJEXAMPLEfakeSESSIONtokenNOTreal01234567890abcDEF==...')`).
|
|
5763
|
-
{ label: "aws-session-token", pattern: /\b(?:IQoJ|FwoG|FwoH|FQoG|FQoH|FwoD)[A-Za-z0-9/+]{40,}={0,2}/g },
|
|
5764
|
-
// Env/shell-assignment secrets — a VAR whose name ends in KEY/SECRET/TOKEN/
|
|
5765
|
-
// PASSWORD/CREDENTIAL, then `=`/`:`, then the value. Zero-width lookbehind so
|
|
5766
|
-
// only the VALUE is redacted (the var name stays for context). This is the
|
|
5767
|
-
// dominant real-world leak vector (`export` dumps, .env files, `curl -H`,
|
|
5768
|
-
// tool output) and is exactly what leaked AWS_SECRET_ACCESS_KEY and
|
|
5769
|
-
// AWS_SESSION_TOKEN in cleartext while the access-key-ID rule above caught
|
|
5770
|
-
// only the (least-sensitive) ID.
|
|
5771
|
-
// Suffix list deliberately broad: DB_PASS (our own helm chart's name!),
|
|
5772
|
-
// NPM_AUTH, SENTRY_DSN, SESSION/COOKIE secrets and BEARER vars all leaked
|
|
5773
|
-
// past the original KEY/SECRET/TOKEN/PASSWORD-only list. Values under 12
|
|
5774
|
-
// chars don't redact, so flag-ish vars (BYPASS=true) stay readable.
|
|
5775
|
-
// Context CONSUMED, secret in group 1 — see RedactionRule.secret. As a
|
|
5776
|
-
// lookbehind this was the second most expensive rule in the collector.
|
|
5777
|
-
{ 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 },
|
|
5778
|
-
// Credentials embedded in a connection URL: scheme://user:PASSWORD@host.
|
|
5779
|
-
// Redacts only the password segment (keeps scheme/user/host for context).
|
|
5780
|
-
//
|
|
5781
|
-
// Scheme-agnostic on purpose. The old rule allowlisted eight DB schemes, so
|
|
5782
|
-
// `https://user:pass@host`, `ssh://`, `ftp://`, `smtp://` and every other
|
|
5783
|
-
// userinfo URL walked through in cleartext — and a `jdbc:postgresql://…`
|
|
5784
|
-
// prefix broke the \b anchor for the one family it did cover. Any scheme
|
|
5785
|
-
// followed by userinfo is a credential, so match the shape, not a list.
|
|
5786
|
-
{ label: "url-password", secret: 1, pattern: /\b[a-zA-Z][a-zA-Z0-9+.-]{1,31}:\/\/[^:@\s/]{1,64}:([^@\s/]{1,256})(?=@)/g },
|
|
5787
|
-
// Bare AWS secret-access-key VALUE: EXACTLY 40 base64 chars, word-bounded,
|
|
5788
|
-
// containing upper+lower+digit. This is the gitleaks heuristic — the mixed-case
|
|
5789
|
-
// requirement excludes 40-char lowercase-hex git SHAs, and the exact-40 bound
|
|
5790
|
-
// excludes longer blobs/tokens. It catches the value when it appears bare in
|
|
5791
|
-
// prose with NO `NAME=` and NO nearby context word (e.g. inside a markdown
|
|
5792
|
-
// table or quoted in analysis) — the residual leak path the env-secret and
|
|
5793
|
-
// contextual rules both miss. Placed AFTER env-secret/url-password so the
|
|
5794
|
-
// keyed/URL forms keep their more-specific labels.
|
|
5795
|
-
// The three stacked lookaheads each re-scanned the same 40 characters, which
|
|
5796
|
-
// made this the MOST expensive rule of all 106 the collector runs (13.1% of a
|
|
5797
|
-
// 29.5 MB scan). The mixed-case-plus-digit test is now one JS pass.
|
|
5798
|
-
{
|
|
5799
|
-
label: "aws-secret-key",
|
|
5800
|
-
secret: 1,
|
|
5801
|
-
pattern: /(?<![A-Za-z0-9/+])([A-Za-z0-9/+]{40})(?![A-Za-z0-9/+])/g,
|
|
5802
|
-
where: (v) => /[a-z]/.test(v) && /[A-Z]/.test(v) && /[0-9]/.test(v)
|
|
5803
|
-
},
|
|
5804
|
-
// GitHub personal access tokens.
|
|
5805
|
-
{ label: "github-pat", pattern: /\bghp_[A-Za-z0-9]{36}\b/g },
|
|
5806
|
-
// GitHub fine-grained PATs.
|
|
5807
|
-
{ label: "github-fine", pattern: /\bgithub_pat_[A-Za-z0-9_]{82}\b/g },
|
|
5808
|
-
// GitHub OAuth.
|
|
5809
|
-
{ label: "github-oauth", pattern: /\bgho_[A-Za-z0-9]{36}\b/g },
|
|
5810
|
-
// GitHub app + refresh.
|
|
5811
|
-
{ label: "github-app", pattern: /\b(?:ghs|ghu|ghr)_[A-Za-z0-9]{36}\b/g },
|
|
5812
|
-
// Slack bot/user/app tokens.
|
|
5813
|
-
{ label: "slack-token", pattern: /\bxox[abopr]-[A-Za-z0-9-]{10,}\b/g },
|
|
5814
|
-
// Slack incoming webhook URL.
|
|
5815
|
-
{ label: "slack-webhook", pattern: /\bhttps:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+\b/g },
|
|
5816
|
-
// Stripe live + test keys.
|
|
5817
|
-
{ label: "stripe-key", pattern: /\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{24,}\b/g },
|
|
5818
|
-
// Anthropic.
|
|
5819
|
-
{ label: "anthropic-key", pattern: /\bsk-ant-[a-zA-Z0-9_-]{40,}\b/g },
|
|
5820
|
-
// OpenAI. Two shapes, deliberately kept apart. The classic key is
|
|
5821
|
-
// `sk-` + base62 with NO hyphens; the modern scoped keys carry an explicit
|
|
5822
|
-
// proj/svcacct/admin segment first. Allowing hyphens after a bare `sk-`
|
|
5823
|
-
// (which is what admitted sk-proj- in the first place) also matches ordinary
|
|
5824
|
-
// kebab-case text — `sk-fix-the-login-redirect-bug`, `.sk-button-primary`,
|
|
5825
|
-
// `sk-2024-01-01-backup` — and this corpus is full of branch names and CSS
|
|
5826
|
-
// classes. Requiring the segment keeps sk-proj- coverage without that.
|
|
5827
|
-
{ label: "openai-key", pattern: /\bsk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,}\b|\bsk-[A-Za-z0-9]{20,}\b/g },
|
|
5828
|
-
// Google API key (AIza…).
|
|
5829
|
-
{ label: "google-api-key", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
|
|
5830
|
-
// Generic JWT — three base64url segments.
|
|
5831
|
-
{ label: "jwt", pattern: /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g },
|
|
5832
|
-
// Private key blocks (PEM). The label is a free word run rather than a fixed
|
|
5833
|
-
// list, so ENCRYPTED PRIVATE KEY and PGP PRIVATE KEY BLOCK are covered
|
|
5834
|
-
// without another enumeration, and \1 forces END to name the same kind as
|
|
5835
|
-
// BEGIN.
|
|
5836
|
-
//
|
|
5837
|
-
// The body is [^-], NOT [\s\S]*?. That is a performance boundary, not a
|
|
5838
|
-
// style choice. An unanchored prefix with an unbounded lazy gap re-scans to
|
|
5839
|
-
// end-of-input from every candidate start, so text carrying many BEGIN
|
|
5840
|
-
// markers and no matching END costs O(n^2): 500KB of that measured 810ms
|
|
5841
|
-
// here, and this runs synchronously on the server's event loop over
|
|
5842
|
-
// tenant-supplied text (services/secret-rescan.ts). A PEM body is base64
|
|
5843
|
-
// plus headers and contains no '-', so excluding it makes the scan stop at
|
|
5844
|
-
// the next delimiter instead of at EOF — same matches, linear cost.
|
|
5845
|
-
{ label: "private-key", pattern: /-----BEGIN ([A-Z0-9 ]{0,32})PRIVATE KEY(?: BLOCK)?-----[^-]{0,10000}?-----END \1PRIVATE KEY(?: BLOCK)?-----/g },
|
|
5846
|
-
// PuTTY keys are not PEM. Match the Private-Lines payload — the actual key
|
|
5847
|
-
// material — rather than spanning the whole file, which would need the same
|
|
5848
|
-
// unbounded gap the rule above just removed. Line-anchored and base64-only,
|
|
5849
|
-
// so it cannot run away.
|
|
5850
|
-
{ label: "putty-key", pattern: /^Private-Lines:[ \t]*\d{1,4}[\r\n]+(?:[A-Za-z0-9+/=]{8,120}[\r\n]+){1,400}/gm },
|
|
5851
|
-
// npm tokens (uuid-shaped — overlap with our UUID filter; intentional).
|
|
5852
|
-
{ label: "npm-token", pattern: /\bnpm_[A-Za-z0-9]{36}\b/g },
|
|
5853
|
-
// .npmrc auth lines: //registry.npmjs.org/:_authToken=VALUE (the key is not
|
|
5854
|
-
// ALL-CAPS, so the env-secret rule above never matched it). The leading `:`
|
|
5855
|
-
// is required — it is what makes this npmrc syntax rather than any shell var
|
|
5856
|
-
// ending in _AUTH, which env-secret already owns and labels better.
|
|
5857
|
-
// [^\s"'`]+ so the closing quote is not swallowed, and a ${VAR} placeholder
|
|
5858
|
-
// is left alone — redacting it destroys a config example and hides nothing.
|
|
5859
|
-
// The rule the perf profile caught the wedged daemon inside: 98.94% of samples.
|
|
5860
|
-
{ label: "npmrc-auth", secret: 1, pattern: /:_(?:authToken|password|auth)[ \t]*=[ \t]*(?!\$\{)([^\s"'`]{12,})/gi },
|
|
5861
|
-
// HTTP Authorization headers. env-secret requires an ALL-CAPS name, and
|
|
5862
|
-
// `Authorization` is mixed case, so both Bearer and Basic walked straight
|
|
5863
|
-
// through — including short opaque tokens the contextual pass also misses
|
|
5864
|
-
// (it needs 32+ chars AND mixed-case-plus-digit).
|
|
5865
|
-
// Same two guards as npmrc-auth: stop at a quote, and skip the obvious
|
|
5866
|
-
// placeholders that fill documentation (<YOUR_TOKEN>, ${TOKEN}, xxx…).
|
|
5867
|
-
{ label: "auth-header", secret: 1, pattern: /\bauthorization[ \t]*:[ \t]*(?:bearer|basic|token)[ \t]+(?![<$])([^\s"'`<]{8,})/gi },
|
|
5868
|
-
// Vendor tokens with unambiguous prefixes. Each is a fixed literal, so a
|
|
5869
|
-
// false positive is essentially impossible and the cost of listing is low.
|
|
5870
|
-
{ label: "gitlab-pat", pattern: /\bglpat-[A-Za-z0-9_-]{20,}\b/g },
|
|
5871
|
-
{ label: "slack-app-token", pattern: /\bxapp-\d-[A-Z0-9]{8,}-\d{10,}-[a-f0-9]{32,}\b/g },
|
|
5872
|
-
{ label: "sendgrid-key", pattern: /\bSG\.[A-Za-z0-9_-]{20,24}\.[A-Za-z0-9_-]{40,50}\b/g },
|
|
5873
|
-
// (?<![-\w]) so `cache-key-<md5>` is not read as a mailgun key.
|
|
5874
|
-
{ label: "mailgun-key", pattern: /(?<![-\w])key-[a-f0-9]{32}\b/g },
|
|
5875
|
-
{ label: "digitalocean-pat", pattern: /\bdop_v1_[a-f0-9]{64}\b/g },
|
|
5876
|
-
{ label: "huggingface-token", pattern: /\bhf_[A-Za-z0-9]{30,}\b/g },
|
|
5877
|
-
{ label: "shopify-token", pattern: /\bshp(?:at|ca|pa|ss)_[a-fA-F0-9]{32}\b/g },
|
|
5878
|
-
{ label: "pypi-token", pattern: /\bpypi-AgEIcHlwaS5vcmc[A-Za-z0-9_-]{50,}\b/g },
|
|
5879
|
-
// No twilio-sid rule: AC…/SK… are Twilio SIDs, which are public account and
|
|
5880
|
-
// key identifiers, not the auth token. Redacting them hid nothing and
|
|
5881
|
-
// collided with 32-hex etags and MD5 sums.
|
|
5882
|
-
// No discord-bot-token rule either: the shape (word.word.word) matched
|
|
5883
|
-
// ordinary dotted symbols such as a.b.ApplicationContextProviderImpl.
|
|
5884
|
-
{ label: "telegram-bot-token", pattern: /\b\d{8,10}:AA[A-Za-z0-9_-]{32,}\b/g },
|
|
5885
|
-
{ label: "doppler-token", pattern: /\bdp\.(?:pt|st|sa|ct)\.[A-Za-z0-9_-]{20,}\b/g },
|
|
5886
|
-
// Azure Storage SAS signature — the `sig=` parameter is the actual secret.
|
|
5887
|
-
{ label: "azure-sas", secret: 1, pattern: /[?&]sig=([A-Za-z0-9%/+=]{20,})/g }
|
|
5888
|
-
];
|
|
5889
|
-
SECRET_CONTEXT_RE = /\b(?:secret|password|passwd|credential|access[ _-]?key|private[ _-]?key|api[ _-]?key|auth(?:oriz\w*)?|bearer|token)\b/gi;
|
|
5890
|
-
HIGH_ENTROPY_TOKEN_RE = /[A-Za-z0-9/+_-]{32,}/g;
|
|
5891
|
-
cache = null;
|
|
5892
|
-
CANARY = "the quick brown fox jumps over the lazy dog 0123456789 commit 8ba1f109551bd432803012645ac136ddd475d0fb src/index.ts:42";
|
|
5893
|
-
MAX_PACK_RULES = 200;
|
|
5894
|
-
MAX_PATTERN_LEN = 512;
|
|
5895
|
-
serverPack = null;
|
|
5896
|
-
globalRegexCache = /* @__PURE__ */ new Map();
|
|
5897
|
-
privacyCache = null;
|
|
5898
|
-
PATH_REGEX = /(?<![A-Za-z0-9_])(?:\/[^\s'"`<>]+|[A-Za-z]:\\[^\s'"`<>]+)/g;
|
|
5899
|
-
}
|
|
5900
|
-
});
|
|
5901
|
-
|
|
5902
|
-
// ../engine/dist/parsers/chunker.js
|
|
5903
|
-
function stripInjectedBanners(text) {
|
|
5904
|
-
let result = text;
|
|
5905
|
-
for (const re of INJECTED_BANNERS)
|
|
5906
|
-
result = result.replace(re, " ");
|
|
5907
|
-
return result;
|
|
5908
|
-
}
|
|
5909
|
-
function cleanText(text) {
|
|
5910
|
-
let result = text.replace(/<system-reminder>.*?<\/system-reminder>/gs, "");
|
|
5911
|
-
result = stripInjectedBanners(result);
|
|
5912
|
-
result = result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
5913
|
-
result = result.replace(/\s+/g, " ");
|
|
5914
|
-
return result.trim();
|
|
5915
|
-
}
|
|
5916
|
-
function windowText(text, maxChars, maxWindows = 12) {
|
|
5917
|
-
const clean = cleanText(text);
|
|
5918
|
-
if (clean.length <= maxChars)
|
|
5919
|
-
return clean ? [clean] : [];
|
|
5920
|
-
const out = [];
|
|
5921
|
-
const overlap = 80;
|
|
5922
|
-
const step = Math.max(1, maxChars - overlap);
|
|
5923
|
-
for (let i = 0; i < clean.length && out.length < maxWindows; i += step) {
|
|
5924
|
-
out.push(clean.slice(i, i + maxChars));
|
|
5925
|
-
}
|
|
5926
|
-
return out;
|
|
5927
|
-
}
|
|
5928
|
-
function chunkSession(entry, content, maxChunks = 80, maxTokensPerChunk = 500) {
|
|
5929
|
-
const chunks = [];
|
|
5930
|
-
const maxChars = maxTokensPerChunk * 4;
|
|
5931
|
-
const baseData = {
|
|
5932
|
-
sessionId: entry.sessionId,
|
|
5933
|
-
projectPath: entry.projectPath,
|
|
5934
|
-
created: entry.created,
|
|
5935
|
-
modified: entry.modified,
|
|
5936
|
-
mtime: entry.fileMtime,
|
|
5937
|
-
firstPrompt: entry.firstPrompt || content.firstPrompt,
|
|
5938
|
-
sourceFile: content.sessionPath
|
|
5939
|
-
};
|
|
5940
|
-
for (let i = 0; i < Math.min(2, content.summaries.length); i++) {
|
|
5941
|
-
const summary = content.summaries[i];
|
|
5942
|
-
if (!summary?.trim())
|
|
5943
|
-
continue;
|
|
5944
|
-
const chunkText = cleanText(summary.slice(0, maxChars));
|
|
5945
|
-
if (chunkText.length > 8e3) {
|
|
5946
|
-
chunks.push({
|
|
5947
|
-
chunkId: `${entry.sessionId}_summary_${i}`,
|
|
5948
|
-
chunkType: "summary",
|
|
5949
|
-
text: chunkText.slice(0, 8e3),
|
|
5950
|
-
sourceLine: 0,
|
|
5951
|
-
...baseData
|
|
5952
|
-
});
|
|
5953
|
-
} else {
|
|
5954
|
-
chunks.push({
|
|
5955
|
-
chunkId: `${entry.sessionId}_summary_${i}`,
|
|
5956
|
-
chunkType: "summary",
|
|
5957
|
-
text: chunkText,
|
|
5958
|
-
sourceLine: 0,
|
|
5959
|
-
...baseData
|
|
5960
|
-
});
|
|
5961
|
-
}
|
|
5962
|
-
}
|
|
5963
|
-
if (chunks.length >= maxChunks) {
|
|
5964
|
-
return chunks.slice(0, maxChunks);
|
|
5965
|
-
}
|
|
5966
|
-
const firstPrompt = entry.firstPrompt || content.firstPrompt;
|
|
5967
|
-
if (firstPrompt?.trim()) {
|
|
5968
|
-
const firstLine = content.userMessages[0]?.lineNumber || 0;
|
|
5969
|
-
chunks.push({
|
|
5970
|
-
chunkId: `${entry.sessionId}_first_prompt`,
|
|
5971
|
-
chunkType: "first_prompt",
|
|
5972
|
-
text: cleanText(firstPrompt.slice(0, maxChars)),
|
|
5973
|
-
sourceLine: firstLine,
|
|
5974
|
-
...baseData
|
|
5975
|
-
});
|
|
5976
|
-
}
|
|
5977
|
-
if (chunks.length >= maxChunks) {
|
|
5978
|
-
return chunks.slice(0, maxChunks);
|
|
5979
|
-
}
|
|
5980
|
-
const userStart = firstPrompt ? 1 : 0;
|
|
5981
|
-
for (let i = userStart; i < content.userMessages.length; i++) {
|
|
5982
|
-
if (chunks.length >= maxChunks)
|
|
5983
|
-
break;
|
|
5984
|
-
const msg = content.userMessages[i];
|
|
5985
|
-
if (!msg.text.trim())
|
|
5986
|
-
continue;
|
|
5987
|
-
const windows = windowText(msg.text, maxChars);
|
|
5988
|
-
for (let w = 0; w < windows.length; w++) {
|
|
5989
|
-
if (chunks.length >= maxChunks)
|
|
5990
|
-
break;
|
|
5991
|
-
chunks.push({
|
|
5992
|
-
chunkId: `${entry.sessionId}_user_${i}_${w}`,
|
|
5993
|
-
chunkType: "user_context",
|
|
5994
|
-
text: windows[w],
|
|
5995
|
-
sourceLine: msg.lineNumber,
|
|
5996
|
-
...baseData
|
|
5997
|
-
});
|
|
5998
|
-
}
|
|
5999
|
-
}
|
|
6000
|
-
if (chunks.length >= maxChunks) {
|
|
6001
|
-
return chunks.slice(0, maxChunks);
|
|
6002
|
-
}
|
|
6003
|
-
for (let i = 0; i < content.assistantMessages.length; i++) {
|
|
6004
|
-
if (chunks.length >= maxChunks)
|
|
6005
|
-
break;
|
|
6006
|
-
const msg = content.assistantMessages[i];
|
|
6007
|
-
if (!msg.text.trim() || msg.text.length <= 50)
|
|
6008
|
-
continue;
|
|
6009
|
-
const windows = windowText(msg.text, maxChars);
|
|
6010
|
-
for (let w = 0; w < windows.length; w++) {
|
|
6011
|
-
if (chunks.length >= maxChunks)
|
|
6012
|
-
break;
|
|
6013
|
-
chunks.push({
|
|
6014
|
-
chunkId: `${entry.sessionId}_assistant_${i}_${w}`,
|
|
6015
|
-
chunkType: "assistant",
|
|
6016
|
-
text: windows[w],
|
|
6017
|
-
sourceLine: msg.lineNumber,
|
|
6018
|
-
...baseData
|
|
6019
|
-
});
|
|
6020
|
-
}
|
|
6021
|
-
}
|
|
6022
|
-
if (chunks.length >= maxChunks) {
|
|
6023
|
-
return chunks.slice(0, maxChunks);
|
|
6024
|
-
}
|
|
6025
|
-
if (content.toolResults.length > 0) {
|
|
6026
|
-
const webSearches = content.toolResults.filter((r) => r.contentType === "web_search");
|
|
6027
|
-
const otherResults = content.toolResults.filter((r) => r.contentType !== "web_search");
|
|
6028
|
-
const resultsToProcess = [...webSearches.slice(0, 5), ...otherResults.slice(0, 10)];
|
|
6029
|
-
const stripBodies = isToolOutputRedactionEnabled();
|
|
6030
|
-
for (let i = 0; i < resultsToProcess.length; i++) {
|
|
6031
|
-
if (chunks.length >= maxChunks)
|
|
6032
|
-
break;
|
|
6033
|
-
const result = resultsToProcess[i];
|
|
6034
|
-
if (result.text.trim()) {
|
|
6035
|
-
const body = stripBodies && result.contentType !== "web_search" ? redactToolResultBody(result.contentType, result.text) : result.text.slice(0, maxChars);
|
|
6036
|
-
chunks.push({
|
|
6037
|
-
chunkId: `${entry.sessionId}_${result.contentType}_${i}`,
|
|
6038
|
-
chunkType: result.contentType,
|
|
6039
|
-
text: cleanText(body),
|
|
6040
|
-
sourceLine: result.lineNumber,
|
|
6041
|
-
...baseData
|
|
6042
|
-
});
|
|
6043
|
-
}
|
|
6044
|
-
}
|
|
6045
|
-
}
|
|
6046
|
-
return chunks.slice(0, maxChunks);
|
|
6047
|
-
}
|
|
6048
|
-
var INJECTED_BANNERS;
|
|
6049
|
-
var init_chunker = __esm({
|
|
6050
|
-
"../engine/dist/parsers/chunker.js"() {
|
|
6051
|
-
"use strict";
|
|
6052
|
-
init_secret_redactor();
|
|
6053
|
-
INJECTED_BANNERS = [
|
|
6054
|
-
/MCP issues detected\. ?Run \/mcp list for status\.?/g,
|
|
6055
|
-
/Context low[^\n]*Run \/compact[^\n]*/g,
|
|
6056
|
-
/API Error:[^\n]{0,120}/g
|
|
6057
|
-
];
|
|
6058
|
-
}
|
|
6059
|
-
});
|
|
6060
|
-
|
|
6061
|
-
// ../engine/dist/core/project-dir-name.js
|
|
6062
|
-
import { readdirSync as readdirSync4 } from "node:fs";
|
|
6063
|
-
function looksWindowsEncoded(dirName) {
|
|
6064
|
-
return /^[A-Za-z]--/.test(dirName);
|
|
6065
|
-
}
|
|
6066
|
-
function decodeProjectDirName(dirName) {
|
|
6067
|
-
if (!dirName)
|
|
6068
|
-
return "";
|
|
6069
|
-
if (looksWindowsEncoded(dirName)) {
|
|
6070
|
-
const drive = dirName[0].toUpperCase();
|
|
6071
|
-
const rest = dirName.slice(3).replace(/-/g, "\\");
|
|
6072
|
-
return `${drive}:\\${rest}`;
|
|
6073
|
-
}
|
|
6074
|
-
return "/" + dirName.replace(/^-/, "").replace(/-/g, "/");
|
|
6075
|
-
}
|
|
6076
|
-
function resolveProjectDirName(dirName, deps = {}) {
|
|
6077
|
-
const structural = decodeProjectDirName(dirName);
|
|
6078
|
-
if (!dirName)
|
|
6079
|
-
return structural;
|
|
6080
|
-
if (looksWindowsEncoded(dirName))
|
|
6081
|
-
return structural;
|
|
6082
|
-
const readdir3 = deps.readdir ?? ((p) => readdirSync4(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name));
|
|
6083
|
-
const encodeName = (name) => name.replace(/[-_.]/g, "-");
|
|
6084
|
-
let remaining = dirName.replace(/^-/, "");
|
|
6085
|
-
let current = "/";
|
|
6086
|
-
while (remaining) {
|
|
6087
|
-
let children;
|
|
6088
|
-
try {
|
|
6089
|
-
children = readdir3(current);
|
|
6090
|
-
} catch {
|
|
6091
|
-
break;
|
|
6092
|
-
}
|
|
6093
|
-
let best = "";
|
|
6094
|
-
for (const child of children) {
|
|
6095
|
-
const encoded = encodeName(child);
|
|
6096
|
-
if (!encoded || encoded.length < best.length)
|
|
6097
|
-
continue;
|
|
6098
|
-
if (!remaining.startsWith(encoded))
|
|
6099
|
-
continue;
|
|
6100
|
-
const next = remaining[encoded.length];
|
|
6101
|
-
if (next !== void 0 && next !== "-")
|
|
6102
|
-
continue;
|
|
6103
|
-
if (encoded.length > best.length)
|
|
6104
|
-
best = child;
|
|
6105
|
-
}
|
|
6106
|
-
if (!best)
|
|
6107
|
-
break;
|
|
6108
|
-
current = current === "/" ? `/${best}` : `${current}/${best}`;
|
|
6109
|
-
remaining = remaining.slice(encodeName(best).length).replace(/^-/, "");
|
|
6110
|
-
}
|
|
6111
|
-
return remaining ? structural : current;
|
|
6112
|
-
}
|
|
6113
|
-
var init_project_dir_name = __esm({
|
|
6114
|
-
"../engine/dist/core/project-dir-name.js"() {
|
|
6115
|
-
"use strict";
|
|
6116
|
-
}
|
|
6117
|
-
});
|
|
6118
|
-
|
|
6119
|
-
// ../engine/dist/core/model-pricing.js
|
|
6120
|
-
function pricingFor(modelId) {
|
|
6121
|
-
if (!modelId)
|
|
6122
|
-
return null;
|
|
6123
|
-
const lower = modelId.toLowerCase();
|
|
6124
|
-
let best = null;
|
|
6125
|
-
let bestLen = -1;
|
|
6126
|
-
for (const [prefix, entry] of Object.entries(TABLE)) {
|
|
6127
|
-
if (lower.startsWith(prefix.toLowerCase()) && prefix.length > bestLen) {
|
|
6128
|
-
best = entry;
|
|
6129
|
-
bestLen = prefix.length;
|
|
6130
|
-
}
|
|
6131
|
-
}
|
|
6132
|
-
return best;
|
|
6133
|
-
}
|
|
6134
|
-
function estimateCostUsdOrNull(modelsUsed, tokens) {
|
|
6135
|
-
let best = null;
|
|
6136
|
-
let bestRate = -1;
|
|
6137
|
-
for (const m of modelsUsed) {
|
|
6138
|
-
if (!m || m === "<synthetic>")
|
|
6139
|
-
continue;
|
|
6140
|
-
const p = pricingFor(m);
|
|
6141
|
-
if (p && p.output > bestRate) {
|
|
6142
|
-
best = p;
|
|
6143
|
-
bestRate = p.output;
|
|
6144
|
-
}
|
|
6145
|
-
}
|
|
6146
|
-
if (!best)
|
|
6147
|
-
return null;
|
|
6148
|
-
const nonCachedInput = Math.max(0, tokens.inputTokens - tokens.cacheReadTokens - tokens.cacheCreationTokens);
|
|
6149
|
-
const cost = nonCachedInput / 1e6 * best.input + tokens.outputTokens / 1e6 * best.output + tokens.cacheReadTokens / 1e6 * best.cacheRead + tokens.cacheCreationTokens / 1e6 * best.cacheCreate;
|
|
6150
|
-
return Math.round(cost * 100) / 100;
|
|
6151
|
-
}
|
|
6152
|
-
function estimateCostUsd(modelsUsed, tokens) {
|
|
6153
|
-
return estimateCostUsdOrNull(modelsUsed, tokens) ?? 0;
|
|
5552
|
+
function estimateCostUsd(modelsUsed, tokens) {
|
|
5553
|
+
return estimateCostUsdOrNull(modelsUsed, tokens) ?? 0;
|
|
6154
5554
|
}
|
|
6155
5555
|
var TABLE, METADATA_VERSION;
|
|
6156
5556
|
var init_model_pricing = __esm({
|
|
@@ -6287,7 +5687,7 @@ var init_model_pricing = __esm({
|
|
|
6287
5687
|
});
|
|
6288
5688
|
|
|
6289
5689
|
// ../engine/dist/parsers/session.js
|
|
6290
|
-
import { createReadStream, existsSync as existsSync5, readdirSync as readdirSync5, readFileSync as readFileSync2, statSync as
|
|
5690
|
+
import { createReadStream, existsSync as existsSync5, readdirSync as readdirSync5, readFileSync as readFileSync2, statSync as statSync3, openSync, readSync, closeSync } from "fs";
|
|
6291
5691
|
import { createInterface } from "readline";
|
|
6292
5692
|
import { join as join5, basename as basename2 } from "path";
|
|
6293
5693
|
function removeCodeBlocks(text) {
|
|
@@ -6334,87 +5734,15 @@ function isValuableToolResult(content, toolName = "") {
|
|
|
6334
5734
|
}
|
|
6335
5735
|
return true;
|
|
6336
5736
|
}
|
|
6337
|
-
function parseSessionsIndex(indexPath) {
|
|
6338
|
-
if (!existsSync5(indexPath)) {
|
|
6339
|
-
return [];
|
|
6340
|
-
}
|
|
6341
|
-
const data = JSON.parse(readFileSync2(indexPath, "utf-8"));
|
|
6342
|
-
const entries = data.entries || [];
|
|
6343
|
-
return entries.map((e) => ({
|
|
6344
|
-
sessionId: e.sessionId || "",
|
|
6345
|
-
fullPath: e.fullPath || "",
|
|
6346
|
-
fileMtime: e.fileMtime || 0,
|
|
6347
|
-
firstPrompt: e.firstPrompt || "",
|
|
6348
|
-
messageCount: e.messageCount || 0,
|
|
6349
|
-
created: e.created || "",
|
|
6350
|
-
modified: e.modified || "",
|
|
6351
|
-
gitBranch: e.gitBranch || "",
|
|
6352
|
-
projectPath: e.projectPath || "",
|
|
6353
|
-
isSidechain: e.isSidechain || false
|
|
6354
|
-
}));
|
|
6355
|
-
}
|
|
6356
|
-
function* iterAllSessionIndices(claudeDir) {
|
|
6357
|
-
const dir = claudeDir || claudeBackend.projectsDir();
|
|
6358
|
-
if (!existsSync5(dir)) {
|
|
6359
|
-
return;
|
|
6360
|
-
}
|
|
6361
|
-
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
6362
|
-
for (const entry of entries) {
|
|
6363
|
-
if (!entry.isDirectory())
|
|
6364
|
-
continue;
|
|
6365
|
-
const projectDir = join5(dir, entry.name);
|
|
6366
|
-
const indexPath = join5(projectDir, "sessions-index.json");
|
|
6367
|
-
if (existsSync5(indexPath)) {
|
|
6368
|
-
const sessions = parseSessionsIndex(indexPath);
|
|
6369
|
-
if (sessions.length > 0) {
|
|
6370
|
-
yield [projectDir, sessions];
|
|
6371
|
-
}
|
|
6372
|
-
}
|
|
6373
|
-
}
|
|
6374
|
-
}
|
|
6375
5737
|
function hasSubagentsDir(sessionPath) {
|
|
6376
5738
|
const dir = sessionPath.slice(0, -6);
|
|
6377
5739
|
return existsSync5(join5(dir, "subagents"));
|
|
6378
5740
|
}
|
|
6379
5741
|
function getSubagentPaths(sessionPath) {
|
|
6380
5742
|
const subagentsDir = join5(sessionPath.slice(0, -6), "subagents");
|
|
6381
|
-
if (!existsSync5(subagentsDir))
|
|
6382
|
-
return [];
|
|
6383
|
-
return readdirSync5(subagentsDir).filter((f) => f.endsWith(".jsonl")).sort().map((f) => join5(subagentsDir, f));
|
|
6384
|
-
}
|
|
6385
|
-
function getSubagentProjectPath(sessionPath, folderDerivedPath) {
|
|
6386
|
-
const subagentPaths = getSubagentPaths(sessionPath);
|
|
6387
|
-
if (subagentPaths.length === 0)
|
|
6388
|
-
return folderDerivedPath;
|
|
6389
|
-
const cwdCounts = /* @__PURE__ */ new Map();
|
|
6390
|
-
for (const filePath of subagentPaths) {
|
|
6391
|
-
try {
|
|
6392
|
-
const lines = readFileSync2(filePath, "utf-8").split("\n");
|
|
6393
|
-
for (const line of lines) {
|
|
6394
|
-
if (!line.trim())
|
|
6395
|
-
continue;
|
|
6396
|
-
try {
|
|
6397
|
-
const entry = JSON.parse(line);
|
|
6398
|
-
if (entry.type === "user" && typeof entry.cwd === "string" && entry.cwd) {
|
|
6399
|
-
cwdCounts.set(entry.cwd, (cwdCounts.get(entry.cwd) ?? 0) + 1);
|
|
6400
|
-
}
|
|
6401
|
-
} catch {
|
|
6402
|
-
}
|
|
6403
|
-
}
|
|
6404
|
-
} catch {
|
|
6405
|
-
}
|
|
6406
|
-
}
|
|
6407
|
-
if (cwdCounts.size === 0)
|
|
6408
|
-
return folderDerivedPath;
|
|
6409
|
-
let best = folderDerivedPath;
|
|
6410
|
-
let bestCount = 0;
|
|
6411
|
-
for (const [cwd, count] of cwdCounts) {
|
|
6412
|
-
if (count > bestCount) {
|
|
6413
|
-
best = cwd;
|
|
6414
|
-
bestCount = count;
|
|
6415
|
-
}
|
|
6416
|
-
}
|
|
6417
|
-
return best;
|
|
5743
|
+
if (!existsSync5(subagentsDir))
|
|
5744
|
+
return [];
|
|
5745
|
+
return readdirSync5(subagentsDir).filter((f) => f.endsWith(".jsonl")).sort().map((f) => join5(subagentsDir, f));
|
|
6418
5746
|
}
|
|
6419
5747
|
function readCwdFromJsonl(sessionPath) {
|
|
6420
5748
|
try {
|
|
@@ -6444,22 +5772,6 @@ function readCwdFromJsonl(sessionPath) {
|
|
|
6444
5772
|
}
|
|
6445
5773
|
return "";
|
|
6446
5774
|
}
|
|
6447
|
-
function getSessionMtime(sessionPath) {
|
|
6448
|
-
const subagentPaths = getSubagentPaths(sessionPath);
|
|
6449
|
-
if (subagentPaths.length === 0) {
|
|
6450
|
-
return statSync4(sessionPath).mtimeMs;
|
|
6451
|
-
}
|
|
6452
|
-
let maxMtime = statSync4(sessionPath).mtimeMs;
|
|
6453
|
-
for (const p of subagentPaths) {
|
|
6454
|
-
try {
|
|
6455
|
-
const m = statSync4(p).mtimeMs;
|
|
6456
|
-
if (m > maxMtime)
|
|
6457
|
-
maxMtime = m;
|
|
6458
|
-
} catch {
|
|
6459
|
-
}
|
|
6460
|
-
}
|
|
6461
|
-
return maxMtime;
|
|
6462
|
-
}
|
|
6463
5775
|
async function parseSessionFile(sessionPath, maxMessages = 50) {
|
|
6464
5776
|
const sessionId = basename2(sessionPath, ".jsonl");
|
|
6465
5777
|
const content = {
|
|
@@ -6697,76 +6009,10 @@ ${resultText}` : resultText;
|
|
|
6697
6009
|
});
|
|
6698
6010
|
return content;
|
|
6699
6011
|
}
|
|
6700
|
-
function* getAllSessions(claudeDir) {
|
|
6701
|
-
const seenSessionIds = /* @__PURE__ */ new Set();
|
|
6702
|
-
const projectDirs = claudeDir ? [claudeDir] : claudeProjectDirs();
|
|
6703
|
-
for (const dir of projectDirs) {
|
|
6704
|
-
for (const [, entries] of iterAllSessionIndices(dir)) {
|
|
6705
|
-
for (const entry of entries) {
|
|
6706
|
-
if (!seenSessionIds.has(entry.sessionId) && existsSync5(entry.fullPath)) {
|
|
6707
|
-
seenSessionIds.add(entry.sessionId);
|
|
6708
|
-
yield [entry, entry.fullPath];
|
|
6709
|
-
}
|
|
6710
|
-
}
|
|
6711
|
-
}
|
|
6712
|
-
if (!existsSync5(dir))
|
|
6713
|
-
continue;
|
|
6714
|
-
const dirEntries = readdirSync5(dir, { withFileTypes: true });
|
|
6715
|
-
for (const projDirEntry of dirEntries) {
|
|
6716
|
-
if (!projDirEntry.isDirectory())
|
|
6717
|
-
continue;
|
|
6718
|
-
const projectPath = join5(dir, projDirEntry.name);
|
|
6719
|
-
const files = readdirSync5(projectPath);
|
|
6720
|
-
for (const file of files) {
|
|
6721
|
-
if (!file.endsWith(".jsonl"))
|
|
6722
|
-
continue;
|
|
6723
|
-
if (file.startsWith("agent-"))
|
|
6724
|
-
continue;
|
|
6725
|
-
if (file === "sessions-index.json")
|
|
6726
|
-
continue;
|
|
6727
|
-
const sessionId = basename2(file, ".jsonl");
|
|
6728
|
-
if (seenSessionIds.has(sessionId))
|
|
6729
|
-
continue;
|
|
6730
|
-
const fullPath = join5(projectPath, file);
|
|
6731
|
-
try {
|
|
6732
|
-
const stat3 = statSync4(fullPath);
|
|
6733
|
-
const derivedProjectPath = resolveProjectDirName(projDirEntry.name);
|
|
6734
|
-
const effectiveMtime = getSessionMtime(fullPath);
|
|
6735
|
-
let projectPath2 = "";
|
|
6736
|
-
if (hasSubagentsDir(fullPath)) {
|
|
6737
|
-
projectPath2 = getSubagentProjectPath(fullPath, derivedProjectPath);
|
|
6738
|
-
} else {
|
|
6739
|
-
const fromJsonl = readCwdFromJsonl(fullPath);
|
|
6740
|
-
if (fromJsonl)
|
|
6741
|
-
projectPath2 = fromJsonl;
|
|
6742
|
-
}
|
|
6743
|
-
if (!projectPath2)
|
|
6744
|
-
projectPath2 = derivedProjectPath;
|
|
6745
|
-
const entry = {
|
|
6746
|
-
sessionId,
|
|
6747
|
-
fullPath,
|
|
6748
|
-
fileMtime: effectiveMtime,
|
|
6749
|
-
firstPrompt: "",
|
|
6750
|
-
messageCount: 0,
|
|
6751
|
-
created: stat3.birthtime.toISOString(),
|
|
6752
|
-
modified: new Date(effectiveMtime).toISOString(),
|
|
6753
|
-
gitBranch: "",
|
|
6754
|
-
projectPath: projectPath2,
|
|
6755
|
-
isSidechain: false
|
|
6756
|
-
};
|
|
6757
|
-
seenSessionIds.add(sessionId);
|
|
6758
|
-
yield [entry, fullPath];
|
|
6759
|
-
} catch {
|
|
6760
|
-
continue;
|
|
6761
|
-
}
|
|
6762
|
-
}
|
|
6763
|
-
}
|
|
6764
|
-
}
|
|
6765
|
-
}
|
|
6766
6012
|
var init_session = __esm({
|
|
6767
6013
|
"../engine/dist/parsers/session.js"() {
|
|
6768
6014
|
"use strict";
|
|
6769
|
-
|
|
6015
|
+
init_banners();
|
|
6770
6016
|
init_claude2();
|
|
6771
6017
|
init_tool_paths();
|
|
6772
6018
|
init_project_dir_name();
|
|
@@ -7287,7 +6533,7 @@ __export(raw_exports, {
|
|
|
7287
6533
|
parseTranscriptFromContainer: () => parseTranscriptFromContainer
|
|
7288
6534
|
});
|
|
7289
6535
|
import { gzipSync, gunzipSync } from "zlib";
|
|
7290
|
-
import { createHash
|
|
6536
|
+
import { createHash } from "crypto";
|
|
7291
6537
|
import * as fsMod from "fs";
|
|
7292
6538
|
import * as pathMod from "path";
|
|
7293
6539
|
function buildRawContainer(exp) {
|
|
@@ -7299,7 +6545,7 @@ function buildRawContainer(exp) {
|
|
|
7299
6545
|
};
|
|
7300
6546
|
}
|
|
7301
6547
|
function containerSrcHash(c) {
|
|
7302
|
-
const h =
|
|
6548
|
+
const h = createHash("sha1");
|
|
7303
6549
|
h.update(c.tool);
|
|
7304
6550
|
for (const f of c.files) {
|
|
7305
6551
|
h.update("\0");
|
|
@@ -7821,6 +7067,28 @@ var init_session_git = __esm({
|
|
|
7821
7067
|
}
|
|
7822
7068
|
});
|
|
7823
7069
|
|
|
7070
|
+
// ../engine/dist/core/session-multi-tool.js
|
|
7071
|
+
function extractTurnsAny(sessionId, opts = {}) {
|
|
7072
|
+
const backend = getBackendForId(sessionId);
|
|
7073
|
+
if (!backend)
|
|
7074
|
+
return { sessionId, found: false, turns: [], startMs: 0, endMs: 0 };
|
|
7075
|
+
return memoInSessionScope(`turns:${sessionId}:${opts.maxTurns ?? ""}:${opts.assistantMax ?? ""}`, () => backend.extractTurns(sessionId, opts));
|
|
7076
|
+
}
|
|
7077
|
+
function replaySessionAny(sessionId) {
|
|
7078
|
+
const backend = getBackendForId(sessionId);
|
|
7079
|
+
if (!backend)
|
|
7080
|
+
return { sessionId, found: false, projectPath: "", files: [], totalLinesAdded: 0, totalLinesRemoved: 0 };
|
|
7081
|
+
return memoInSessionScope(`replay:${sessionId}`, () => backend.replay(sessionId));
|
|
7082
|
+
}
|
|
7083
|
+
var init_session_multi_tool = __esm({
|
|
7084
|
+
"../engine/dist/core/session-multi-tool.js"() {
|
|
7085
|
+
"use strict";
|
|
7086
|
+
init_tool_backend();
|
|
7087
|
+
init_live_session_scan();
|
|
7088
|
+
init_backends();
|
|
7089
|
+
}
|
|
7090
|
+
});
|
|
7091
|
+
|
|
7824
7092
|
// ../engine/dist/core/session-outcome.js
|
|
7825
7093
|
function extractDecisionsFromTurn(turn) {
|
|
7826
7094
|
if (turn.kind !== "assistant_text" || !turn.text)
|
|
@@ -7917,7 +7185,7 @@ function computeOutcome(sessionId, opts = {}) {
|
|
|
7917
7185
|
};
|
|
7918
7186
|
if (!backend)
|
|
7919
7187
|
return notFound;
|
|
7920
|
-
const turnsRes =
|
|
7188
|
+
const turnsRes = extractTurnsAny(sessionId, { assistantMax: 2e3 });
|
|
7921
7189
|
if (!turnsRes.found)
|
|
7922
7190
|
return notFound;
|
|
7923
7191
|
const decisions = [];
|
|
@@ -7956,7 +7224,7 @@ function computeOutcome(sessionId, opts = {}) {
|
|
|
7956
7224
|
totalLinesAdded = fast.totalLinesAdded;
|
|
7957
7225
|
totalLinesRemoved = fast.totalLinesRemoved;
|
|
7958
7226
|
} else {
|
|
7959
|
-
const replay =
|
|
7227
|
+
const replay = replaySessionAny(sessionId);
|
|
7960
7228
|
filesChanged = replay.files.map((f) => f.file);
|
|
7961
7229
|
totalLinesAdded = replay.totalLinesAdded;
|
|
7962
7230
|
totalLinesRemoved = replay.totalLinesRemoved;
|
|
@@ -7995,6 +7263,7 @@ var init_session_outcome = __esm({
|
|
|
7995
7263
|
init_session_sentiment();
|
|
7996
7264
|
init_session_git();
|
|
7997
7265
|
init_tool_backend();
|
|
7266
|
+
init_session_multi_tool();
|
|
7998
7267
|
init_backends();
|
|
7999
7268
|
DECISION_PATTERNS = [
|
|
8000
7269
|
/\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,
|
|
@@ -8823,11 +8092,11 @@ var init_opencode = __esm({
|
|
|
8823
8092
|
});
|
|
8824
8093
|
|
|
8825
8094
|
// ../engine/dist/core/backends/tail-read.js
|
|
8826
|
-
import { openSync as openSync2, readSync as readSync2, closeSync as closeSync2, statSync as
|
|
8095
|
+
import { openSync as openSync2, readSync as readSync2, closeSync as closeSync2, statSync as statSync4 } from "node:fs";
|
|
8827
8096
|
function readTailFromOffset(path, offset) {
|
|
8828
8097
|
let size = 0;
|
|
8829
8098
|
try {
|
|
8830
|
-
size =
|
|
8099
|
+
size = statSync4(path).size;
|
|
8831
8100
|
} catch {
|
|
8832
8101
|
return { text: "", newOffset: offset };
|
|
8833
8102
|
}
|
|
@@ -8872,7 +8141,7 @@ var init_tail_read = __esm({
|
|
|
8872
8141
|
});
|
|
8873
8142
|
|
|
8874
8143
|
// ../engine/dist/core/backends/codex.js
|
|
8875
|
-
import { readdirSync as readdirSync6, readFileSync as readFileSync3, statSync as
|
|
8144
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync3, statSync as statSync5, openSync as openSync3, readSync as readSync3, closeSync as closeSync3 } from "fs";
|
|
8876
8145
|
import { basename as basename7, dirname as dirname9, join as join9 } from "path";
|
|
8877
8146
|
function parseShellWrites(cmd) {
|
|
8878
8147
|
const out = [];
|
|
@@ -9080,7 +8349,7 @@ var init_codex2 = __esm({
|
|
|
9080
8349
|
return null;
|
|
9081
8350
|
let mtime = 0;
|
|
9082
8351
|
try {
|
|
9083
|
-
mtime =
|
|
8352
|
+
mtime = statSync5(located.path).mtimeMs;
|
|
9084
8353
|
} catch {
|
|
9085
8354
|
}
|
|
9086
8355
|
return {
|
|
@@ -9100,7 +8369,7 @@ var init_codex2 = __esm({
|
|
|
9100
8369
|
for (const filePath of walkRollouts(root)) {
|
|
9101
8370
|
let stat3;
|
|
9102
8371
|
try {
|
|
9103
|
-
stat3 =
|
|
8372
|
+
stat3 = statSync5(filePath);
|
|
9104
8373
|
} catch {
|
|
9105
8374
|
continue;
|
|
9106
8375
|
}
|
|
@@ -9296,7 +8565,7 @@ var init_codex2 = __esm({
|
|
|
9296
8565
|
let mtime = 0;
|
|
9297
8566
|
try {
|
|
9298
8567
|
raw = readFileSync3(path, "utf-8");
|
|
9299
|
-
mtime =
|
|
8568
|
+
mtime = statSync5(path).mtimeMs;
|
|
9300
8569
|
} catch {
|
|
9301
8570
|
return [];
|
|
9302
8571
|
}
|
|
@@ -9396,7 +8665,7 @@ var init_codex2 = __esm({
|
|
|
9396
8665
|
const events = this.readEvents(this.toRawId(id));
|
|
9397
8666
|
let fileMtime = 0;
|
|
9398
8667
|
try {
|
|
9399
|
-
fileMtime =
|
|
8668
|
+
fileMtime = statSync5(located.path).mtimeMs;
|
|
9400
8669
|
} catch {
|
|
9401
8670
|
}
|
|
9402
8671
|
return liveScanEditsFromEvents(events, this.fileToolMap, {
|
|
@@ -9460,7 +8729,7 @@ var init_codex2 = __esm({
|
|
|
9460
8729
|
const p = join9(dir, entry.name);
|
|
9461
8730
|
let st;
|
|
9462
8731
|
try {
|
|
9463
|
-
st =
|
|
8732
|
+
st = statSync5(p);
|
|
9464
8733
|
} catch {
|
|
9465
8734
|
continue;
|
|
9466
8735
|
}
|
|
@@ -9527,7 +8796,7 @@ var init_codex2 = __esm({
|
|
|
9527
8796
|
let mtime = 0;
|
|
9528
8797
|
const push = (path, name) => {
|
|
9529
8798
|
try {
|
|
9530
|
-
const st =
|
|
8799
|
+
const st = statSync5(path);
|
|
9531
8800
|
files.push({ name, bytes: readFileSync3(path) });
|
|
9532
8801
|
if (st.mtimeMs > mtime)
|
|
9533
8802
|
mtime = st.mtimeMs;
|
|
@@ -9569,7 +8838,7 @@ var init_codex2 = __esm({
|
|
|
9569
8838
|
if (!loc)
|
|
9570
8839
|
return 0;
|
|
9571
8840
|
try {
|
|
9572
|
-
return
|
|
8841
|
+
return statSync5(loc.path).size;
|
|
9573
8842
|
} catch {
|
|
9574
8843
|
return 0;
|
|
9575
8844
|
}
|
|
@@ -9586,7 +8855,7 @@ var init_codex2 = __esm({
|
|
|
9586
8855
|
});
|
|
9587
8856
|
|
|
9588
8857
|
// ../engine/dist/core/backends/agy.js
|
|
9589
|
-
import { existsSync as existsSync8, readdirSync as readdirSync7, readFileSync as readFileSync4, statSync as
|
|
8858
|
+
import { existsSync as existsSync8, readdirSync as readdirSync7, readFileSync as readFileSync4, statSync as statSync6 } from "fs";
|
|
9590
8859
|
import { basename as basename8, dirname as dirname10, join as join10 } from "path";
|
|
9591
8860
|
function agyTranscriptPath(logsDir) {
|
|
9592
8861
|
const full = join10(logsDir, "transcript_full.jsonl");
|
|
@@ -9779,7 +9048,7 @@ var init_agy = __esm({
|
|
|
9779
9048
|
return null;
|
|
9780
9049
|
let mtime = 0;
|
|
9781
9050
|
try {
|
|
9782
|
-
mtime =
|
|
9051
|
+
mtime = statSync6(path).mtimeMs;
|
|
9783
9052
|
} catch {
|
|
9784
9053
|
}
|
|
9785
9054
|
const projectPath = resolveAgyProject(dirname10(path), this.homeDir());
|
|
@@ -9807,7 +9076,7 @@ var init_agy = __esm({
|
|
|
9807
9076
|
for (const rawId of sessionDirs) {
|
|
9808
9077
|
const sessionPath = join10(brainDir, rawId);
|
|
9809
9078
|
try {
|
|
9810
|
-
if (!
|
|
9079
|
+
if (!statSync6(sessionPath).isDirectory())
|
|
9811
9080
|
continue;
|
|
9812
9081
|
} catch {
|
|
9813
9082
|
continue;
|
|
@@ -9817,7 +9086,7 @@ var init_agy = __esm({
|
|
|
9817
9086
|
continue;
|
|
9818
9087
|
let stat3;
|
|
9819
9088
|
try {
|
|
9820
|
-
stat3 =
|
|
9089
|
+
stat3 = statSync6(filePath);
|
|
9821
9090
|
} catch {
|
|
9822
9091
|
continue;
|
|
9823
9092
|
}
|
|
@@ -10082,7 +9351,7 @@ var init_agy = __esm({
|
|
|
10082
9351
|
for (const rawId of sessionDirs) {
|
|
10083
9352
|
const sessionPath = join10(brainDir, rawId);
|
|
10084
9353
|
try {
|
|
10085
|
-
if (!
|
|
9354
|
+
if (!statSync6(sessionPath).isDirectory())
|
|
10086
9355
|
continue;
|
|
10087
9356
|
} catch {
|
|
10088
9357
|
continue;
|
|
@@ -10091,7 +9360,7 @@ var init_agy = __esm({
|
|
|
10091
9360
|
if (!filePath)
|
|
10092
9361
|
continue;
|
|
10093
9362
|
try {
|
|
10094
|
-
const st =
|
|
9363
|
+
const st = statSync6(filePath);
|
|
10095
9364
|
if (st.mtimeMs < opts.sinceMs)
|
|
10096
9365
|
continue;
|
|
10097
9366
|
candidates2.push({ rawId, mtime: st.mtimeMs });
|
|
@@ -10119,7 +9388,7 @@ var init_agy = __esm({
|
|
|
10119
9388
|
try {
|
|
10120
9389
|
return {
|
|
10121
9390
|
tool: "agy",
|
|
10122
|
-
mtime:
|
|
9391
|
+
mtime: statSync6(loc.path).mtimeMs,
|
|
10123
9392
|
files: [{ name: basename8(loc.path), bytes: readFileSync4(loc.path) }]
|
|
10124
9393
|
};
|
|
10125
9394
|
} catch {
|
|
@@ -10134,7 +9403,7 @@ var init_agy = __esm({
|
|
|
10134
9403
|
if (!loc)
|
|
10135
9404
|
return 0;
|
|
10136
9405
|
try {
|
|
10137
|
-
return
|
|
9406
|
+
return statSync6(loc.path).size;
|
|
10138
9407
|
} catch {
|
|
10139
9408
|
return 0;
|
|
10140
9409
|
}
|
|
@@ -10151,7 +9420,7 @@ var init_agy = __esm({
|
|
|
10151
9420
|
});
|
|
10152
9421
|
|
|
10153
9422
|
// ../engine/dist/core/backends/cursor-store.js
|
|
10154
|
-
import { existsSync as existsSync9, readdirSync as readdirSync8, readFileSync as readFileSync5, statSync as
|
|
9423
|
+
import { existsSync as existsSync9, readdirSync as readdirSync8, readFileSync as readFileSync5, statSync as statSync7 } from "fs";
|
|
10155
9424
|
import { join as join11 } from "path";
|
|
10156
9425
|
function cursorProjectSlug(absPath) {
|
|
10157
9426
|
return absPath.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -10168,7 +9437,7 @@ function listChatsIn(chatsRoot) {
|
|
|
10168
9437
|
const hashPath3 = join11(chatsRoot, workspaceHash);
|
|
10169
9438
|
let chatIds;
|
|
10170
9439
|
try {
|
|
10171
|
-
if (!
|
|
9440
|
+
if (!statSync7(hashPath3).isDirectory())
|
|
10172
9441
|
continue;
|
|
10173
9442
|
chatIds = readdirSync8(hashPath3);
|
|
10174
9443
|
} catch {
|
|
@@ -10190,7 +9459,7 @@ function listChatsIn(chatsRoot) {
|
|
|
10190
9459
|
let created = meta.createdAtMs ?? 0;
|
|
10191
9460
|
if (!mtime || !created) {
|
|
10192
9461
|
try {
|
|
10193
|
-
const st =
|
|
9462
|
+
const st = statSync7(dbPath);
|
|
10194
9463
|
mtime = mtime || st.mtimeMs;
|
|
10195
9464
|
created = created || st.birthtimeMs || st.mtimeMs;
|
|
10196
9465
|
} catch {
|
|
@@ -10233,7 +9502,7 @@ function findChat(chatsRoots, chatId) {
|
|
|
10233
9502
|
let created = meta.createdAtMs ?? 0;
|
|
10234
9503
|
if (!mtime || !created) {
|
|
10235
9504
|
try {
|
|
10236
|
-
const st =
|
|
9505
|
+
const st = statSync7(dbPath);
|
|
10237
9506
|
mtime = mtime || st.mtimeMs;
|
|
10238
9507
|
created = created || st.birthtimeMs || st.mtimeMs;
|
|
10239
9508
|
} catch {
|
|
@@ -10536,7 +9805,7 @@ var init_cursor_store = __esm({
|
|
|
10536
9805
|
});
|
|
10537
9806
|
|
|
10538
9807
|
// ../engine/dist/core/backends/cursor-ide.js
|
|
10539
|
-
import { existsSync as existsSync10, readFileSync as readFileSync6, readdirSync as readdirSync9, statSync as
|
|
9808
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6, readdirSync as readdirSync9, statSync as statSync8 } from "fs";
|
|
10540
9809
|
import { dirname as dirname11, join as join12 } from "path";
|
|
10541
9810
|
function jsonOf(value) {
|
|
10542
9811
|
if (value == null)
|
|
@@ -10606,7 +9875,7 @@ function workspaceIndex(workspaceRoots) {
|
|
|
10606
9875
|
for (const id of entries) {
|
|
10607
9876
|
const dir = join12(root, id);
|
|
10608
9877
|
try {
|
|
10609
|
-
if (!
|
|
9878
|
+
if (!statSync8(dir).isDirectory())
|
|
10610
9879
|
continue;
|
|
10611
9880
|
} catch {
|
|
10612
9881
|
continue;
|
|
@@ -10952,7 +10221,7 @@ var init_cursor_ide = __esm({
|
|
|
10952
10221
|
});
|
|
10953
10222
|
|
|
10954
10223
|
// ../engine/dist/core/backends/cursor.js
|
|
10955
|
-
import { existsSync as existsSync11, readFileSync as readFileSync7, statSync as
|
|
10224
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7, statSync as statSync9 } from "fs";
|
|
10956
10225
|
import { join as join13 } from "path";
|
|
10957
10226
|
var PREFIX4, CursorBackend, cursorBackend;
|
|
10958
10227
|
var init_cursor = __esm({
|
|
@@ -11005,7 +10274,7 @@ var init_cursor = __esm({
|
|
|
11005
10274
|
if (r.surface === "cli") {
|
|
11006
10275
|
let mtime2 = r.chat.updatedAtMs;
|
|
11007
10276
|
try {
|
|
11008
|
-
mtime2 =
|
|
10277
|
+
mtime2 = statSync9(r.chat.dbPath).mtimeMs || mtime2;
|
|
11009
10278
|
} catch {
|
|
11010
10279
|
}
|
|
11011
10280
|
return {
|
|
@@ -11018,7 +10287,7 @@ var init_cursor = __esm({
|
|
|
11018
10287
|
}
|
|
11019
10288
|
let mtime = r.composer.updatedAtMs;
|
|
11020
10289
|
try {
|
|
11021
|
-
mtime =
|
|
10290
|
+
mtime = statSync9(r.composer.globalDbPath).mtimeMs || mtime;
|
|
11022
10291
|
} catch {
|
|
11023
10292
|
}
|
|
11024
10293
|
return {
|
|
@@ -11264,7 +10533,7 @@ var init_cursor = __esm({
|
|
|
11264
10533
|
if (existsSync11(metaPath)) {
|
|
11265
10534
|
files.push({ name: "meta.json", bytes: readFileSync7(metaPath) });
|
|
11266
10535
|
}
|
|
11267
|
-
return { tool: "cursor", mtime:
|
|
10536
|
+
return { tool: "cursor", mtime: statSync9(r.chat.dbPath).mtimeMs, files };
|
|
11268
10537
|
}
|
|
11269
10538
|
const events = readComposerEvents(r.composer);
|
|
11270
10539
|
if (events.length === 0)
|
|
@@ -11272,7 +10541,7 @@ var init_cursor = __esm({
|
|
|
11272
10541
|
const body = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
11273
10542
|
return {
|
|
11274
10543
|
tool: "cursor",
|
|
11275
|
-
mtime:
|
|
10544
|
+
mtime: statSync9(r.composer.globalDbPath).mtimeMs,
|
|
11276
10545
|
files: [{ name: `${r.composer.composerId}.jsonl`, bytes: Buffer.from(body, "utf8") }]
|
|
11277
10546
|
};
|
|
11278
10547
|
} catch {
|
|
@@ -11338,8 +10607,8 @@ var init_backends = __esm({
|
|
|
11338
10607
|
|
|
11339
10608
|
// ../engine/dist/transcript/shadow.js
|
|
11340
10609
|
import { gzipSync as gzipSync2, gunzipSync as gunzipSync2 } from "zlib";
|
|
11341
|
-
import { createHash as
|
|
11342
|
-
import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync2, renameSync as renameSync2, statSync as
|
|
10610
|
+
import { createHash as createHash2 } from "crypto";
|
|
10611
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync2, renameSync as renameSync2, statSync as statSync10 } from "fs";
|
|
11343
10612
|
import { dirname as dirname12, join as join14 } from "path";
|
|
11344
10613
|
function shadowRoot() {
|
|
11345
10614
|
return join14(getDataDir(), "shadow");
|
|
@@ -11369,7 +10638,7 @@ function writeShadowContainer(tool, rawId, container) {
|
|
|
11369
10638
|
renameSync2(tmp, path);
|
|
11370
10639
|
}
|
|
11371
10640
|
function sha1(s) {
|
|
11372
|
-
return
|
|
10641
|
+
return createHash2("sha1").update(s).digest("hex");
|
|
11373
10642
|
}
|
|
11374
10643
|
function lineKey(line) {
|
|
11375
10644
|
try {
|
|
@@ -11547,7 +10816,7 @@ function liveFingerprint(backend, rawId) {
|
|
|
11547
10816
|
const loc = backend.findSession(rawId);
|
|
11548
10817
|
if (!loc?.path)
|
|
11549
10818
|
return "";
|
|
11550
|
-
const st =
|
|
10819
|
+
const st = statSync10(loc.path);
|
|
11551
10820
|
return `${st.mtimeMs}:${st.size}`;
|
|
11552
10821
|
} catch {
|
|
11553
10822
|
return "";
|
|
@@ -11565,7 +10834,7 @@ function seedShadow(sessionId, container) {
|
|
|
11565
10834
|
}
|
|
11566
10835
|
function shadowSizeOnDisk(tool, rawId) {
|
|
11567
10836
|
try {
|
|
11568
|
-
return
|
|
10837
|
+
return statSync10(shadowFileFor(tool, rawId)).size;
|
|
11569
10838
|
} catch {
|
|
11570
10839
|
return 0;
|
|
11571
10840
|
}
|
|
@@ -11583,7 +10852,7 @@ var init_shadow = __esm({
|
|
|
11583
10852
|
});
|
|
11584
10853
|
|
|
11585
10854
|
// ../engine/dist/core/live-session-scan.js
|
|
11586
|
-
import { existsSync as existsSync13, readdirSync as readdirSync10, readFileSync as readFileSync9, statSync as
|
|
10855
|
+
import { existsSync as existsSync13, readdirSync as readdirSync10, readFileSync as readFileSync9, statSync as statSync11, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
|
|
11587
10856
|
import { join as join15, basename as basename9 } from "path";
|
|
11588
10857
|
function detectTool(sessionId) {
|
|
11589
10858
|
return getBackendForId(sessionId)?.id ?? "claude";
|
|
@@ -11675,20 +10944,33 @@ function resolveSessionContentGroups(sessionId) {
|
|
|
11675
10944
|
}
|
|
11676
10945
|
function withSessionReadCache(fn) {
|
|
11677
10946
|
readScopeDepth++;
|
|
10947
|
+
if (readScopeDepth === 1)
|
|
10948
|
+
derivedScope = /* @__PURE__ */ new Map();
|
|
11678
10949
|
try {
|
|
11679
10950
|
return fn();
|
|
11680
10951
|
} finally {
|
|
11681
|
-
if (--readScopeDepth === 0)
|
|
10952
|
+
if (--readScopeDepth === 0) {
|
|
11682
10953
|
readScope = null;
|
|
10954
|
+
derivedScope = null;
|
|
10955
|
+
}
|
|
11683
10956
|
}
|
|
11684
10957
|
}
|
|
10958
|
+
function memoInSessionScope(key2, compute) {
|
|
10959
|
+
if (!derivedScope)
|
|
10960
|
+
return compute();
|
|
10961
|
+
if (derivedScope.has(key2))
|
|
10962
|
+
return derivedScope.get(key2);
|
|
10963
|
+
const value = compute();
|
|
10964
|
+
derivedScope.set(key2, value);
|
|
10965
|
+
return value;
|
|
10966
|
+
}
|
|
11685
10967
|
function readSessionGroupText(group) {
|
|
11686
10968
|
let key2 = "";
|
|
11687
10969
|
if (readScopeDepth > 0) {
|
|
11688
10970
|
const parts = [];
|
|
11689
10971
|
for (const p of group.paths) {
|
|
11690
10972
|
try {
|
|
11691
|
-
const st =
|
|
10973
|
+
const st = statSync11(p);
|
|
11692
10974
|
parts.push(`${p}:${st.mtimeMs}:${st.size}`);
|
|
11693
10975
|
} catch {
|
|
11694
10976
|
parts.push(`${p}:missing`);
|
|
@@ -11708,7 +10990,7 @@ function readSessionGroupText(group) {
|
|
|
11708
10990
|
continue;
|
|
11709
10991
|
}
|
|
11710
10992
|
try {
|
|
11711
|
-
mtime = Math.max(mtime,
|
|
10993
|
+
mtime = Math.max(mtime, statSync11(p).mtimeMs);
|
|
11712
10994
|
} catch {
|
|
11713
10995
|
}
|
|
11714
10996
|
text = text ? mergeLineText(text, raw).text : raw;
|
|
@@ -11793,7 +11075,7 @@ function findCodexSessionFileIn(root, sessionId) {
|
|
|
11793
11075
|
}
|
|
11794
11076
|
return scanDir(root);
|
|
11795
11077
|
}
|
|
11796
|
-
var scanScopeDepth, indexCache, readScope, readScopeDepth, geminiCache, MAX_EVENT_LINE_BYTES;
|
|
11078
|
+
var scanScopeDepth, indexCache, readScope, readScopeDepth, derivedScope, geminiCache, MAX_EVENT_LINE_BYTES;
|
|
11797
11079
|
var init_live_session_scan = __esm({
|
|
11798
11080
|
"../engine/dist/core/live-session-scan.js"() {
|
|
11799
11081
|
"use strict";
|
|
@@ -11807,6 +11089,7 @@ var init_live_session_scan = __esm({
|
|
|
11807
11089
|
indexCache = null;
|
|
11808
11090
|
readScope = null;
|
|
11809
11091
|
readScopeDepth = 0;
|
|
11092
|
+
derivedScope = null;
|
|
11810
11093
|
geminiCache = null;
|
|
11811
11094
|
MAX_EVENT_LINE_BYTES = 8 * 1024 * 1024;
|
|
11812
11095
|
}
|
|
@@ -11924,7 +11207,7 @@ var init_first_prompt = __esm({
|
|
|
11924
11207
|
});
|
|
11925
11208
|
|
|
11926
11209
|
// ../engine/dist/core/backends/claude.js
|
|
11927
|
-
import { existsSync as existsSync15, readdirSync as readdirSync11, readFileSync as readFileSync10, statSync as
|
|
11210
|
+
import { existsSync as existsSync15, readdirSync as readdirSync11, readFileSync as readFileSync10, statSync as statSync12, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
|
|
11928
11211
|
import { join as join16, basename as basename10 } from "path";
|
|
11929
11212
|
function stripReminders(text) {
|
|
11930
11213
|
return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
|
|
@@ -11937,6 +11220,7 @@ var UUID_RE, ClaudeBackend, claudeBackend;
|
|
|
11937
11220
|
var init_claude2 = __esm({
|
|
11938
11221
|
"../engine/dist/core/backends/claude.js"() {
|
|
11939
11222
|
"use strict";
|
|
11223
|
+
init_live_session_scan();
|
|
11940
11224
|
init_tool_paths();
|
|
11941
11225
|
init_live_session_scan();
|
|
11942
11226
|
init_session_outcome();
|
|
@@ -12053,7 +11337,7 @@ var init_claude2 = __esm({
|
|
|
12053
11337
|
let mtime = 0;
|
|
12054
11338
|
for (const c of copies) {
|
|
12055
11339
|
try {
|
|
12056
|
-
mtime = Math.max(mtime,
|
|
11340
|
+
mtime = Math.max(mtime, statSync12(c.path).mtimeMs);
|
|
12057
11341
|
} catch {
|
|
12058
11342
|
}
|
|
12059
11343
|
}
|
|
@@ -12129,7 +11413,7 @@ var init_claude2 = __esm({
|
|
|
12129
11413
|
const fullPath = join16(projPath, f);
|
|
12130
11414
|
let stat3;
|
|
12131
11415
|
try {
|
|
12132
|
-
stat3 =
|
|
11416
|
+
stat3 = statSync12(fullPath);
|
|
12133
11417
|
} catch {
|
|
12134
11418
|
continue;
|
|
12135
11419
|
}
|
|
@@ -12199,6 +11483,9 @@ var init_claude2 = __esm({
|
|
|
12199
11483
|
* splits show up alongside the parent.
|
|
12200
11484
|
*/
|
|
12201
11485
|
readEvents(rawId) {
|
|
11486
|
+
return memoInSessionScope(`events:claude:${rawId}`, () => this.#readEventsUncached(rawId));
|
|
11487
|
+
}
|
|
11488
|
+
#readEventsUncached(rawId) {
|
|
12202
11489
|
const groups = resolveSessionContentGroups(rawId);
|
|
12203
11490
|
if (groups.length === 0)
|
|
12204
11491
|
return [];
|
|
@@ -12347,7 +11634,7 @@ var init_claude2 = __esm({
|
|
|
12347
11634
|
const events = this.readEvents(rawId);
|
|
12348
11635
|
let fileMtime = 0;
|
|
12349
11636
|
try {
|
|
12350
|
-
fileMtime =
|
|
11637
|
+
fileMtime = statSync12(located.path).mtimeMs;
|
|
12351
11638
|
} catch {
|
|
12352
11639
|
}
|
|
12353
11640
|
return liveScanEditsFromEvents(events, this.fileToolMap, {
|
|
@@ -12402,13 +11689,13 @@ var init_claude2 = __esm({
|
|
|
12402
11689
|
continue;
|
|
12403
11690
|
const fp = join16(projPath, f);
|
|
12404
11691
|
try {
|
|
12405
|
-
const st =
|
|
11692
|
+
const st = statSync12(fp);
|
|
12406
11693
|
let mtime = st.mtimeMs;
|
|
12407
11694
|
const subDir = join16(projPath, f.slice(0, -6), "subagents");
|
|
12408
11695
|
if (existsSync15(subDir)) {
|
|
12409
11696
|
for (const sub of readdirSync11(subDir)) {
|
|
12410
11697
|
try {
|
|
12411
|
-
const subSt =
|
|
11698
|
+
const subSt = statSync12(join16(subDir, sub));
|
|
12412
11699
|
if (subSt.mtimeMs > mtime)
|
|
12413
11700
|
mtime = subSt.mtimeMs;
|
|
12414
11701
|
} catch {
|
|
@@ -12473,7 +11760,7 @@ var init_claude2 = __esm({
|
|
|
12473
11760
|
let total = 0;
|
|
12474
11761
|
for (const located of findSessionFiles(this.toRawId(prefixedId))) {
|
|
12475
11762
|
try {
|
|
12476
|
-
total +=
|
|
11763
|
+
total += statSync12(located.path).size;
|
|
12477
11764
|
} catch {
|
|
12478
11765
|
}
|
|
12479
11766
|
}
|
|
@@ -12495,417 +11782,877 @@ var init_claude2 = __esm({
|
|
|
12495
11782
|
spansMultipleSources(prefixedId) {
|
|
12496
11783
|
return findSessionFiles(this.toRawId(prefixedId)).length > 1;
|
|
12497
11784
|
}
|
|
12498
|
-
async readFromOffset(prefixedId, offset) {
|
|
12499
|
-
const rawId = this.toRawId(prefixedId);
|
|
12500
|
-
const copies = findSessionFiles(rawId);
|
|
12501
|
-
if (copies.length === 0)
|
|
12502
|
-
return { text: "", newOffset: offset };
|
|
12503
|
-
if (copies.length > 1)
|
|
12504
|
-
return { text: "", newOffset: offset };
|
|
12505
|
-
return readTailFromOffset(copies[0].path, offset);
|
|
11785
|
+
async readFromOffset(prefixedId, offset) {
|
|
11786
|
+
const rawId = this.toRawId(prefixedId);
|
|
11787
|
+
const copies = findSessionFiles(rawId);
|
|
11788
|
+
if (copies.length === 0)
|
|
11789
|
+
return { text: "", newOffset: offset };
|
|
11790
|
+
if (copies.length > 1)
|
|
11791
|
+
return { text: "", newOffset: offset };
|
|
11792
|
+
return readTailFromOffset(copies[0].path, offset);
|
|
11793
|
+
}
|
|
11794
|
+
};
|
|
11795
|
+
claudeBackend = new ClaudeBackend();
|
|
11796
|
+
}
|
|
11797
|
+
});
|
|
11798
|
+
|
|
11799
|
+
// ../engine/dist/core/project-resolver.js
|
|
11800
|
+
var project_resolver_exports = {};
|
|
11801
|
+
__export(project_resolver_exports, {
|
|
11802
|
+
_setProjectsConfigForTests: () => _setProjectsConfigForTests,
|
|
11803
|
+
getProjectsConfigPath: () => getProjectsConfigPath,
|
|
11804
|
+
globMatch: () => globMatch,
|
|
11805
|
+
loadProjectsConfig: () => loadProjectsConfig,
|
|
11806
|
+
parseGitRemote: () => parseGitRemote,
|
|
11807
|
+
resetProjectResolverCache: () => resetProjectResolverCache,
|
|
11808
|
+
resolveProjectId: () => resolveProjectId,
|
|
11809
|
+
resolveWorkspaceId: () => resolveWorkspaceId
|
|
11810
|
+
});
|
|
11811
|
+
import { execSync } from "child_process";
|
|
11812
|
+
import { createHash as createHash3 } from "crypto";
|
|
11813
|
+
import { existsSync as existsSync16, readFileSync as readFileSync11, realpathSync as realpathSync2, readdirSync as readdirSync12, statSync as statSync13 } from "fs";
|
|
11814
|
+
import { basename as basename11, dirname as dirname13, isAbsolute, join as join17, resolve, sep } from "path";
|
|
11815
|
+
function getProjectsConfigPath() {
|
|
11816
|
+
return join17(getDataDir(), "projects.json");
|
|
11817
|
+
}
|
|
11818
|
+
function loadProjectsConfig() {
|
|
11819
|
+
if (cachedConfig && cachedConfig.mtime === Number.MAX_SAFE_INTEGER) {
|
|
11820
|
+
return cachedConfig.cfg;
|
|
11821
|
+
}
|
|
11822
|
+
const path = getProjectsConfigPath();
|
|
11823
|
+
if (!existsSync16(path)) {
|
|
11824
|
+
cachedConfig = { mtime: 0, cfg: {} };
|
|
11825
|
+
return cachedConfig.cfg;
|
|
11826
|
+
}
|
|
11827
|
+
const mtime = statSync13(path).mtimeMs;
|
|
11828
|
+
if (cachedConfig && cachedConfig.mtime === mtime)
|
|
11829
|
+
return cachedConfig.cfg;
|
|
11830
|
+
try {
|
|
11831
|
+
const raw = readFileSync11(path, "utf-8");
|
|
11832
|
+
const cfg = JSON.parse(raw);
|
|
11833
|
+
cachedConfig = { mtime, cfg };
|
|
11834
|
+
return cfg;
|
|
11835
|
+
} catch {
|
|
11836
|
+
cachedConfig = { mtime, cfg: {} };
|
|
11837
|
+
return cachedConfig.cfg;
|
|
11838
|
+
}
|
|
11839
|
+
}
|
|
11840
|
+
function resetProjectResolverCache() {
|
|
11841
|
+
resolveCache.clear();
|
|
11842
|
+
gitCache.clear();
|
|
11843
|
+
workspaceCache.clear();
|
|
11844
|
+
wsIdCache.clear();
|
|
11845
|
+
cachedConfig = null;
|
|
11846
|
+
}
|
|
11847
|
+
function _setProjectsConfigForTests(cfg) {
|
|
11848
|
+
resetProjectResolverCache();
|
|
11849
|
+
cachedConfig = cfg === null ? null : { mtime: Number.MAX_SAFE_INTEGER, cfg };
|
|
11850
|
+
}
|
|
11851
|
+
function resolveProjectId(inputPath) {
|
|
11852
|
+
if (!inputPath) {
|
|
11853
|
+
return { id: "", displayName: "", source: "path" };
|
|
11854
|
+
}
|
|
11855
|
+
if (!isAbsolute(inputPath)) {
|
|
11856
|
+
return { id: `path:${inputPath}`, displayName: inputPath, source: "path" };
|
|
11857
|
+
}
|
|
11858
|
+
const realPath = safeRealpath(inputPath);
|
|
11859
|
+
const cached = resolveCache.get(realPath);
|
|
11860
|
+
if (cached)
|
|
11861
|
+
return cached;
|
|
11862
|
+
const result = doResolve(realPath);
|
|
11863
|
+
resolveCache.set(realPath, result);
|
|
11864
|
+
return result;
|
|
11865
|
+
}
|
|
11866
|
+
function resolveWorkspaceId(inputPath) {
|
|
11867
|
+
if (!inputPath || !isAbsolute(inputPath))
|
|
11868
|
+
return null;
|
|
11869
|
+
const realPath = safeRealpath(inputPath);
|
|
11870
|
+
const cached = wsIdCache.get(realPath);
|
|
11871
|
+
if (cached !== void 0)
|
|
11872
|
+
return cached;
|
|
11873
|
+
const result = doResolveWorkspace(realPath);
|
|
11874
|
+
wsIdCache.set(realPath, result);
|
|
11875
|
+
return result;
|
|
11876
|
+
}
|
|
11877
|
+
function doResolveWorkspace(realPath) {
|
|
11878
|
+
const cfg = loadProjectsConfig();
|
|
11879
|
+
const declaredWorkspaces = (cfg.projects || []).filter((p) => p.workspace).sort((a, b) => b.root.length - a.root.length);
|
|
11880
|
+
for (const proj of declaredWorkspaces) {
|
|
11881
|
+
if (isInside(realPath, safeRealpath(proj.root)))
|
|
11882
|
+
return proj.id;
|
|
11883
|
+
}
|
|
11884
|
+
const anchor = findGitToplevel(realPath) ?? realPath;
|
|
11885
|
+
const ws = resolveAutoWorkspace(anchor, cfg.autoWorkspaceMinRepos ?? DEFAULT_AUTO_WORKSPACE_MIN_REPOS);
|
|
11886
|
+
return ws ? ws.id : null;
|
|
11887
|
+
}
|
|
11888
|
+
function doResolve(realPath) {
|
|
11889
|
+
const cfg = loadProjectsConfig();
|
|
11890
|
+
if (matchesIgnore(realPath, cfg)) {
|
|
11891
|
+
return { id: `ignored:${realPath}`, displayName: basename11(realPath), source: "ignored" };
|
|
11892
|
+
}
|
|
11893
|
+
const declared = matchDeclaredProject(realPath, cfg.projects || []);
|
|
11894
|
+
if (declared)
|
|
11895
|
+
return declared;
|
|
11896
|
+
const bot = resolveBotWorktree(realPath);
|
|
11897
|
+
if (bot)
|
|
11898
|
+
return bot;
|
|
11899
|
+
const git2 = resolveGit(realPath);
|
|
11900
|
+
if (git2)
|
|
11901
|
+
return git2;
|
|
11902
|
+
const ws = resolveAutoWorkspace(realPath, cfg.autoWorkspaceMinRepos ?? DEFAULT_AUTO_WORKSPACE_MIN_REPOS);
|
|
11903
|
+
if (ws)
|
|
11904
|
+
return ws;
|
|
11905
|
+
return {
|
|
11906
|
+
id: `path:${realPath}`,
|
|
11907
|
+
displayName: basename11(realPath) || realPath,
|
|
11908
|
+
source: "path"
|
|
11909
|
+
};
|
|
11910
|
+
}
|
|
11911
|
+
function matchesIgnore(realPath, cfg) {
|
|
11912
|
+
const rules = cfg.ignore || [];
|
|
11913
|
+
for (const r of rules) {
|
|
11914
|
+
if (globMatch(r.match, realPath))
|
|
11915
|
+
return true;
|
|
11916
|
+
}
|
|
11917
|
+
return false;
|
|
11918
|
+
}
|
|
11919
|
+
function resolveBotWorktree(realPath) {
|
|
11920
|
+
const parts = realPath.split(sep);
|
|
11921
|
+
const idx = parts.findIndex((p) => BOT_WORKTREE_MARKERS.includes(p));
|
|
11922
|
+
if (idx === -1)
|
|
11923
|
+
return null;
|
|
11924
|
+
const botRoot = parts.slice(0, idx + 1).join(sep);
|
|
11925
|
+
return {
|
|
11926
|
+
id: `path:${botRoot}`,
|
|
11927
|
+
displayName: parts[idx],
|
|
11928
|
+
source: "path"
|
|
11929
|
+
};
|
|
11930
|
+
}
|
|
11931
|
+
function matchDeclaredProject(realPath, projects) {
|
|
11932
|
+
const candidates2 = [...projects].sort((a, b) => b.root.length - a.root.length);
|
|
11933
|
+
for (const proj of candidates2) {
|
|
11934
|
+
const root = safeRealpath(proj.root);
|
|
11935
|
+
if (!isInside(realPath, root))
|
|
11936
|
+
continue;
|
|
11937
|
+
if (!proj.workspace && proj.children?.length) {
|
|
11938
|
+
const child = matchChild(realPath, root, proj.children);
|
|
11939
|
+
if (child) {
|
|
11940
|
+
return {
|
|
11941
|
+
id: `${proj.id}/${child.id}`,
|
|
11942
|
+
displayName: child.name || child.id,
|
|
11943
|
+
source: "user",
|
|
11944
|
+
workspaceId: proj.id
|
|
11945
|
+
};
|
|
11946
|
+
}
|
|
11947
|
+
}
|
|
11948
|
+
return {
|
|
11949
|
+
id: proj.id,
|
|
11950
|
+
displayName: proj.name || proj.id,
|
|
11951
|
+
source: "user",
|
|
11952
|
+
workspaceId: proj.workspace ? void 0 : void 0
|
|
11953
|
+
};
|
|
11954
|
+
}
|
|
11955
|
+
return null;
|
|
11956
|
+
}
|
|
11957
|
+
function matchChild(realPath, parentRoot, children) {
|
|
11958
|
+
const sorted = [...children].sort((a, b) => b.path.length - a.path.length);
|
|
11959
|
+
for (const c of sorted) {
|
|
11960
|
+
const childRoot = safeRealpath(join17(parentRoot, c.path));
|
|
11961
|
+
if (isInside(realPath, childRoot))
|
|
11962
|
+
return c;
|
|
11963
|
+
}
|
|
11964
|
+
return null;
|
|
11965
|
+
}
|
|
11966
|
+
function resolveGit(realPath) {
|
|
11967
|
+
const toplevel = findGitToplevel(realPath);
|
|
11968
|
+
if (!toplevel)
|
|
11969
|
+
return null;
|
|
11970
|
+
const probe = getGitRemote(toplevel);
|
|
11971
|
+
if (probe.kind === "remote") {
|
|
11972
|
+
const parsed = parseGitRemote(probe.url);
|
|
11973
|
+
if (parsed) {
|
|
11974
|
+
return {
|
|
11975
|
+
id: `git:${parsed.host}/${parsed.owner}/${parsed.repo}`,
|
|
11976
|
+
displayName: parsed.repo,
|
|
11977
|
+
source: "git-remote"
|
|
11978
|
+
};
|
|
11979
|
+
}
|
|
11980
|
+
}
|
|
11981
|
+
if (probe.kind === "failed") {
|
|
11982
|
+
console.error(`[project] could not read git remotes in ${toplevel} \u2014 filing it as a local-only repo for now, which is probably wrong: ${probe.reason}`);
|
|
11983
|
+
}
|
|
11984
|
+
const hash = createHash3("sha1").update(toplevel).digest("hex").slice(0, 12);
|
|
11985
|
+
return {
|
|
11986
|
+
id: `git-local:${hash}`,
|
|
11987
|
+
displayName: basename11(toplevel),
|
|
11988
|
+
source: "git-local"
|
|
11989
|
+
};
|
|
11990
|
+
}
|
|
11991
|
+
function findGitToplevel(realPath) {
|
|
11992
|
+
let cur = realPath;
|
|
11993
|
+
let last = "";
|
|
11994
|
+
while (cur && cur !== last) {
|
|
11995
|
+
if (existsSync16(join17(cur, ".git")))
|
|
11996
|
+
return cur;
|
|
11997
|
+
last = cur;
|
|
11998
|
+
cur = dirname13(cur);
|
|
11999
|
+
}
|
|
12000
|
+
return null;
|
|
12001
|
+
}
|
|
12002
|
+
function git(toplevel, args) {
|
|
12003
|
+
return execSync(`git ${args}`, {
|
|
12004
|
+
cwd: toplevel,
|
|
12005
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
12006
|
+
encoding: "utf-8",
|
|
12007
|
+
timeout: GIT_TIMEOUT_MS
|
|
12008
|
+
});
|
|
12009
|
+
}
|
|
12010
|
+
function probeGitRemote(toplevel) {
|
|
12011
|
+
let names;
|
|
12012
|
+
try {
|
|
12013
|
+
names = git(toplevel, "remote").split("\n").map((n) => n.trim()).filter(Boolean);
|
|
12014
|
+
} catch (err) {
|
|
12015
|
+
return { kind: "failed", reason: err instanceof Error ? err.message : String(err) };
|
|
12016
|
+
}
|
|
12017
|
+
if (names.length === 0)
|
|
12018
|
+
return { kind: "none" };
|
|
12019
|
+
const pick = names.includes("origin") ? "origin" : names[0];
|
|
12020
|
+
try {
|
|
12021
|
+
const url = git(toplevel, `remote get-url ${pick}`).trim();
|
|
12022
|
+
return url ? { kind: "remote", url } : { kind: "none" };
|
|
12023
|
+
} catch (err) {
|
|
12024
|
+
return { kind: "failed", reason: err instanceof Error ? err.message : String(err) };
|
|
12025
|
+
}
|
|
12026
|
+
}
|
|
12027
|
+
function getGitRemote(toplevel) {
|
|
12028
|
+
const cached = gitCache.get(toplevel);
|
|
12029
|
+
if (cached)
|
|
12030
|
+
return cached;
|
|
12031
|
+
const probe = probeGitRemote(toplevel);
|
|
12032
|
+
if (probe.kind !== "failed")
|
|
12033
|
+
gitCache.set(toplevel, probe);
|
|
12034
|
+
return probe;
|
|
12035
|
+
}
|
|
12036
|
+
function parseGitRemote(url) {
|
|
12037
|
+
const trimmed = url.trim().replace(/\.git$/, "");
|
|
12038
|
+
if (!trimmed)
|
|
12039
|
+
return null;
|
|
12040
|
+
const ssh = /^[^@]+@([^:]+):(.+?)\/([^/]+)$/.exec(trimmed);
|
|
12041
|
+
if (ssh)
|
|
12042
|
+
return { host: normaliseHost(ssh[1]), owner: ssh[2], repo: ssh[3] };
|
|
12043
|
+
const url1 = /^[a-z+]+:\/\/(?:[^@]+@)?([^/:]+)(?::\d+)?\/(.+?)\/([^/]+)$/.exec(trimmed);
|
|
12044
|
+
if (url1)
|
|
12045
|
+
return { host: normaliseHost(url1[1]), owner: url1[2], repo: url1[3] };
|
|
12046
|
+
return null;
|
|
12047
|
+
}
|
|
12048
|
+
function normaliseHost(host) {
|
|
12049
|
+
for (const known of ["github.com", "gitlab.com", "bitbucket.org", "codeberg.org", "gitea.com"]) {
|
|
12050
|
+
if (host === known)
|
|
12051
|
+
return known;
|
|
12052
|
+
if (host.startsWith(known + "-") || host.startsWith(known + "."))
|
|
12053
|
+
return known;
|
|
12054
|
+
}
|
|
12055
|
+
return host;
|
|
12056
|
+
}
|
|
12057
|
+
function resolveAutoWorkspace(realPath, minRepos) {
|
|
12058
|
+
let cur = realPath;
|
|
12059
|
+
let last = "";
|
|
12060
|
+
let depth = 0;
|
|
12061
|
+
while (cur && cur !== last && depth < AUTO_WORKSPACE_MAX_DEPTH) {
|
|
12062
|
+
if (!existsSync16(join17(cur, ".git"))) {
|
|
12063
|
+
const cached = workspaceCache.get(cur);
|
|
12064
|
+
let qualifies;
|
|
12065
|
+
if (cached === void 0) {
|
|
12066
|
+
qualifies = countGitChildren(cur) >= minRepos;
|
|
12067
|
+
workspaceCache.set(cur, qualifies ? cur : null);
|
|
12068
|
+
} else {
|
|
12069
|
+
qualifies = cached !== null;
|
|
12070
|
+
}
|
|
12071
|
+
if (qualifies) {
|
|
12072
|
+
const name = basename11(cur);
|
|
12073
|
+
return { id: `ws:${name}`, displayName: name, source: "auto-workspace" };
|
|
12506
12074
|
}
|
|
12507
|
-
}
|
|
12508
|
-
|
|
12075
|
+
}
|
|
12076
|
+
last = cur;
|
|
12077
|
+
cur = dirname13(cur);
|
|
12078
|
+
depth++;
|
|
12509
12079
|
}
|
|
12510
|
-
|
|
12511
|
-
|
|
12512
|
-
// ../engine/dist/core/project-resolver.js
|
|
12513
|
-
var project_resolver_exports = {};
|
|
12514
|
-
__export(project_resolver_exports, {
|
|
12515
|
-
_setProjectsConfigForTests: () => _setProjectsConfigForTests,
|
|
12516
|
-
getProjectsConfigPath: () => getProjectsConfigPath,
|
|
12517
|
-
globMatch: () => globMatch,
|
|
12518
|
-
loadProjectsConfig: () => loadProjectsConfig,
|
|
12519
|
-
parseGitRemote: () => parseGitRemote,
|
|
12520
|
-
resetProjectResolverCache: () => resetProjectResolverCache,
|
|
12521
|
-
resolveProjectId: () => resolveProjectId,
|
|
12522
|
-
resolveWorkspaceId: () => resolveWorkspaceId
|
|
12523
|
-
});
|
|
12524
|
-
import { execSync } from "child_process";
|
|
12525
|
-
import { createHash as createHash4 } from "crypto";
|
|
12526
|
-
import { existsSync as existsSync16, readFileSync as readFileSync11, realpathSync as realpathSync2, readdirSync as readdirSync12, statSync as statSync14 } from "fs";
|
|
12527
|
-
import { basename as basename11, dirname as dirname13, isAbsolute, join as join17, resolve, sep } from "path";
|
|
12528
|
-
function getProjectsConfigPath() {
|
|
12529
|
-
return join17(getDataDir(), "projects.json");
|
|
12080
|
+
return null;
|
|
12530
12081
|
}
|
|
12531
|
-
function
|
|
12532
|
-
|
|
12533
|
-
|
|
12082
|
+
function countGitChildren(dir) {
|
|
12083
|
+
let count = 0;
|
|
12084
|
+
let entries;
|
|
12085
|
+
try {
|
|
12086
|
+
entries = readdirSync12(dir);
|
|
12087
|
+
} catch {
|
|
12088
|
+
return 0;
|
|
12534
12089
|
}
|
|
12535
|
-
const
|
|
12536
|
-
|
|
12537
|
-
|
|
12538
|
-
|
|
12090
|
+
for (const name of entries) {
|
|
12091
|
+
if (name.startsWith("."))
|
|
12092
|
+
continue;
|
|
12093
|
+
const child = join17(dir, name);
|
|
12094
|
+
try {
|
|
12095
|
+
const st = statSync13(child);
|
|
12096
|
+
if (!st.isDirectory())
|
|
12097
|
+
continue;
|
|
12098
|
+
if (existsSync16(join17(child, ".git")))
|
|
12099
|
+
count++;
|
|
12100
|
+
} catch {
|
|
12101
|
+
}
|
|
12539
12102
|
}
|
|
12540
|
-
|
|
12541
|
-
|
|
12542
|
-
|
|
12103
|
+
return count;
|
|
12104
|
+
}
|
|
12105
|
+
function safeRealpath(p) {
|
|
12543
12106
|
try {
|
|
12544
|
-
|
|
12545
|
-
const cfg = JSON.parse(raw);
|
|
12546
|
-
cachedConfig = { mtime, cfg };
|
|
12547
|
-
return cfg;
|
|
12107
|
+
return realpathSync2(p);
|
|
12548
12108
|
} catch {
|
|
12549
|
-
|
|
12550
|
-
return cachedConfig.cfg;
|
|
12109
|
+
return resolve(p);
|
|
12551
12110
|
}
|
|
12552
12111
|
}
|
|
12553
|
-
function
|
|
12554
|
-
|
|
12555
|
-
|
|
12556
|
-
|
|
12557
|
-
|
|
12558
|
-
|
|
12112
|
+
function isInside(child, parent) {
|
|
12113
|
+
if (!parent)
|
|
12114
|
+
return false;
|
|
12115
|
+
if (child === parent)
|
|
12116
|
+
return true;
|
|
12117
|
+
const c = child.endsWith(sep) ? child : child + sep;
|
|
12118
|
+
const p = parent.endsWith(sep) ? parent : parent + sep;
|
|
12119
|
+
return c.startsWith(p);
|
|
12559
12120
|
}
|
|
12560
|
-
function
|
|
12561
|
-
|
|
12562
|
-
|
|
12121
|
+
function globMatch(pattern, path) {
|
|
12122
|
+
const re = new RegExp("^" + globToRegex(toPosix(pattern)) + "$");
|
|
12123
|
+
return re.test(toPosix(path));
|
|
12563
12124
|
}
|
|
12564
|
-
function
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
|
|
12125
|
+
function toPosix(p) {
|
|
12126
|
+
return p.replace(/\\/g, "/");
|
|
12127
|
+
}
|
|
12128
|
+
function globToRegex(glob) {
|
|
12129
|
+
let out = "";
|
|
12130
|
+
let i = 0;
|
|
12131
|
+
while (i < glob.length) {
|
|
12132
|
+
const c = glob[i];
|
|
12133
|
+
if (c === "*" && glob[i + 1] === "*") {
|
|
12134
|
+
out += ".*";
|
|
12135
|
+
i += 2;
|
|
12136
|
+
if (glob[i] === "/")
|
|
12137
|
+
i++;
|
|
12138
|
+
} else if (c === "*") {
|
|
12139
|
+
out += "[^/]*";
|
|
12140
|
+
i++;
|
|
12141
|
+
} else if (c === "?") {
|
|
12142
|
+
out += "[^/]";
|
|
12143
|
+
i++;
|
|
12144
|
+
} else if (c === "{") {
|
|
12145
|
+
const end = glob.indexOf("}", i);
|
|
12146
|
+
if (end === -1) {
|
|
12147
|
+
out += "\\{";
|
|
12148
|
+
i++;
|
|
12149
|
+
continue;
|
|
12150
|
+
}
|
|
12151
|
+
const opts = glob.slice(i + 1, end).split(",").map(escapeRe).join("|");
|
|
12152
|
+
out += "(?:" + opts + ")";
|
|
12153
|
+
i = end + 1;
|
|
12154
|
+
} else {
|
|
12155
|
+
out += escapeRe(c);
|
|
12156
|
+
i++;
|
|
12157
|
+
}
|
|
12570
12158
|
}
|
|
12571
|
-
|
|
12572
|
-
const cached = resolveCache.get(realPath);
|
|
12573
|
-
if (cached)
|
|
12574
|
-
return cached;
|
|
12575
|
-
const result = doResolve(realPath);
|
|
12576
|
-
resolveCache.set(realPath, result);
|
|
12577
|
-
return result;
|
|
12159
|
+
return out;
|
|
12578
12160
|
}
|
|
12579
|
-
function
|
|
12580
|
-
|
|
12161
|
+
function escapeRe(s) {
|
|
12162
|
+
return s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
12163
|
+
}
|
|
12164
|
+
var DEFAULT_AUTO_WORKSPACE_MIN_REPOS, BOT_WORKTREE_MARKERS, resolveCache, gitCache, workspaceCache, wsIdCache, cachedConfig, GIT_TIMEOUT_MS, AUTO_WORKSPACE_MAX_DEPTH;
|
|
12165
|
+
var init_project_resolver = __esm({
|
|
12166
|
+
"../engine/dist/core/project-resolver.js"() {
|
|
12167
|
+
"use strict";
|
|
12168
|
+
init_paths();
|
|
12169
|
+
DEFAULT_AUTO_WORKSPACE_MIN_REPOS = 3;
|
|
12170
|
+
BOT_WORKTREE_MARKERS = [".claude-pr-bot"];
|
|
12171
|
+
resolveCache = /* @__PURE__ */ new Map();
|
|
12172
|
+
gitCache = /* @__PURE__ */ new Map();
|
|
12173
|
+
workspaceCache = /* @__PURE__ */ new Map();
|
|
12174
|
+
wsIdCache = /* @__PURE__ */ new Map();
|
|
12175
|
+
cachedConfig = null;
|
|
12176
|
+
GIT_TIMEOUT_MS = 1e4;
|
|
12177
|
+
AUTO_WORKSPACE_MAX_DEPTH = 2;
|
|
12178
|
+
}
|
|
12179
|
+
});
|
|
12180
|
+
|
|
12181
|
+
// ../engine/dist/core/credentials-env.js
|
|
12182
|
+
function envCredentials(env = process.env) {
|
|
12183
|
+
const token = (env.CHAT_RECALL_TOKEN || "").trim();
|
|
12184
|
+
const server = (env.CHAT_RECALL_SERVER || "").trim();
|
|
12185
|
+
if (!token || !server)
|
|
12581
12186
|
return null;
|
|
12582
|
-
|
|
12583
|
-
|
|
12584
|
-
|
|
12585
|
-
return cached;
|
|
12586
|
-
const result = doResolveWorkspace(realPath);
|
|
12587
|
-
wsIdCache.set(realPath, result);
|
|
12588
|
-
return result;
|
|
12187
|
+
if (!/^https?:\/\//i.test(server))
|
|
12188
|
+
return null;
|
|
12189
|
+
return { serverUrl: normalise(server), token };
|
|
12589
12190
|
}
|
|
12590
|
-
|
|
12591
|
-
|
|
12592
|
-
|
|
12593
|
-
|
|
12594
|
-
|
|
12595
|
-
return proj.id;
|
|
12191
|
+
var normalise;
|
|
12192
|
+
var init_credentials_env = __esm({
|
|
12193
|
+
"../engine/dist/core/credentials-env.js"() {
|
|
12194
|
+
"use strict";
|
|
12195
|
+
normalise = (u) => u.trim().replace(/\/+$/, "");
|
|
12596
12196
|
}
|
|
12597
|
-
|
|
12598
|
-
|
|
12599
|
-
|
|
12197
|
+
});
|
|
12198
|
+
|
|
12199
|
+
// ../engine/dist/core/secret-redactor.js
|
|
12200
|
+
var secret_redactor_exports = {};
|
|
12201
|
+
__export(secret_redactor_exports, {
|
|
12202
|
+
DEFAULT_REDACTION_RULES: () => DEFAULT_REDACTION_RULES,
|
|
12203
|
+
_clearServerRulePack: () => _clearServerRulePack,
|
|
12204
|
+
_resetPrivacyCache: () => _resetPrivacyCache,
|
|
12205
|
+
_resetRedactorCache: () => _resetRedactorCache,
|
|
12206
|
+
applyChunkPrivacy: () => applyChunkPrivacy,
|
|
12207
|
+
findContextualSecrets: () => findContextualSecrets,
|
|
12208
|
+
installServerRulePack: () => installServerRulePack,
|
|
12209
|
+
isFilePathRedactionEnabled: () => isFilePathRedactionEnabled,
|
|
12210
|
+
isRedactionEnabled: () => isRedactionEnabled,
|
|
12211
|
+
isToolOutputRedactionEnabled: () => isToolOutputRedactionEnabled,
|
|
12212
|
+
redactFilePaths: () => redactFilePaths,
|
|
12213
|
+
redactSecrets: () => redactSecrets,
|
|
12214
|
+
redactToolResultBody: () => redactToolResultBody,
|
|
12215
|
+
scanTextForFindings: () => scanTextForFindings,
|
|
12216
|
+
serverRulePackVersion: () => serverRulePackVersion,
|
|
12217
|
+
validateRedactionRule: () => validateRedactionRule
|
|
12218
|
+
});
|
|
12219
|
+
import { statSync as statSync14 } from "fs";
|
|
12220
|
+
import { createHash as createHash4 } from "crypto";
|
|
12221
|
+
function secretOf(rule, m) {
|
|
12222
|
+
const raw = rule.secret != null ? m[rule.secret] : m[0];
|
|
12223
|
+
if (raw == null)
|
|
12224
|
+
return null;
|
|
12225
|
+
if (rule.where && !rule.where(raw))
|
|
12226
|
+
return null;
|
|
12227
|
+
return raw;
|
|
12600
12228
|
}
|
|
12601
|
-
function
|
|
12602
|
-
|
|
12603
|
-
|
|
12604
|
-
|
|
12605
|
-
|
|
12606
|
-
|
|
12607
|
-
if (declared)
|
|
12608
|
-
return declared;
|
|
12609
|
-
const bot = resolveBotWorktree(realPath);
|
|
12610
|
-
if (bot)
|
|
12611
|
-
return bot;
|
|
12612
|
-
const git2 = resolveGit(realPath);
|
|
12613
|
-
if (git2)
|
|
12614
|
-
return git2;
|
|
12615
|
-
const ws = resolveAutoWorkspace(realPath, cfg.autoWorkspaceMinRepos ?? DEFAULT_AUTO_WORKSPACE_MIN_REPOS);
|
|
12616
|
-
if (ws)
|
|
12617
|
-
return ws;
|
|
12618
|
-
return {
|
|
12619
|
-
id: `path:${realPath}`,
|
|
12620
|
-
displayName: basename11(realPath) || realPath,
|
|
12621
|
-
source: "path"
|
|
12622
|
-
};
|
|
12229
|
+
function looksLikeSecretToken(t) {
|
|
12230
|
+
if (t.length < 32)
|
|
12231
|
+
return false;
|
|
12232
|
+
if (/^[a-f0-9]+$/i.test(t))
|
|
12233
|
+
return true;
|
|
12234
|
+
return /[A-Z]/.test(t) && /[a-z]/.test(t) && /[0-9]/.test(t);
|
|
12623
12235
|
}
|
|
12624
|
-
function
|
|
12625
|
-
|
|
12626
|
-
|
|
12627
|
-
|
|
12628
|
-
|
|
12236
|
+
function findContextualSecrets(text) {
|
|
12237
|
+
if (!text)
|
|
12238
|
+
return [];
|
|
12239
|
+
const ranges = [];
|
|
12240
|
+
let m;
|
|
12241
|
+
SECRET_CONTEXT_RE.lastIndex = 0;
|
|
12242
|
+
while ((m = SECRET_CONTEXT_RE.exec(text)) !== null) {
|
|
12243
|
+
const winStart = m.index + m[0].length;
|
|
12244
|
+
const window = text.slice(winStart, winStart + 80);
|
|
12245
|
+
HIGH_ENTROPY_TOKEN_RE.lastIndex = 0;
|
|
12246
|
+
let tm;
|
|
12247
|
+
while ((tm = HIGH_ENTROPY_TOKEN_RE.exec(window)) !== null) {
|
|
12248
|
+
if (!looksLikeSecretToken(tm[0]))
|
|
12249
|
+
continue;
|
|
12250
|
+
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))) {
|
|
12251
|
+
continue;
|
|
12252
|
+
}
|
|
12253
|
+
ranges.push({ start: winStart + tm.index, end: winStart + tm.index + tm[0].length });
|
|
12254
|
+
break;
|
|
12255
|
+
}
|
|
12629
12256
|
}
|
|
12630
|
-
|
|
12257
|
+
ranges.sort((a, b) => a.start - b.start);
|
|
12258
|
+
const out = [];
|
|
12259
|
+
for (const r of ranges) {
|
|
12260
|
+
const last = out[out.length - 1];
|
|
12261
|
+
if (last && r.start < last.end) {
|
|
12262
|
+
last.end = Math.max(last.end, r.end);
|
|
12263
|
+
continue;
|
|
12264
|
+
}
|
|
12265
|
+
out.push({ ...r });
|
|
12266
|
+
}
|
|
12267
|
+
return out;
|
|
12631
12268
|
}
|
|
12632
|
-
function
|
|
12633
|
-
const
|
|
12634
|
-
|
|
12635
|
-
if (idx === -1)
|
|
12269
|
+
function isEnvRedactionRequested() {
|
|
12270
|
+
const v = process.env.CHAT_RECALL_REDACT_INDEX;
|
|
12271
|
+
if (v === void 0)
|
|
12636
12272
|
return null;
|
|
12637
|
-
|
|
12638
|
-
return {
|
|
12639
|
-
id: `path:${botRoot}`,
|
|
12640
|
-
displayName: parts[idx],
|
|
12641
|
-
source: "path"
|
|
12642
|
-
};
|
|
12273
|
+
return v === "1" || v === "true" || v === "yes";
|
|
12643
12274
|
}
|
|
12644
|
-
function
|
|
12645
|
-
|
|
12646
|
-
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
|
|
12650
|
-
|
|
12651
|
-
|
|
12652
|
-
|
|
12653
|
-
|
|
12654
|
-
|
|
12655
|
-
|
|
12656
|
-
|
|
12657
|
-
|
|
12658
|
-
|
|
12275
|
+
function settingsView() {
|
|
12276
|
+
let mtimeMs = -1;
|
|
12277
|
+
try {
|
|
12278
|
+
mtimeMs = statSync14(settingsFilePath()).mtimeMs;
|
|
12279
|
+
} catch {
|
|
12280
|
+
}
|
|
12281
|
+
if (cache && cache.mtimeMs === mtimeMs)
|
|
12282
|
+
return cache;
|
|
12283
|
+
let enabled = false;
|
|
12284
|
+
const extra = [];
|
|
12285
|
+
try {
|
|
12286
|
+
const s = loadSettings();
|
|
12287
|
+
enabled = !!s.privacy?.redactIndex;
|
|
12288
|
+
for (const r of s.privacy?.redactionRules ?? []) {
|
|
12289
|
+
try {
|
|
12290
|
+
extra.push({ label: r.label, pattern: new RegExp(r.pattern, "g") });
|
|
12291
|
+
} catch {
|
|
12659
12292
|
}
|
|
12660
12293
|
}
|
|
12661
|
-
|
|
12662
|
-
id: proj.id,
|
|
12663
|
-
displayName: proj.name || proj.id,
|
|
12664
|
-
source: "user",
|
|
12665
|
-
workspaceId: proj.workspace ? void 0 : void 0
|
|
12666
|
-
};
|
|
12294
|
+
} catch {
|
|
12667
12295
|
}
|
|
12668
|
-
|
|
12296
|
+
cache = { mtimeMs, enabled, rules: [...DEFAULT_REDACTION_RULES, ...extra] };
|
|
12297
|
+
return cache;
|
|
12669
12298
|
}
|
|
12670
|
-
function
|
|
12671
|
-
const
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
|
|
12675
|
-
|
|
12299
|
+
function compileServerRule(spec) {
|
|
12300
|
+
const name = spec.name || "(unnamed)";
|
|
12301
|
+
if (!spec.regex)
|
|
12302
|
+
return { name, reason: "empty pattern" };
|
|
12303
|
+
if (spec.regex.length > MAX_PATTERN_LEN)
|
|
12304
|
+
return { name, reason: `pattern longer than ${MAX_PATTERN_LEN} chars` };
|
|
12305
|
+
let re;
|
|
12306
|
+
try {
|
|
12307
|
+
const flags = "g" + (spec.flags || "").replace(/[^ims]/g, "");
|
|
12308
|
+
re = new RegExp(spec.regex, flags);
|
|
12309
|
+
} catch (e) {
|
|
12310
|
+
return { name, reason: `invalid regex: ${e instanceof Error ? e.message : e}` };
|
|
12676
12311
|
}
|
|
12677
|
-
|
|
12312
|
+
if (re.test(""))
|
|
12313
|
+
return { name, reason: "matches the empty string (would redact everything)" };
|
|
12314
|
+
re.lastIndex = 0;
|
|
12315
|
+
if (re.test(CANARY))
|
|
12316
|
+
return { name, reason: "matches benign text \u2014 too broad" };
|
|
12317
|
+
re.lastIndex = 0;
|
|
12318
|
+
return { label: `${spec.source === "pack" ? "pack" : "tenant"}:${name}`, pattern: re };
|
|
12678
12319
|
}
|
|
12679
|
-
function
|
|
12680
|
-
const
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12685
|
-
|
|
12686
|
-
|
|
12687
|
-
|
|
12688
|
-
|
|
12689
|
-
|
|
12690
|
-
source: "git-remote"
|
|
12691
|
-
};
|
|
12320
|
+
function validateRedactionRule(spec) {
|
|
12321
|
+
const out = compileServerRule(spec);
|
|
12322
|
+
return "reason" in out ? { ok: false, reason: out.reason } : { ok: true };
|
|
12323
|
+
}
|
|
12324
|
+
function installServerRulePack(pack) {
|
|
12325
|
+
const rejected = [];
|
|
12326
|
+
const rules = [];
|
|
12327
|
+
for (const spec of pack.rules || []) {
|
|
12328
|
+
if (rules.length >= MAX_PACK_RULES) {
|
|
12329
|
+
rejected.push({ name: spec.name || "(unnamed)", reason: `pack exceeds ${MAX_PACK_RULES} rules` });
|
|
12330
|
+
continue;
|
|
12692
12331
|
}
|
|
12332
|
+
const out = compileServerRule(spec);
|
|
12333
|
+
if ("reason" in out)
|
|
12334
|
+
rejected.push(out);
|
|
12335
|
+
else
|
|
12336
|
+
rules.push(out);
|
|
12693
12337
|
}
|
|
12694
|
-
|
|
12695
|
-
|
|
12696
|
-
}
|
|
12697
|
-
const hash = createHash4("sha1").update(toplevel).digest("hex").slice(0, 12);
|
|
12698
|
-
return {
|
|
12699
|
-
id: `git-local:${hash}`,
|
|
12700
|
-
displayName: basename11(toplevel),
|
|
12701
|
-
source: "git-local"
|
|
12702
|
-
};
|
|
12338
|
+
const version2 = pack.version || "";
|
|
12339
|
+
serverPack = { version: version2, rules };
|
|
12340
|
+
return { version: version2, accepted: rules.length, rejected };
|
|
12703
12341
|
}
|
|
12704
|
-
function
|
|
12705
|
-
|
|
12706
|
-
let last = "";
|
|
12707
|
-
while (cur && cur !== last) {
|
|
12708
|
-
if (existsSync16(join17(cur, ".git")))
|
|
12709
|
-
return cur;
|
|
12710
|
-
last = cur;
|
|
12711
|
-
cur = dirname13(cur);
|
|
12712
|
-
}
|
|
12713
|
-
return null;
|
|
12342
|
+
function serverRulePackVersion() {
|
|
12343
|
+
return serverPack ? serverPack.version || "unversioned" : null;
|
|
12714
12344
|
}
|
|
12715
|
-
function
|
|
12716
|
-
|
|
12717
|
-
cwd: toplevel,
|
|
12718
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
12719
|
-
encoding: "utf-8",
|
|
12720
|
-
timeout: GIT_TIMEOUT_MS
|
|
12721
|
-
});
|
|
12345
|
+
function _clearServerRulePack() {
|
|
12346
|
+
serverPack = null;
|
|
12722
12347
|
}
|
|
12723
|
-
function
|
|
12724
|
-
|
|
12725
|
-
|
|
12726
|
-
|
|
12727
|
-
|
|
12728
|
-
|
|
12348
|
+
function activeRules() {
|
|
12349
|
+
const base3 = settingsView().rules;
|
|
12350
|
+
return serverPack && serverPack.rules.length > 0 ? [...base3, ...serverPack.rules] : base3;
|
|
12351
|
+
}
|
|
12352
|
+
function isRedactionEnabled() {
|
|
12353
|
+
const env = isEnvRedactionRequested();
|
|
12354
|
+
if (env !== null)
|
|
12355
|
+
return env;
|
|
12356
|
+
return settingsView().enabled;
|
|
12357
|
+
}
|
|
12358
|
+
function _resetRedactorCache() {
|
|
12359
|
+
cache = null;
|
|
12360
|
+
}
|
|
12361
|
+
function redactSecrets(text, opts = {}) {
|
|
12362
|
+
if (!opts.force && !isRedactionEnabled())
|
|
12363
|
+
return text;
|
|
12364
|
+
if (!text)
|
|
12365
|
+
return text;
|
|
12366
|
+
const rules = opts.rules || activeRules();
|
|
12367
|
+
let out = text;
|
|
12368
|
+
for (const r of rules) {
|
|
12369
|
+
out = out.replace(r.pattern, (...args) => {
|
|
12370
|
+
const whole = args[0];
|
|
12371
|
+
const m = args.slice(0, args.length);
|
|
12372
|
+
const secret = secretOf(r, m);
|
|
12373
|
+
if (secret == null)
|
|
12374
|
+
return whole;
|
|
12375
|
+
if (opts.count)
|
|
12376
|
+
opts.count.redactions++;
|
|
12377
|
+
return whole.slice(0, whole.length - secret.length) + `[REDACTED:${r.label}]`;
|
|
12378
|
+
});
|
|
12729
12379
|
}
|
|
12730
|
-
|
|
12731
|
-
|
|
12732
|
-
|
|
12733
|
-
|
|
12734
|
-
|
|
12735
|
-
|
|
12736
|
-
} catch (err) {
|
|
12737
|
-
return { kind: "failed", reason: err instanceof Error ? err.message : String(err) };
|
|
12380
|
+
const ctx = findContextualSecrets(out);
|
|
12381
|
+
for (let i = ctx.length - 1; i >= 0; i--) {
|
|
12382
|
+
const { start, end } = ctx[i];
|
|
12383
|
+
if (opts.count)
|
|
12384
|
+
opts.count.redactions++;
|
|
12385
|
+
out = out.slice(0, start) + "[REDACTED:secret-context]" + out.slice(end);
|
|
12738
12386
|
}
|
|
12387
|
+
return out;
|
|
12739
12388
|
}
|
|
12740
|
-
function
|
|
12741
|
-
|
|
12742
|
-
|
|
12743
|
-
|
|
12744
|
-
|
|
12745
|
-
|
|
12746
|
-
gitCache.set(toplevel, probe);
|
|
12747
|
-
return probe;
|
|
12748
|
-
}
|
|
12749
|
-
function parseGitRemote(url) {
|
|
12750
|
-
const trimmed = url.trim().replace(/\.git$/, "");
|
|
12751
|
-
if (!trimmed)
|
|
12752
|
-
return null;
|
|
12753
|
-
const ssh = /^[^@]+@([^:]+):(.+?)\/([^/]+)$/.exec(trimmed);
|
|
12754
|
-
if (ssh)
|
|
12755
|
-
return { host: normaliseHost(ssh[1]), owner: ssh[2], repo: ssh[3] };
|
|
12756
|
-
const url1 = /^[a-z+]+:\/\/(?:[^@]+@)?([^/:]+)(?::\d+)?\/(.+?)\/([^/]+)$/.exec(trimmed);
|
|
12757
|
-
if (url1)
|
|
12758
|
-
return { host: normaliseHost(url1[1]), owner: url1[2], repo: url1[3] };
|
|
12759
|
-
return null;
|
|
12389
|
+
function maskSecret(s) {
|
|
12390
|
+
if (!s)
|
|
12391
|
+
return "";
|
|
12392
|
+
if (s.length <= 4)
|
|
12393
|
+
return "*".repeat(s.length);
|
|
12394
|
+
return "*".repeat(Math.min(s.length - 4, 60)) + s.slice(-4);
|
|
12760
12395
|
}
|
|
12761
|
-
function
|
|
12762
|
-
|
|
12763
|
-
|
|
12764
|
-
|
|
12765
|
-
|
|
12766
|
-
|
|
12396
|
+
function globalRegexFor(pattern) {
|
|
12397
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
|
|
12398
|
+
const key2 = pattern.source + "\0" + flags;
|
|
12399
|
+
let re = globalRegexCache.get(key2);
|
|
12400
|
+
if (!re) {
|
|
12401
|
+
re = new RegExp(pattern.source, flags);
|
|
12402
|
+
globalRegexCache.set(key2, re);
|
|
12767
12403
|
}
|
|
12768
|
-
return
|
|
12404
|
+
return re;
|
|
12769
12405
|
}
|
|
12770
|
-
function
|
|
12771
|
-
|
|
12772
|
-
let
|
|
12773
|
-
|
|
12774
|
-
|
|
12775
|
-
|
|
12776
|
-
|
|
12777
|
-
|
|
12778
|
-
if (
|
|
12779
|
-
|
|
12780
|
-
|
|
12781
|
-
|
|
12782
|
-
|
|
12406
|
+
function makeLineResolver(text) {
|
|
12407
|
+
const starts = [];
|
|
12408
|
+
for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1))
|
|
12409
|
+
starts.push(i);
|
|
12410
|
+
return (index) => {
|
|
12411
|
+
let lo = 0, hi = starts.length;
|
|
12412
|
+
while (lo < hi) {
|
|
12413
|
+
const mid = lo + hi >> 1;
|
|
12414
|
+
if (starts[mid] < index)
|
|
12415
|
+
lo = mid + 1;
|
|
12416
|
+
else
|
|
12417
|
+
hi = mid;
|
|
12418
|
+
}
|
|
12419
|
+
return lo + 1;
|
|
12420
|
+
};
|
|
12421
|
+
}
|
|
12422
|
+
function scanTextForFindings(text, opts = {}) {
|
|
12423
|
+
if (!text)
|
|
12424
|
+
return [];
|
|
12425
|
+
const rules = opts.rules || activeRules();
|
|
12426
|
+
const findings = [];
|
|
12427
|
+
const seenSpans = /* @__PURE__ */ new Set();
|
|
12428
|
+
const lineAt = makeLineResolver(text);
|
|
12429
|
+
for (const r of rules) {
|
|
12430
|
+
const re = globalRegexFor(r.pattern);
|
|
12431
|
+
re.lastIndex = 0;
|
|
12432
|
+
let m;
|
|
12433
|
+
while ((m = re.exec(text)) !== null) {
|
|
12434
|
+
const secret = secretOf(r, m);
|
|
12435
|
+
if (secret == null) {
|
|
12436
|
+
if (re.lastIndex === m.index)
|
|
12437
|
+
re.lastIndex++;
|
|
12438
|
+
continue;
|
|
12783
12439
|
}
|
|
12784
|
-
|
|
12785
|
-
|
|
12786
|
-
|
|
12440
|
+
const at = m.index + (m[0].length - secret.length);
|
|
12441
|
+
const span = `${at}:${secret.length}`;
|
|
12442
|
+
if (!seenSpans.has(span)) {
|
|
12443
|
+
seenSpans.add(span);
|
|
12444
|
+
findings.push({ rule: r.label, line: lineAt(at), preview: maskSecret(secret) });
|
|
12787
12445
|
}
|
|
12446
|
+
if (re.lastIndex === m.index)
|
|
12447
|
+
re.lastIndex++;
|
|
12788
12448
|
}
|
|
12789
|
-
last = cur;
|
|
12790
|
-
cur = dirname13(cur);
|
|
12791
|
-
depth++;
|
|
12792
12449
|
}
|
|
12793
|
-
|
|
12450
|
+
for (const { start, end } of findContextualSecrets(text)) {
|
|
12451
|
+
const line = lineAt(start);
|
|
12452
|
+
findings.push({ rule: "secret-context", line, preview: maskSecret(text.slice(start, end)) });
|
|
12453
|
+
}
|
|
12454
|
+
return findings;
|
|
12794
12455
|
}
|
|
12795
|
-
function
|
|
12796
|
-
let
|
|
12797
|
-
let entries;
|
|
12456
|
+
function privacyView() {
|
|
12457
|
+
let mtimeMs = -1;
|
|
12798
12458
|
try {
|
|
12799
|
-
|
|
12459
|
+
mtimeMs = statSync14(settingsFilePath()).mtimeMs;
|
|
12800
12460
|
} catch {
|
|
12801
|
-
return 0;
|
|
12802
|
-
}
|
|
12803
|
-
for (const name of entries) {
|
|
12804
|
-
if (name.startsWith("."))
|
|
12805
|
-
continue;
|
|
12806
|
-
const child = join17(dir, name);
|
|
12807
|
-
try {
|
|
12808
|
-
const st = statSync14(child);
|
|
12809
|
-
if (!st.isDirectory())
|
|
12810
|
-
continue;
|
|
12811
|
-
if (existsSync16(join17(child, ".git")))
|
|
12812
|
-
count++;
|
|
12813
|
-
} catch {
|
|
12814
|
-
}
|
|
12815
12461
|
}
|
|
12816
|
-
|
|
12817
|
-
|
|
12818
|
-
|
|
12462
|
+
if (privacyCache && privacyCache.mtimeMs === mtimeMs)
|
|
12463
|
+
return privacyCache;
|
|
12464
|
+
let redactToolOutputs = false, redactFilePaths2 = false;
|
|
12819
12465
|
try {
|
|
12820
|
-
|
|
12466
|
+
const s = loadSettings();
|
|
12467
|
+
redactToolOutputs = !!s.privacy?.redactToolOutputs;
|
|
12468
|
+
redactFilePaths2 = !!s.privacy?.redactFilePaths;
|
|
12821
12469
|
} catch {
|
|
12822
|
-
return resolve(p);
|
|
12823
12470
|
}
|
|
12471
|
+
privacyCache = { mtimeMs, redactToolOutputs, redactFilePaths: redactFilePaths2 };
|
|
12472
|
+
return privacyCache;
|
|
12824
12473
|
}
|
|
12825
|
-
function
|
|
12826
|
-
|
|
12827
|
-
return false;
|
|
12828
|
-
if (child === parent)
|
|
12829
|
-
return true;
|
|
12830
|
-
const c = child.endsWith(sep) ? child : child + sep;
|
|
12831
|
-
const p = parent.endsWith(sep) ? parent : parent + sep;
|
|
12832
|
-
return c.startsWith(p);
|
|
12474
|
+
function _resetPrivacyCache() {
|
|
12475
|
+
privacyCache = null;
|
|
12833
12476
|
}
|
|
12834
|
-
function
|
|
12835
|
-
|
|
12836
|
-
return re.test(toPosix(path));
|
|
12477
|
+
function isToolOutputRedactionEnabled() {
|
|
12478
|
+
return privacyView().redactToolOutputs;
|
|
12837
12479
|
}
|
|
12838
|
-
function
|
|
12839
|
-
return
|
|
12480
|
+
function isFilePathRedactionEnabled() {
|
|
12481
|
+
return privacyView().redactFilePaths;
|
|
12840
12482
|
}
|
|
12841
|
-
function
|
|
12842
|
-
|
|
12843
|
-
|
|
12844
|
-
|
|
12845
|
-
|
|
12846
|
-
|
|
12847
|
-
|
|
12848
|
-
|
|
12849
|
-
|
|
12850
|
-
i++;
|
|
12851
|
-
} else if (c === "*") {
|
|
12852
|
-
out += "[^/]*";
|
|
12853
|
-
i++;
|
|
12854
|
-
} else if (c === "?") {
|
|
12855
|
-
out += "[^/]";
|
|
12856
|
-
i++;
|
|
12857
|
-
} else if (c === "{") {
|
|
12858
|
-
const end = glob.indexOf("}", i);
|
|
12859
|
-
if (end === -1) {
|
|
12860
|
-
out += "\\{";
|
|
12861
|
-
i++;
|
|
12862
|
-
continue;
|
|
12863
|
-
}
|
|
12864
|
-
const opts = glob.slice(i + 1, end).split(",").map(escapeRe).join("|");
|
|
12865
|
-
out += "(?:" + opts + ")";
|
|
12866
|
-
i = end + 1;
|
|
12867
|
-
} else {
|
|
12868
|
-
out += escapeRe(c);
|
|
12869
|
-
i++;
|
|
12870
|
-
}
|
|
12871
|
-
}
|
|
12872
|
-
return out;
|
|
12483
|
+
function redactFilePaths(text) {
|
|
12484
|
+
if (!text)
|
|
12485
|
+
return text;
|
|
12486
|
+
return text.replace(PATH_REGEX, (m) => {
|
|
12487
|
+
if (m.startsWith("[REDACTED:"))
|
|
12488
|
+
return m;
|
|
12489
|
+
const h = createHash4("sha256").update(m).digest("hex").slice(0, 12);
|
|
12490
|
+
return `[path:${h}]`;
|
|
12491
|
+
});
|
|
12873
12492
|
}
|
|
12874
|
-
function
|
|
12875
|
-
|
|
12493
|
+
function applyChunkPrivacy(text) {
|
|
12494
|
+
let out = redactSecrets(text);
|
|
12495
|
+
if (privacyView().redactFilePaths)
|
|
12496
|
+
out = redactFilePaths(out);
|
|
12497
|
+
return out;
|
|
12876
12498
|
}
|
|
12877
|
-
|
|
12878
|
-
|
|
12879
|
-
|
|
12880
|
-
|
|
12881
|
-
init_paths();
|
|
12882
|
-
DEFAULT_AUTO_WORKSPACE_MIN_REPOS = 3;
|
|
12883
|
-
BOT_WORKTREE_MARKERS = [".claude-pr-bot"];
|
|
12884
|
-
resolveCache = /* @__PURE__ */ new Map();
|
|
12885
|
-
gitCache = /* @__PURE__ */ new Map();
|
|
12886
|
-
workspaceCache = /* @__PURE__ */ new Map();
|
|
12887
|
-
wsIdCache = /* @__PURE__ */ new Map();
|
|
12888
|
-
cachedConfig = null;
|
|
12889
|
-
GIT_TIMEOUT_MS = 1e4;
|
|
12890
|
-
AUTO_WORKSPACE_MAX_DEPTH = 2;
|
|
12891
|
-
}
|
|
12892
|
-
});
|
|
12893
|
-
|
|
12894
|
-
// ../engine/dist/core/credentials-env.js
|
|
12895
|
-
function envCredentials(env = process.env) {
|
|
12896
|
-
const token = (env.CHAT_RECALL_TOKEN || "").trim();
|
|
12897
|
-
const server = (env.CHAT_RECALL_SERVER || "").trim();
|
|
12898
|
-
if (!token || !server)
|
|
12899
|
-
return null;
|
|
12900
|
-
if (!/^https?:\/\//i.test(server))
|
|
12901
|
-
return null;
|
|
12902
|
-
return { serverUrl: normalise(server), token };
|
|
12499
|
+
function redactToolResultBody(toolName, body) {
|
|
12500
|
+
if (!body)
|
|
12501
|
+
return body;
|
|
12502
|
+
return `[redacted: ${toolName} output, ${body.length} chars]`;
|
|
12903
12503
|
}
|
|
12904
|
-
var
|
|
12905
|
-
var
|
|
12906
|
-
"../engine/dist/core/
|
|
12504
|
+
var DEFAULT_REDACTION_RULES, SECRET_CONTEXT_RE, HIGH_ENTROPY_TOKEN_RE, cache, CANARY, MAX_PACK_RULES, MAX_PATTERN_LEN, serverPack, globalRegexCache, privacyCache, PATH_REGEX;
|
|
12505
|
+
var init_secret_redactor = __esm({
|
|
12506
|
+
"../engine/dist/core/secret-redactor.js"() {
|
|
12907
12507
|
"use strict";
|
|
12908
|
-
|
|
12508
|
+
init_settings();
|
|
12509
|
+
DEFAULT_REDACTION_RULES = [
|
|
12510
|
+
// AWS access tokens. Prefix-anchored: AKIA, ASIA, ABIA (long-term,
|
|
12511
|
+
// STS, EC2). Always exactly 16 hex/alphanumeric after the prefix.
|
|
12512
|
+
// NOTE: this matches only the access-key-ID, NOT the secret key value —
|
|
12513
|
+
// the `env-secret` rule below covers AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN.
|
|
12514
|
+
{ label: "aws-access-token", pattern: /\b(?:AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}\b/g },
|
|
12515
|
+
// AWS STS session tokens. These are long base64 blobs with NO `NAME=` context
|
|
12516
|
+
// when pasted bare into prose/code, so `env-secret` misses them — but they
|
|
12517
|
+
// reliably start with one of a handful of base64 prefixes (the encoded
|
|
12518
|
+
// `{"...` JSON header). Prefix-anchored → low false-positive. This is the
|
|
12519
|
+
// bare-value form that leaked (`search('IQoJEXAMPLEfakeSESSIONtokenNOTreal01234567890abcDEF==...')`).
|
|
12520
|
+
{ label: "aws-session-token", pattern: /\b(?:IQoJ|FwoG|FwoH|FQoG|FQoH|FwoD)[A-Za-z0-9/+]{40,}={0,2}/g },
|
|
12521
|
+
// Env/shell-assignment secrets — a VAR whose name ends in KEY/SECRET/TOKEN/
|
|
12522
|
+
// PASSWORD/CREDENTIAL, then `=`/`:`, then the value. Zero-width lookbehind so
|
|
12523
|
+
// only the VALUE is redacted (the var name stays for context). This is the
|
|
12524
|
+
// dominant real-world leak vector (`export` dumps, .env files, `curl -H`,
|
|
12525
|
+
// tool output) and is exactly what leaked AWS_SECRET_ACCESS_KEY and
|
|
12526
|
+
// AWS_SESSION_TOKEN in cleartext while the access-key-ID rule above caught
|
|
12527
|
+
// only the (least-sensitive) ID.
|
|
12528
|
+
// Suffix list deliberately broad: DB_PASS (our own helm chart's name!),
|
|
12529
|
+
// NPM_AUTH, SENTRY_DSN, SESSION/COOKIE secrets and BEARER vars all leaked
|
|
12530
|
+
// past the original KEY/SECRET/TOKEN/PASSWORD-only list. Values under 12
|
|
12531
|
+
// chars don't redact, so flag-ish vars (BYPASS=true) stay readable.
|
|
12532
|
+
// Context CONSUMED, secret in group 1 — see RedactionRule.secret. As a
|
|
12533
|
+
// lookbehind this was the second most expensive rule in the collector.
|
|
12534
|
+
{ 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 },
|
|
12535
|
+
// Credentials embedded in a connection URL: scheme://user:PASSWORD@host.
|
|
12536
|
+
// Redacts only the password segment (keeps scheme/user/host for context).
|
|
12537
|
+
//
|
|
12538
|
+
// Scheme-agnostic on purpose. The old rule allowlisted eight DB schemes, so
|
|
12539
|
+
// `https://user:pass@host`, `ssh://`, `ftp://`, `smtp://` and every other
|
|
12540
|
+
// userinfo URL walked through in cleartext — and a `jdbc:postgresql://…`
|
|
12541
|
+
// prefix broke the \b anchor for the one family it did cover. Any scheme
|
|
12542
|
+
// followed by userinfo is a credential, so match the shape, not a list.
|
|
12543
|
+
{ label: "url-password", secret: 1, pattern: /\b[a-zA-Z][a-zA-Z0-9+.-]{1,31}:\/\/[^:@\s/]{1,64}:([^@\s/]{1,256})(?=@)/g },
|
|
12544
|
+
// Bare AWS secret-access-key VALUE: EXACTLY 40 base64 chars, word-bounded,
|
|
12545
|
+
// containing upper+lower+digit. This is the gitleaks heuristic — the mixed-case
|
|
12546
|
+
// requirement excludes 40-char lowercase-hex git SHAs, and the exact-40 bound
|
|
12547
|
+
// excludes longer blobs/tokens. It catches the value when it appears bare in
|
|
12548
|
+
// prose with NO `NAME=` and NO nearby context word (e.g. inside a markdown
|
|
12549
|
+
// table or quoted in analysis) — the residual leak path the env-secret and
|
|
12550
|
+
// contextual rules both miss. Placed AFTER env-secret/url-password so the
|
|
12551
|
+
// keyed/URL forms keep their more-specific labels.
|
|
12552
|
+
// The three stacked lookaheads each re-scanned the same 40 characters, which
|
|
12553
|
+
// made this the MOST expensive rule of all 106 the collector runs (13.1% of a
|
|
12554
|
+
// 29.5 MB scan). The mixed-case-plus-digit test is now one JS pass.
|
|
12555
|
+
{
|
|
12556
|
+
label: "aws-secret-key",
|
|
12557
|
+
secret: 1,
|
|
12558
|
+
pattern: /(?<![A-Za-z0-9/+])([A-Za-z0-9/+]{40})(?![A-Za-z0-9/+])/g,
|
|
12559
|
+
where: (v) => /[a-z]/.test(v) && /[A-Z]/.test(v) && /[0-9]/.test(v)
|
|
12560
|
+
},
|
|
12561
|
+
// GitHub personal access tokens.
|
|
12562
|
+
{ label: "github-pat", pattern: /\bghp_[A-Za-z0-9]{36}\b/g },
|
|
12563
|
+
// GitHub fine-grained PATs.
|
|
12564
|
+
{ label: "github-fine", pattern: /\bgithub_pat_[A-Za-z0-9_]{82}\b/g },
|
|
12565
|
+
// GitHub OAuth.
|
|
12566
|
+
{ label: "github-oauth", pattern: /\bgho_[A-Za-z0-9]{36}\b/g },
|
|
12567
|
+
// GitHub app + refresh.
|
|
12568
|
+
{ label: "github-app", pattern: /\b(?:ghs|ghu|ghr)_[A-Za-z0-9]{36}\b/g },
|
|
12569
|
+
// Slack bot/user/app tokens.
|
|
12570
|
+
{ label: "slack-token", pattern: /\bxox[abopr]-[A-Za-z0-9-]{10,}\b/g },
|
|
12571
|
+
// Slack incoming webhook URL.
|
|
12572
|
+
{ label: "slack-webhook", pattern: /\bhttps:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+\b/g },
|
|
12573
|
+
// Stripe live + test keys.
|
|
12574
|
+
{ label: "stripe-key", pattern: /\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{24,}\b/g },
|
|
12575
|
+
// Anthropic.
|
|
12576
|
+
{ label: "anthropic-key", pattern: /\bsk-ant-[a-zA-Z0-9_-]{40,}\b/g },
|
|
12577
|
+
// OpenAI. Two shapes, deliberately kept apart. The classic key is
|
|
12578
|
+
// `sk-` + base62 with NO hyphens; the modern scoped keys carry an explicit
|
|
12579
|
+
// proj/svcacct/admin segment first. Allowing hyphens after a bare `sk-`
|
|
12580
|
+
// (which is what admitted sk-proj- in the first place) also matches ordinary
|
|
12581
|
+
// kebab-case text — `sk-fix-the-login-redirect-bug`, `.sk-button-primary`,
|
|
12582
|
+
// `sk-2024-01-01-backup` — and this corpus is full of branch names and CSS
|
|
12583
|
+
// classes. Requiring the segment keeps sk-proj- coverage without that.
|
|
12584
|
+
{ label: "openai-key", pattern: /\bsk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,}\b|\bsk-[A-Za-z0-9]{20,}\b/g },
|
|
12585
|
+
// Google API key (AIza…).
|
|
12586
|
+
{ label: "google-api-key", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
|
|
12587
|
+
// Generic JWT — three base64url segments.
|
|
12588
|
+
{ label: "jwt", pattern: /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g },
|
|
12589
|
+
// Private key blocks (PEM). The label is a free word run rather than a fixed
|
|
12590
|
+
// list, so ENCRYPTED PRIVATE KEY and PGP PRIVATE KEY BLOCK are covered
|
|
12591
|
+
// without another enumeration, and \1 forces END to name the same kind as
|
|
12592
|
+
// BEGIN.
|
|
12593
|
+
//
|
|
12594
|
+
// The body is [^-], NOT [\s\S]*?. That is a performance boundary, not a
|
|
12595
|
+
// style choice. An unanchored prefix with an unbounded lazy gap re-scans to
|
|
12596
|
+
// end-of-input from every candidate start, so text carrying many BEGIN
|
|
12597
|
+
// markers and no matching END costs O(n^2): 500KB of that measured 810ms
|
|
12598
|
+
// here, and this runs synchronously on the server's event loop over
|
|
12599
|
+
// tenant-supplied text (services/secret-rescan.ts). A PEM body is base64
|
|
12600
|
+
// plus headers and contains no '-', so excluding it makes the scan stop at
|
|
12601
|
+
// the next delimiter instead of at EOF — same matches, linear cost.
|
|
12602
|
+
{ label: "private-key", pattern: /-----BEGIN ([A-Z0-9 ]{0,32})PRIVATE KEY(?: BLOCK)?-----[^-]{0,10000}?-----END \1PRIVATE KEY(?: BLOCK)?-----/g },
|
|
12603
|
+
// PuTTY keys are not PEM. Match the Private-Lines payload — the actual key
|
|
12604
|
+
// material — rather than spanning the whole file, which would need the same
|
|
12605
|
+
// unbounded gap the rule above just removed. Line-anchored and base64-only,
|
|
12606
|
+
// so it cannot run away.
|
|
12607
|
+
{ label: "putty-key", pattern: /^Private-Lines:[ \t]*\d{1,4}[\r\n]+(?:[A-Za-z0-9+/=]{8,120}[\r\n]+){1,400}/gm },
|
|
12608
|
+
// npm tokens (uuid-shaped — overlap with our UUID filter; intentional).
|
|
12609
|
+
{ label: "npm-token", pattern: /\bnpm_[A-Za-z0-9]{36}\b/g },
|
|
12610
|
+
// .npmrc auth lines: //registry.npmjs.org/:_authToken=VALUE (the key is not
|
|
12611
|
+
// ALL-CAPS, so the env-secret rule above never matched it). The leading `:`
|
|
12612
|
+
// is required — it is what makes this npmrc syntax rather than any shell var
|
|
12613
|
+
// ending in _AUTH, which env-secret already owns and labels better.
|
|
12614
|
+
// [^\s"'`]+ so the closing quote is not swallowed, and a ${VAR} placeholder
|
|
12615
|
+
// is left alone — redacting it destroys a config example and hides nothing.
|
|
12616
|
+
// The rule the perf profile caught the wedged daemon inside: 98.94% of samples.
|
|
12617
|
+
{ label: "npmrc-auth", secret: 1, pattern: /:_(?:authToken|password|auth)[ \t]*=[ \t]*(?!\$\{)([^\s"'`]{12,})/gi },
|
|
12618
|
+
// HTTP Authorization headers. env-secret requires an ALL-CAPS name, and
|
|
12619
|
+
// `Authorization` is mixed case, so both Bearer and Basic walked straight
|
|
12620
|
+
// through — including short opaque tokens the contextual pass also misses
|
|
12621
|
+
// (it needs 32+ chars AND mixed-case-plus-digit).
|
|
12622
|
+
// Same two guards as npmrc-auth: stop at a quote, and skip the obvious
|
|
12623
|
+
// placeholders that fill documentation (<YOUR_TOKEN>, ${TOKEN}, xxx…).
|
|
12624
|
+
{ label: "auth-header", secret: 1, pattern: /\bauthorization[ \t]*:[ \t]*(?:bearer|basic|token)[ \t]+(?![<$])([^\s"'`<]{8,})/gi },
|
|
12625
|
+
// Vendor tokens with unambiguous prefixes. Each is a fixed literal, so a
|
|
12626
|
+
// false positive is essentially impossible and the cost of listing is low.
|
|
12627
|
+
{ label: "gitlab-pat", pattern: /\bglpat-[A-Za-z0-9_-]{20,}\b/g },
|
|
12628
|
+
{ label: "slack-app-token", pattern: /\bxapp-\d-[A-Z0-9]{8,}-\d{10,}-[a-f0-9]{32,}\b/g },
|
|
12629
|
+
{ label: "sendgrid-key", pattern: /\bSG\.[A-Za-z0-9_-]{20,24}\.[A-Za-z0-9_-]{40,50}\b/g },
|
|
12630
|
+
// (?<![-\w]) so `cache-key-<md5>` is not read as a mailgun key.
|
|
12631
|
+
{ label: "mailgun-key", pattern: /(?<![-\w])key-[a-f0-9]{32}\b/g },
|
|
12632
|
+
{ label: "digitalocean-pat", pattern: /\bdop_v1_[a-f0-9]{64}\b/g },
|
|
12633
|
+
{ label: "huggingface-token", pattern: /\bhf_[A-Za-z0-9]{30,}\b/g },
|
|
12634
|
+
{ label: "shopify-token", pattern: /\bshp(?:at|ca|pa|ss)_[a-fA-F0-9]{32}\b/g },
|
|
12635
|
+
{ label: "pypi-token", pattern: /\bpypi-AgEIcHlwaS5vcmc[A-Za-z0-9_-]{50,}\b/g },
|
|
12636
|
+
// No twilio-sid rule: AC…/SK… are Twilio SIDs, which are public account and
|
|
12637
|
+
// key identifiers, not the auth token. Redacting them hid nothing and
|
|
12638
|
+
// collided with 32-hex etags and MD5 sums.
|
|
12639
|
+
// No discord-bot-token rule either: the shape (word.word.word) matched
|
|
12640
|
+
// ordinary dotted symbols such as a.b.ApplicationContextProviderImpl.
|
|
12641
|
+
{ label: "telegram-bot-token", pattern: /\b\d{8,10}:AA[A-Za-z0-9_-]{32,}\b/g },
|
|
12642
|
+
{ label: "doppler-token", pattern: /\bdp\.(?:pt|st|sa|ct)\.[A-Za-z0-9_-]{20,}\b/g },
|
|
12643
|
+
// Azure Storage SAS signature — the `sig=` parameter is the actual secret.
|
|
12644
|
+
{ label: "azure-sas", secret: 1, pattern: /[?&]sig=([A-Za-z0-9%/+=]{20,})/g }
|
|
12645
|
+
];
|
|
12646
|
+
SECRET_CONTEXT_RE = /\b(?:secret|password|passwd|credential|access[ _-]?key|private[ _-]?key|api[ _-]?key|auth(?:oriz\w*)?|bearer|token)\b/gi;
|
|
12647
|
+
HIGH_ENTROPY_TOKEN_RE = /[A-Za-z0-9/+_-]{32,}/g;
|
|
12648
|
+
cache = null;
|
|
12649
|
+
CANARY = "the quick brown fox jumps over the lazy dog 0123456789 commit 8ba1f109551bd432803012645ac136ddd475d0fb src/index.ts:42";
|
|
12650
|
+
MAX_PACK_RULES = 200;
|
|
12651
|
+
MAX_PATTERN_LEN = 512;
|
|
12652
|
+
serverPack = null;
|
|
12653
|
+
globalRegexCache = /* @__PURE__ */ new Map();
|
|
12654
|
+
privacyCache = null;
|
|
12655
|
+
PATH_REGEX = /(?<![A-Za-z0-9_])(?:\/[^\s'"`<>]+|[A-Za-z]:\\[^\s'"`<>]+)/g;
|
|
12909
12656
|
}
|
|
12910
12657
|
});
|
|
12911
12658
|
|
|
@@ -14888,111 +14635,6 @@ var init_source_registry = __esm({
|
|
|
14888
14635
|
}
|
|
14889
14636
|
});
|
|
14890
14637
|
|
|
14891
|
-
// ../engine/dist/parsers/session-source.js
|
|
14892
|
-
var SessionSource;
|
|
14893
|
-
var init_session_source = __esm({
|
|
14894
|
-
"../engine/dist/parsers/session-source.js"() {
|
|
14895
|
-
"use strict";
|
|
14896
|
-
init_session();
|
|
14897
|
-
init_chunker();
|
|
14898
|
-
init_settings();
|
|
14899
|
-
SessionSource = class {
|
|
14900
|
-
sourceType = "session";
|
|
14901
|
-
claudeDir;
|
|
14902
|
-
constructor(claudeDir) {
|
|
14903
|
-
this.claudeDir = claudeDir;
|
|
14904
|
-
}
|
|
14905
|
-
async *discover() {
|
|
14906
|
-
if (!isSourceEnabled("claude", "sessions"))
|
|
14907
|
-
return;
|
|
14908
|
-
for (const [entry, sessionPath] of getAllSessions(this.claudeDir)) {
|
|
14909
|
-
yield this.entryToItem(entry, sessionPath);
|
|
14910
|
-
}
|
|
14911
|
-
}
|
|
14912
|
-
async parse(item) {
|
|
14913
|
-
const content = await parseSessionFile(item.filePath);
|
|
14914
|
-
const entry = this.itemToEntry(item);
|
|
14915
|
-
const sessionChunks = chunkSession(entry, content);
|
|
14916
|
-
item.extra = {
|
|
14917
|
-
...item.extra || {},
|
|
14918
|
-
...content.metadata
|
|
14919
|
-
};
|
|
14920
|
-
return sessionChunks.map((sc) => ({
|
|
14921
|
-
chunkId: sc.chunkId,
|
|
14922
|
-
itemId: sc.sessionId,
|
|
14923
|
-
sourceType: "session",
|
|
14924
|
-
// Empty title is preferable to "Session abc12345" placeholder text:
|
|
14925
|
-
// the latter pollutes search results and gets surfaced in the UI as
|
|
14926
|
-
// a misleading heading. Downstream renderers fall back to
|
|
14927
|
-
// project path / time when title is empty.
|
|
14928
|
-
title: sc.firstPrompt.slice(0, 100),
|
|
14929
|
-
text: sc.text,
|
|
14930
|
-
chunkType: sc.chunkType,
|
|
14931
|
-
projectPath: sc.projectPath,
|
|
14932
|
-
filePath: sc.sourceFile,
|
|
14933
|
-
mtime: sc.mtime
|
|
14934
|
-
}));
|
|
14935
|
-
}
|
|
14936
|
-
async extractLinks(item) {
|
|
14937
|
-
if (item.filePath) {
|
|
14938
|
-
try {
|
|
14939
|
-
const { detectForkPredecessor: detectForkPredecessor2 } = await Promise.resolve().then(() => (init_raw(), raw_exports));
|
|
14940
|
-
const predecessor = detectForkPredecessor2(item.filePath);
|
|
14941
|
-
if (predecessor) {
|
|
14942
|
-
return [{
|
|
14943
|
-
sourceType: "session",
|
|
14944
|
-
sourceId: item.id,
|
|
14945
|
-
targetType: "session",
|
|
14946
|
-
targetId: predecessor,
|
|
14947
|
-
linkType: "forked_from",
|
|
14948
|
-
confidence: 1
|
|
14949
|
-
}];
|
|
14950
|
-
}
|
|
14951
|
-
} catch {
|
|
14952
|
-
}
|
|
14953
|
-
}
|
|
14954
|
-
return [];
|
|
14955
|
-
}
|
|
14956
|
-
entryToItem(entry, sessionPath) {
|
|
14957
|
-
return {
|
|
14958
|
-
id: entry.sessionId,
|
|
14959
|
-
sourceType: "session",
|
|
14960
|
-
// Empty title when there's no first prompt — the UI then knows to
|
|
14961
|
-
// fall back to summary or project path instead of rendering the
|
|
14962
|
-
// useless "Session abc12345" placeholder as a heading.
|
|
14963
|
-
title: entry.firstPrompt ? entry.firstPrompt.replace(/\n/g, " ").trim().slice(0, 100) : "",
|
|
14964
|
-
projectPath: entry.projectPath,
|
|
14965
|
-
filePath: sessionPath,
|
|
14966
|
-
mtime: entry.fileMtime,
|
|
14967
|
-
contentPreview: entry.firstPrompt?.slice(0, 200),
|
|
14968
|
-
extra: {
|
|
14969
|
-
messageCount: entry.messageCount,
|
|
14970
|
-
gitBranch: entry.gitBranch,
|
|
14971
|
-
created: entry.created,
|
|
14972
|
-
modified: entry.modified,
|
|
14973
|
-
isSidechain: entry.isSidechain
|
|
14974
|
-
}
|
|
14975
|
-
};
|
|
14976
|
-
}
|
|
14977
|
-
itemToEntry(item) {
|
|
14978
|
-
const extra = item.extra || {};
|
|
14979
|
-
return {
|
|
14980
|
-
sessionId: item.id,
|
|
14981
|
-
fullPath: item.filePath,
|
|
14982
|
-
fileMtime: item.mtime,
|
|
14983
|
-
firstPrompt: item.contentPreview || "",
|
|
14984
|
-
messageCount: extra.messageCount || 0,
|
|
14985
|
-
created: extra.created || "",
|
|
14986
|
-
modified: extra.modified || "",
|
|
14987
|
-
gitBranch: extra.gitBranch || "",
|
|
14988
|
-
projectPath: item.projectPath,
|
|
14989
|
-
isSidechain: extra.isSidechain || false
|
|
14990
|
-
};
|
|
14991
|
-
}
|
|
14992
|
-
};
|
|
14993
|
-
}
|
|
14994
|
-
});
|
|
14995
|
-
|
|
14996
14638
|
// ../engine/dist/core/utils.js
|
|
14997
14639
|
import { existsSync as existsSync21, readdirSync as readdirSync15 } from "fs";
|
|
14998
14640
|
import { homedir as homedir6 } from "os";
|
|
@@ -18349,7 +17991,6 @@ var init_plugins_source = __esm({
|
|
|
18349
17991
|
|
|
18350
17992
|
// ../engine/dist/parsers/all-sources.js
|
|
18351
17993
|
function buildSourceRegistry(registry = new SourceRegistry()) {
|
|
18352
|
-
registry.register(new SessionSource());
|
|
18353
17994
|
registry.register(new PlanSource());
|
|
18354
17995
|
registry.register(new TaskSource());
|
|
18355
17996
|
registry.register(new ClaudeMdSource());
|
|
@@ -18373,7 +18014,6 @@ var init_all_sources = __esm({
|
|
|
18373
18014
|
"../engine/dist/parsers/all-sources.js"() {
|
|
18374
18015
|
"use strict";
|
|
18375
18016
|
init_source_registry();
|
|
18376
|
-
init_session_source();
|
|
18377
18017
|
init_plan_source();
|
|
18378
18018
|
init_task_source();
|
|
18379
18019
|
init_claude_md_source();
|
|
@@ -18817,7 +18457,7 @@ var init_http = __esm({
|
|
|
18817
18457
|
CLI_VERSION = JSON.parse(readFileSync31(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
|
|
18818
18458
|
} catch {
|
|
18819
18459
|
try {
|
|
18820
|
-
if (true) CLI_VERSION = "0.
|
|
18460
|
+
if (true) CLI_VERSION = "0.7.0";
|
|
18821
18461
|
} catch {
|
|
18822
18462
|
}
|
|
18823
18463
|
}
|
|
@@ -18842,27 +18482,6 @@ var init_redact_deep = __esm({
|
|
|
18842
18482
|
}
|
|
18843
18483
|
});
|
|
18844
18484
|
|
|
18845
|
-
// ../engine/dist/core/session-multi-tool.js
|
|
18846
|
-
function extractTurnsAny(sessionId, opts = {}) {
|
|
18847
|
-
const backend = getBackendForId(sessionId);
|
|
18848
|
-
if (!backend)
|
|
18849
|
-
return { sessionId, found: false, turns: [], startMs: 0, endMs: 0 };
|
|
18850
|
-
return backend.extractTurns(sessionId, opts);
|
|
18851
|
-
}
|
|
18852
|
-
function replaySessionAny(sessionId) {
|
|
18853
|
-
const backend = getBackendForId(sessionId);
|
|
18854
|
-
if (!backend)
|
|
18855
|
-
return { sessionId, found: false, projectPath: "", files: [], totalLinesAdded: 0, totalLinesRemoved: 0 };
|
|
18856
|
-
return backend.replay(sessionId);
|
|
18857
|
-
}
|
|
18858
|
-
var init_session_multi_tool = __esm({
|
|
18859
|
-
"../engine/dist/core/session-multi-tool.js"() {
|
|
18860
|
-
"use strict";
|
|
18861
|
-
init_tool_backend();
|
|
18862
|
-
init_backends();
|
|
18863
|
-
}
|
|
18864
|
-
});
|
|
18865
|
-
|
|
18866
18485
|
// src/derived.ts
|
|
18867
18486
|
function collectDerivedRows(ref, mtime, count, maxRowBytes) {
|
|
18868
18487
|
const id = ref.prefixedId;
|
|
@@ -19337,7 +18956,7 @@ var init_telemetry = __esm({
|
|
|
19337
18956
|
init_sync_client();
|
|
19338
18957
|
cliVersion2 = () => {
|
|
19339
18958
|
try {
|
|
19340
|
-
return true ? "0.
|
|
18959
|
+
return true ? "0.7.0" : "dev";
|
|
19341
18960
|
} catch {
|
|
19342
18961
|
return "dev";
|
|
19343
18962
|
}
|
|
@@ -19352,6 +18971,7 @@ var collector_health_exports = {};
|
|
|
19352
18971
|
__export(collector_health_exports, {
|
|
19353
18972
|
CRASHLOOP_RESTARTS: () => CRASHLOOP_RESTARTS,
|
|
19354
18973
|
STALE_AFTER_MS: () => STALE_AFTER_MS,
|
|
18974
|
+
applySyncOutcome: () => applySyncOutcome,
|
|
19355
18975
|
collectorHealthPath: () => collectorHealthPath,
|
|
19356
18976
|
judgeHealth: () => judgeHealth,
|
|
19357
18977
|
progressLine: () => progressLine,
|
|
@@ -19362,6 +18982,22 @@ __export(collector_health_exports, {
|
|
|
19362
18982
|
});
|
|
19363
18983
|
import { readFileSync as readFileSync32, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8 } from "node:fs";
|
|
19364
18984
|
import { join as join40, dirname as dirname17 } from "node:path";
|
|
18985
|
+
function applySyncOutcome(prior, outcome, now = Date.now()) {
|
|
18986
|
+
const t = { lastOkAt: prior?.lastOkAt ?? null, failures: prior?.failures ?? 0 };
|
|
18987
|
+
if (prior?.lastError !== void 0)
|
|
18988
|
+
t.lastError = prior.lastError;
|
|
18989
|
+
if (outcome.ok) {
|
|
18990
|
+
t.lastOkAt = now;
|
|
18991
|
+
t.failures = 0;
|
|
18992
|
+
delete t.lastError;
|
|
18993
|
+
return t;
|
|
18994
|
+
}
|
|
18995
|
+
if ((outcome.accepted ?? 0) > 0)
|
|
18996
|
+
t.lastOkAt = now;
|
|
18997
|
+
t.failures += 1;
|
|
18998
|
+
t.lastError = (outcome.error ?? "unknown error").slice(0, 160);
|
|
18999
|
+
return t;
|
|
19000
|
+
}
|
|
19365
19001
|
function progressLine(h) {
|
|
19366
19002
|
const p = h?.progress;
|
|
19367
19003
|
if (!p || p.complete || p.total <= 0)
|
|
@@ -19920,6 +19556,7 @@ var sync_client_exports = {};
|
|
|
19920
19556
|
__export(sync_client_exports, {
|
|
19921
19557
|
BATCH_SCAN_MAX_BYTES: () => BATCH_SCAN_MAX_BYTES,
|
|
19922
19558
|
FULL_BUILD_MAX_BYTES: () => FULL_BUILD_MAX_BYTES,
|
|
19559
|
+
_acceptedUploads: () => _acceptedUploads,
|
|
19923
19560
|
_fetchTenantSyncConfigForTests: () => _fetchTenantSyncConfigForTests,
|
|
19924
19561
|
_resetCapsCacheForTests: () => _resetCapsCacheForTests,
|
|
19925
19562
|
_resetTenantSecurityConfigCache: () => _resetTenantSecurityConfigCache,
|
|
@@ -20183,6 +19820,9 @@ function removeCredentials(serverUrl) {
|
|
|
20183
19820
|
writeFileSync9(credPath(), JSON.stringify({ targets: kept }, null, 2));
|
|
20184
19821
|
return true;
|
|
20185
19822
|
}
|
|
19823
|
+
function _acceptedUploads(url) {
|
|
19824
|
+
return acceptedUploads.get(url) ?? 0;
|
|
19825
|
+
}
|
|
20186
19826
|
async function syncSessions(opts = {}) {
|
|
20187
19827
|
const targets = loadAllCredentials();
|
|
20188
19828
|
if (targets.length === 0) throw new Error("Not logged in \u2014 run `chat-recall login <server-url> --token <token>`");
|
|
@@ -20206,10 +19846,11 @@ async function syncSessions(opts = {}) {
|
|
|
20206
19846
|
};
|
|
20207
19847
|
continue;
|
|
20208
19848
|
}
|
|
19849
|
+
acceptedUploads.set(cred.serverUrl, 0);
|
|
20209
19850
|
try {
|
|
20210
19851
|
const r = await syncToTarget(cred, opts);
|
|
20211
19852
|
noteTargetSuccess(cred.serverUrl);
|
|
20212
|
-
perTarget[cred.serverUrl] = { ok: true };
|
|
19853
|
+
perTarget[cred.serverUrl] = { ok: true, accepted: acceptedUploads.get(cred.serverUrl) ?? 0 };
|
|
20213
19854
|
agg = agg ? {
|
|
20214
19855
|
uploaded: agg.uploaded + r.uploaded,
|
|
20215
19856
|
skipped: agg.skipped + r.skipped,
|
|
@@ -20226,7 +19867,7 @@ async function syncSessions(opts = {}) {
|
|
|
20226
19867
|
} catch (e) {
|
|
20227
19868
|
const msg = e instanceof Error ? e.message : String(e);
|
|
20228
19869
|
errors.push(`${cred.serverUrl}: ${msg}`);
|
|
20229
|
-
perTarget[cred.serverUrl] = { ok: false, error: msg };
|
|
19870
|
+
perTarget[cred.serverUrl] = { ok: false, error: msg, accepted: acceptedUploads.get(cred.serverUrl) ?? 0 };
|
|
20230
19871
|
const next = noteTargetFailure(cred.serverUrl, msg);
|
|
20231
19872
|
const notice = breakerNotice(cred.serverUrl, next);
|
|
20232
19873
|
if (notice) console.error(`[sync] ${notice}`);
|
|
@@ -20492,7 +20133,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
20492
20133
|
return [];
|
|
20493
20134
|
}
|
|
20494
20135
|
});
|
|
20495
|
-
const MAX_BATCH_BYTES = 4 * 1024 * 1024;
|
|
20136
|
+
const MAX_BATCH_BYTES = Number(process.env.CHAT_RECALL_MAX_BATCH_BYTES) || (isLocalHost(cred.serverUrl) ? 4 * 1024 * 1024 : 1024 * 1024);
|
|
20496
20137
|
const sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
20497
20138
|
const explicitThrottle = typeof opts.throttleMs === "number" && opts.throttleMs > 0;
|
|
20498
20139
|
const throttleMs = explicitThrottle ? opts.throttleMs : 0;
|
|
@@ -20527,6 +20168,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
20527
20168
|
signal: ac.signal
|
|
20528
20169
|
});
|
|
20529
20170
|
if (res.ok) {
|
|
20171
|
+
acceptedUploads.set(cred.serverUrl, (acceptedUploads.get(cred.serverUrl) ?? 0) + 1);
|
|
20530
20172
|
const body2 = await res.json().catch(() => ({}));
|
|
20531
20173
|
if (typeof body2.telemetry === "boolean") {
|
|
20532
20174
|
setTelemetryEligible(base3, body2.telemetry);
|
|
@@ -20577,7 +20219,8 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
20577
20219
|
if (attempt >= RETRY_DELAYS_MS.length) throw new Error(`sync failed after ${attempt + 1} attempts: HTTP ${res.status} ${text}`);
|
|
20578
20220
|
const retryAfterS = Number(res.headers.get("retry-after"));
|
|
20579
20221
|
const waitMs = res.status === 429 && retryAfterS > 0 ? retryAfterS * 1e3 : RETRY_DELAYS_MS[attempt];
|
|
20580
|
-
|
|
20222
|
+
const why = res.status === 524 ? ` \u2014 the proxy cut the request at its time limit; the batch is too big for one request` : "";
|
|
20223
|
+
console.error(`[sync] HTTP ${res.status} from ${base3}${why} \u2014 backing off ${Math.round(waitMs / 1e3)}s (attempt ${attempt + 1})`);
|
|
20581
20224
|
await sleep2(waitMs);
|
|
20582
20225
|
continue;
|
|
20583
20226
|
} catch (err) {
|
|
@@ -21436,7 +21079,7 @@ async function syncIncremental(opts = {}) {
|
|
|
21436
21079
|
lock.release();
|
|
21437
21080
|
}
|
|
21438
21081
|
}
|
|
21439
|
-
var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, 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;
|
|
21082
|
+
var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, SYNC_RULES_REFUSAL, hashPath, acceptedUploads, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
|
|
21440
21083
|
var init_sync_client = __esm({
|
|
21441
21084
|
"src/sync-client.ts"() {
|
|
21442
21085
|
"use strict";
|
|
@@ -21481,7 +21124,7 @@ var init_sync_client = __esm({
|
|
|
21481
21124
|
ownVersion = JSON.parse(readFileSync35(join42(dir, "../package.json"), "utf-8")).version || ownVersion;
|
|
21482
21125
|
} catch {
|
|
21483
21126
|
try {
|
|
21484
|
-
if (true) ownVersion = "0.
|
|
21127
|
+
if (true) ownVersion = "0.7.0";
|
|
21485
21128
|
} catch {
|
|
21486
21129
|
}
|
|
21487
21130
|
}
|
|
@@ -21501,6 +21144,7 @@ var init_sync_client = __esm({
|
|
|
21501
21144
|
_fetchTenantSyncConfigForTests = fetchTenantSyncConfig;
|
|
21502
21145
|
SYNC_RULES_REFUSAL = /* @__PURE__ */ Symbol.for("chat-recall.syncRulesRefusal");
|
|
21503
21146
|
hashPath = (p) => p ? "p_" + createHash9("sha256").update(p).digest("hex").slice(0, 12) : "";
|
|
21147
|
+
acceptedUploads = /* @__PURE__ */ new Map();
|
|
21504
21148
|
MAX_DERIVED_ROW_BYTES = 2 * 1024 * 1024;
|
|
21505
21149
|
isLocalHost = isPrivateHost;
|
|
21506
21150
|
CAPS_TTL_MS = 60 * 6e4;
|
|
@@ -25596,7 +25240,7 @@ var init_intent_drain = __esm({
|
|
|
25596
25240
|
ownVersion2 = JSON.parse(readFileSync45(join56(dir, "../package.json"), "utf-8")).version || ownVersion2;
|
|
25597
25241
|
} catch {
|
|
25598
25242
|
try {
|
|
25599
|
-
if (true) ownVersion2 = "0.
|
|
25243
|
+
if (true) ownVersion2 = "0.7.0";
|
|
25600
25244
|
} catch {
|
|
25601
25245
|
}
|
|
25602
25246
|
}
|
|
@@ -27968,6 +27612,7 @@ var DEFAULT_ALLOW = [
|
|
|
27968
27612
|
"recall_user_prompts",
|
|
27969
27613
|
"recall_decision_record",
|
|
27970
27614
|
"recall_decisions",
|
|
27615
|
+
"recall_decision_scan",
|
|
27971
27616
|
"recall_analytics_summary",
|
|
27972
27617
|
"recall_outcome_summary",
|
|
27973
27618
|
"recall_regenerate_summary",
|
|
@@ -29945,7 +29590,19 @@ program.command("guard").description("Check a pending tool call against the deci
|
|
|
29945
29590
|
if (target.token) headers.authorization = `Bearer ${target.token}`;
|
|
29946
29591
|
const res = await fetchWithTimeout2(
|
|
29947
29592
|
`${target.base}/api/decisions/check`,
|
|
29948
|
-
{
|
|
29593
|
+
{
|
|
29594
|
+
method: "POST",
|
|
29595
|
+
headers,
|
|
29596
|
+
// The folder group is resolved HERE, from the directory the hook runs
|
|
29597
|
+
// in. The server has no checkout to read it from, and a lookup per
|
|
29598
|
+
// tool call is exactly the cost this guard cannot afford.
|
|
29599
|
+
body: JSON.stringify({
|
|
29600
|
+
names,
|
|
29601
|
+
via,
|
|
29602
|
+
project: opts.project,
|
|
29603
|
+
workspace: resolveWorkspaceId(process.cwd())?.slice(3)
|
|
29604
|
+
})
|
|
29605
|
+
},
|
|
29949
29606
|
4e3
|
|
29950
29607
|
);
|
|
29951
29608
|
if (!res.ok) return;
|