chat-recall 0.6.9 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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();
@@ -5083,753 +5102,134 @@ var init_settings = __esm({
5083
5102
  }
5084
5103
  });
5085
5104
 
5086
- // ../engine/dist/core/secret-redactor.js
5087
- var secret_redactor_exports = {};
5088
- __export(secret_redactor_exports, {
5089
- DEFAULT_REDACTION_RULES: () => DEFAULT_REDACTION_RULES,
5090
- _clearServerRulePack: () => _clearServerRulePack,
5091
- _resetPrivacyCache: () => _resetPrivacyCache,
5092
- _resetRedactorCache: () => _resetRedactorCache,
5093
- applyChunkPrivacy: () => applyChunkPrivacy,
5094
- findContextualSecrets: () => findContextualSecrets,
5095
- installServerRulePack: () => installServerRulePack,
5096
- isFilePathRedactionEnabled: () => isFilePathRedactionEnabled,
5097
- isRedactionEnabled: () => isRedactionEnabled,
5098
- isToolOutputRedactionEnabled: () => isToolOutputRedactionEnabled,
5099
- redactFilePaths: () => redactFilePaths,
5100
- redactSecrets: () => redactSecrets,
5101
- redactToolResultBody: () => redactToolResultBody,
5102
- scanTextForFindings: () => scanTextForFindings,
5103
- serverRulePackVersion: () => serverRulePackVersion,
5104
- validateRedactionRule: () => validateRedactionRule
5105
+ // ../engine/dist/core/home-approval.js
5106
+ var home_approval_exports = {};
5107
+ __export(home_approval_exports, {
5108
+ approveHome: () => approveHome,
5109
+ declineHome: () => declineHome,
5110
+ grandfatherLegacyHomes: () => grandfatherLegacyHomes,
5111
+ homeDecision: () => homeDecision,
5112
+ isHomeSynced: () => isHomeSynced,
5113
+ normalizeHomePath: () => normalizeHomePath,
5114
+ resetHomeDecision: () => resetHomeDecision
5105
5115
  });
5106
- import { statSync as statSync3 } from "fs";
5107
- import { createHash } from "crypto";
5108
- function secretOf(rule, m) {
5109
- const raw = rule.secret != null ? m[rule.secret] : m[0];
5110
- if (raw == null)
5111
- return null;
5112
- if (rule.where && !rule.where(raw))
5113
- return null;
5114
- return raw;
5115
- }
5116
- function looksLikeSecretToken(t) {
5117
- if (t.length < 32)
5118
- return false;
5119
- if (/^[a-f0-9]+$/i.test(t))
5120
- return true;
5121
- return /[A-Z]/.test(t) && /[a-z]/.test(t) && /[0-9]/.test(t);
5122
- }
5123
- function findContextualSecrets(text) {
5124
- if (!text)
5125
- return [];
5126
- const ranges = [];
5127
- let m;
5128
- SECRET_CONTEXT_RE.lastIndex = 0;
5129
- while ((m = SECRET_CONTEXT_RE.exec(text)) !== null) {
5130
- const winStart = m.index + m[0].length;
5131
- const window = text.slice(winStart, winStart + 80);
5132
- HIGH_ENTROPY_TOKEN_RE.lastIndex = 0;
5133
- let tm;
5134
- while ((tm = HIGH_ENTROPY_TOKEN_RE.exec(window)) !== null) {
5135
- if (!looksLikeSecretToken(tm[0]))
5136
- continue;
5137
- 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))) {
5138
- continue;
5139
- }
5140
- ranges.push({ start: winStart + tm.index, end: winStart + tm.index + tm[0].length });
5141
- break;
5142
- }
5116
+ import { existsSync as existsSync3, readdirSync as readdirSync2, realpathSync } from "fs";
5117
+ import { homedir as homedir2 } from "os";
5118
+ import { join as join3, dirname as dirname3 } from "path";
5119
+ function normalizeHomePath(p) {
5120
+ const expanded = p.startsWith("~") ? join3(homedir2(), p.slice(1)) : p;
5121
+ const trimmed = expanded.replace(/[/\\]+$/, "");
5122
+ try {
5123
+ return realpathSync(trimmed);
5124
+ } catch {
5125
+ return trimmed;
5143
5126
  }
5144
- ranges.sort((a, b) => a.start - b.start);
5127
+ }
5128
+ function legacyAutoSyncedHomes() {
5129
+ const home = homedir2();
5145
5130
  const out = [];
5146
- for (const r of ranges) {
5147
- const last = out[out.length - 1];
5148
- if (last && r.start < last.end) {
5149
- last.end = Math.max(last.end, r.end);
5150
- continue;
5131
+ const add = (dir, marker) => {
5132
+ if (!existsSync3(join3(dir, marker)))
5133
+ return;
5134
+ const n = normalizeHomePath(dir);
5135
+ if (!out.includes(n))
5136
+ out.push(n);
5137
+ };
5138
+ const bases = [
5139
+ { base: join3(home, ".claude"), marker: "projects" },
5140
+ { base: join3(home, ".gemini"), marker: "tmp" },
5141
+ { base: join3(home, ".codex"), marker: "sessions" },
5142
+ { base: join3(home, ".gemini", "antigravity-cli"), marker: "brain" },
5143
+ { base: join3(home, ".local", "share", "opencode"), marker: "opencode.db" },
5144
+ { base: join3(home, ".cursor"), marker: "chats" }
5145
+ ];
5146
+ for (const { base: base3, marker } of bases) {
5147
+ add(base3, marker);
5148
+ const parent = dirname3(base3);
5149
+ const leaf = base3.slice(parent.length + 1);
5150
+ try {
5151
+ for (const e of readdirSync2(parent, { withFileTypes: true })) {
5152
+ if (!e.isDirectory())
5153
+ continue;
5154
+ if (e.name === leaf || !e.name.startsWith(leaf + "-"))
5155
+ continue;
5156
+ if (e.name === ".claude-code")
5157
+ continue;
5158
+ add(join3(parent, e.name), marker);
5159
+ }
5160
+ } catch {
5151
5161
  }
5152
- out.push({ ...r });
5153
5162
  }
5154
5163
  return out;
5155
5164
  }
5156
- function isEnvRedactionRequested() {
5157
- const v = process.env.CHAT_RECALL_REDACT_INDEX;
5158
- if (v === void 0)
5159
- return null;
5160
- return v === "1" || v === "true" || v === "yes";
5161
- }
5162
- function settingsView() {
5163
- let mtimeMs = -1;
5165
+ function grandfatherLegacyHomes() {
5166
+ let s;
5164
5167
  try {
5165
- mtimeMs = statSync3(settingsFilePath()).mtimeMs;
5168
+ s = loadSettings();
5166
5169
  } catch {
5170
+ return { seeded: [] };
5167
5171
  }
5168
- if (cache && cache.mtimeMs === mtimeMs)
5169
- return cache;
5170
- let enabled = false;
5171
- const extra = [];
5172
- try {
5173
- const s = loadSettings();
5174
- enabled = !!s.privacy?.redactIndex;
5175
- for (const r of s.privacy?.redactionRules ?? []) {
5176
- try {
5177
- extra.push({ label: r.label, pattern: new RegExp(r.pattern, "g") });
5178
- } catch {
5179
- }
5172
+ if (s.sources.homesGrandfathered)
5173
+ return { seeded: [] };
5174
+ const legacy = legacyAutoSyncedHomes();
5175
+ const approved = new Set((s.sources.approvedHomes ?? []).map(normalizeHomePath));
5176
+ const seeded = [];
5177
+ for (const p of legacy) {
5178
+ if (!approved.has(p)) {
5179
+ approved.add(p);
5180
+ seeded.push(p);
5180
5181
  }
5182
+ }
5183
+ s.sources.approvedHomes = [...approved];
5184
+ s.sources.homesGrandfathered = true;
5185
+ try {
5186
+ saveSettings(s);
5181
5187
  } catch {
5188
+ return { seeded: [] };
5182
5189
  }
5183
- cache = { mtimeMs, enabled, rules: [...DEFAULT_REDACTION_RULES, ...extra] };
5184
- return cache;
5190
+ return { seeded };
5185
5191
  }
5186
- function compileServerRule(spec) {
5187
- const name = spec.name || "(unnamed)";
5188
- if (!spec.regex)
5189
- return { name, reason: "empty pattern" };
5190
- if (spec.regex.length > MAX_PATTERN_LEN)
5191
- return { name, reason: `pattern longer than ${MAX_PATTERN_LEN} chars` };
5192
- let re;
5192
+ function primaryHomes() {
5193
+ const home = homedir2();
5194
+ const env = process.env;
5195
+ let s;
5193
5196
  try {
5194
- const flags = "g" + (spec.flags || "").replace(/[^ims]/g, "");
5195
- re = new RegExp(spec.regex, flags);
5196
- } catch (e) {
5197
- return { name, reason: `invalid regex: ${e instanceof Error ? e.message : e}` };
5197
+ s = loadSettings().sources;
5198
+ } catch {
5199
+ s = void 0;
5198
5200
  }
5199
- if (re.test(""))
5200
- return { name, reason: "matches the empty string (would redact everything)" };
5201
- re.lastIndex = 0;
5202
- if (re.test(CANARY))
5203
- return { name, reason: "matches benign text \u2014 too broad" };
5204
- re.lastIndex = 0;
5205
- return { label: `${spec.source === "pack" ? "pack" : "tenant"}:${name}`, pattern: re };
5206
- }
5207
- function validateRedactionRule(spec) {
5208
- const out = compileServerRule(spec);
5209
- return "reason" in out ? { ok: false, reason: out.reason } : { ok: true };
5201
+ const opencodeDb = env.CHAT_RECALL_OPENCODE_DB || s?.opencodeDbPath || join3(home, ".local", "share", "opencode", "opencode.db");
5202
+ const candidates2 = [
5203
+ env.CHAT_RECALL_CLAUDE_HOME || s?.claudeHome || join3(home, ".claude"),
5204
+ env.CHAT_RECALL_GEMINI_HOME || s?.geminiHome || join3(home, ".gemini"),
5205
+ env.CHAT_RECALL_CODEX_HOME || s?.codexHome || join3(home, ".codex"),
5206
+ env.CHAT_RECALL_AGY_HOME || s?.agyHome || join3(home, ".gemini", "antigravity-cli"),
5207
+ env.CHAT_RECALL_CURSOR_HOME || s?.cursorHome || join3(home, ".cursor"),
5208
+ dirOf(opencodeDb)
5209
+ ];
5210
+ return candidates2.map(normalizeHomePath);
5210
5211
  }
5211
- function installServerRulePack(pack) {
5212
- const rejected = [];
5213
- const rules = [];
5214
- for (const spec of pack.rules || []) {
5215
- if (rules.length >= MAX_PACK_RULES) {
5216
- rejected.push({ name: spec.name || "(unnamed)", reason: `pack exceeds ${MAX_PACK_RULES} rules` });
5217
- continue;
5218
- }
5219
- const out = compileServerRule(spec);
5220
- if ("reason" in out)
5221
- rejected.push(out);
5222
- else
5223
- rules.push(out);
5212
+ function homeDecision(path) {
5213
+ const p = normalizeHomePath(path);
5214
+ if (primaryHomes().includes(p))
5215
+ return "primary";
5216
+ let s;
5217
+ try {
5218
+ s = loadSettings().sources;
5219
+ } catch {
5220
+ return "pending";
5224
5221
  }
5225
- const version2 = pack.version || "";
5226
- serverPack = { version: version2, rules };
5227
- return { version: version2, accepted: rules.length, rejected };
5228
- }
5229
- function serverRulePackVersion() {
5230
- return serverPack ? serverPack.version || "unversioned" : null;
5231
- }
5232
- function _clearServerRulePack() {
5233
- serverPack = null;
5222
+ if ((s.declinedHomes ?? []).map(normalizeHomePath).includes(p))
5223
+ return "declined";
5224
+ if ((s.approvedHomes ?? []).map(normalizeHomePath).includes(p))
5225
+ return "approved";
5226
+ if ((s.extraClaudeHomes ?? []).map(normalizeHomePath).includes(p))
5227
+ return "approved";
5228
+ return "pending";
5234
5229
  }
5235
- function activeRules() {
5236
- const base3 = settingsView().rules;
5237
- return serverPack && serverPack.rules.length > 0 ? [...base3, ...serverPack.rules] : base3;
5238
- }
5239
- function isRedactionEnabled() {
5240
- const env = isEnvRedactionRequested();
5241
- if (env !== null)
5242
- return env;
5243
- return settingsView().enabled;
5244
- }
5245
- function _resetRedactorCache() {
5246
- cache = null;
5247
- }
5248
- function redactSecrets(text, opts = {}) {
5249
- if (!opts.force && !isRedactionEnabled())
5250
- return text;
5251
- if (!text)
5252
- return text;
5253
- const rules = opts.rules || activeRules();
5254
- let out = text;
5255
- for (const r of rules) {
5256
- out = out.replace(r.pattern, (...args) => {
5257
- const whole = args[0];
5258
- const m = args.slice(0, args.length);
5259
- const secret = secretOf(r, m);
5260
- if (secret == null)
5261
- return whole;
5262
- if (opts.count)
5263
- opts.count.redactions++;
5264
- return whole.slice(0, whole.length - secret.length) + `[REDACTED:${r.label}]`;
5265
- });
5266
- }
5267
- const ctx = findContextualSecrets(out);
5268
- for (let i = ctx.length - 1; i >= 0; i--) {
5269
- const { start, end } = ctx[i];
5270
- if (opts.count)
5271
- opts.count.redactions++;
5272
- out = out.slice(0, start) + "[REDACTED:secret-context]" + out.slice(end);
5273
- }
5274
- return out;
5275
- }
5276
- function maskSecret(s) {
5277
- if (!s)
5278
- return "";
5279
- if (s.length <= 4)
5280
- return "*".repeat(s.length);
5281
- return "*".repeat(Math.min(s.length - 4, 60)) + s.slice(-4);
5282
- }
5283
- function globalRegexFor(pattern) {
5284
- const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
5285
- const key2 = pattern.source + "\0" + flags;
5286
- let re = globalRegexCache.get(key2);
5287
- if (!re) {
5288
- re = new RegExp(pattern.source, flags);
5289
- globalRegexCache.set(key2, re);
5290
- }
5291
- return re;
5292
- }
5293
- function makeLineResolver(text) {
5294
- const starts = [];
5295
- for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1))
5296
- starts.push(i);
5297
- return (index) => {
5298
- let lo = 0, hi = starts.length;
5299
- while (lo < hi) {
5300
- const mid = lo + hi >> 1;
5301
- if (starts[mid] < index)
5302
- lo = mid + 1;
5303
- else
5304
- hi = mid;
5305
- }
5306
- return lo + 1;
5307
- };
5308
- }
5309
- function scanTextForFindings(text, opts = {}) {
5310
- if (!text)
5311
- return [];
5312
- const rules = opts.rules || activeRules();
5313
- const findings = [];
5314
- const seenSpans = /* @__PURE__ */ new Set();
5315
- const lineAt = makeLineResolver(text);
5316
- for (const r of rules) {
5317
- const re = globalRegexFor(r.pattern);
5318
- re.lastIndex = 0;
5319
- let m;
5320
- while ((m = re.exec(text)) !== null) {
5321
- const secret = secretOf(r, m);
5322
- if (secret == null) {
5323
- if (re.lastIndex === m.index)
5324
- re.lastIndex++;
5325
- continue;
5326
- }
5327
- const at = m.index + (m[0].length - secret.length);
5328
- const span = `${at}:${secret.length}`;
5329
- if (!seenSpans.has(span)) {
5330
- seenSpans.add(span);
5331
- findings.push({ rule: r.label, line: lineAt(at), preview: maskSecret(secret) });
5332
- }
5333
- if (re.lastIndex === m.index)
5334
- re.lastIndex++;
5335
- }
5336
- }
5337
- for (const { start, end } of findContextualSecrets(text)) {
5338
- const line = lineAt(start);
5339
- findings.push({ rule: "secret-context", line, preview: maskSecret(text.slice(start, end)) });
5340
- }
5341
- return findings;
5342
- }
5343
- function privacyView() {
5344
- let mtimeMs = -1;
5345
- try {
5346
- mtimeMs = statSync3(settingsFilePath()).mtimeMs;
5347
- } catch {
5348
- }
5349
- if (privacyCache && privacyCache.mtimeMs === mtimeMs)
5350
- return privacyCache;
5351
- let redactToolOutputs = false, redactFilePaths2 = false;
5352
- try {
5353
- const s = loadSettings();
5354
- redactToolOutputs = !!s.privacy?.redactToolOutputs;
5355
- redactFilePaths2 = !!s.privacy?.redactFilePaths;
5356
- } catch {
5357
- }
5358
- privacyCache = { mtimeMs, redactToolOutputs, redactFilePaths: redactFilePaths2 };
5359
- return privacyCache;
5360
- }
5361
- function _resetPrivacyCache() {
5362
- privacyCache = null;
5363
- }
5364
- function isToolOutputRedactionEnabled() {
5365
- return privacyView().redactToolOutputs;
5366
- }
5367
- function isFilePathRedactionEnabled() {
5368
- return privacyView().redactFilePaths;
5369
- }
5370
- function redactFilePaths(text) {
5371
- if (!text)
5372
- return text;
5373
- return text.replace(PATH_REGEX, (m) => {
5374
- if (m.startsWith("[REDACTED:"))
5375
- return m;
5376
- const h = createHash("sha256").update(m).digest("hex").slice(0, 12);
5377
- return `[path:${h}]`;
5378
- });
5379
- }
5380
- function applyChunkPrivacy(text) {
5381
- let out = redactSecrets(text);
5382
- if (privacyView().redactFilePaths)
5383
- out = redactFilePaths(out);
5384
- return out;
5385
- }
5386
- function redactToolResultBody(toolName, body) {
5387
- if (!body)
5388
- return body;
5389
- return `[redacted: ${toolName} output, ${body.length} chars]`;
5390
- }
5391
- var DEFAULT_REDACTION_RULES, SECRET_CONTEXT_RE, HIGH_ENTROPY_TOKEN_RE, cache, CANARY, MAX_PACK_RULES, MAX_PATTERN_LEN, serverPack, globalRegexCache, privacyCache, PATH_REGEX;
5392
- var init_secret_redactor = __esm({
5393
- "../engine/dist/core/secret-redactor.js"() {
5394
- "use strict";
5395
- init_settings();
5396
- DEFAULT_REDACTION_RULES = [
5397
- // AWS access tokens. Prefix-anchored: AKIA, ASIA, ABIA (long-term,
5398
- // STS, EC2). Always exactly 16 hex/alphanumeric after the prefix.
5399
- // NOTE: this matches only the access-key-ID, NOT the secret key value —
5400
- // the `env-secret` rule below covers AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN.
5401
- { label: "aws-access-token", pattern: /\b(?:AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}\b/g },
5402
- // AWS STS session tokens. These are long base64 blobs with NO `NAME=` context
5403
- // when pasted bare into prose/code, so `env-secret` misses them — but they
5404
- // reliably start with one of a handful of base64 prefixes (the encoded
5405
- // `{"...` JSON header). Prefix-anchored → low false-positive. This is the
5406
- // bare-value form that leaked (`search('IQoJEXAMPLEfakeSESSIONtokenNOTreal01234567890abcDEF==...')`).
5407
- { label: "aws-session-token", pattern: /\b(?:IQoJ|FwoG|FwoH|FQoG|FQoH|FwoD)[A-Za-z0-9/+]{40,}={0,2}/g },
5408
- // Env/shell-assignment secrets — a VAR whose name ends in KEY/SECRET/TOKEN/
5409
- // PASSWORD/CREDENTIAL, then `=`/`:`, then the value. Zero-width lookbehind so
5410
- // only the VALUE is redacted (the var name stays for context). This is the
5411
- // dominant real-world leak vector (`export` dumps, .env files, `curl -H`,
5412
- // tool output) and is exactly what leaked AWS_SECRET_ACCESS_KEY and
5413
- // AWS_SESSION_TOKEN in cleartext while the access-key-ID rule above caught
5414
- // only the (least-sensitive) ID.
5415
- // Suffix list deliberately broad: DB_PASS (our own helm chart's name!),
5416
- // NPM_AUTH, SENTRY_DSN, SESSION/COOKIE secrets and BEARER vars all leaked
5417
- // past the original KEY/SECRET/TOKEN/PASSWORD-only list. Values under 12
5418
- // chars don't redact, so flag-ish vars (BYPASS=true) stay readable.
5419
- // Context CONSUMED, secret in group 1 — see RedactionRule.secret. As a
5420
- // lookbehind this was the second most expensive rule in the collector.
5421
- { 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 },
5422
- // Credentials embedded in a connection URL: scheme://user:PASSWORD@host.
5423
- // Redacts only the password segment (keeps scheme/user/host for context).
5424
- //
5425
- // Scheme-agnostic on purpose. The old rule allowlisted eight DB schemes, so
5426
- // `https://user:pass@host`, `ssh://`, `ftp://`, `smtp://` and every other
5427
- // userinfo URL walked through in cleartext — and a `jdbc:postgresql://…`
5428
- // prefix broke the \b anchor for the one family it did cover. Any scheme
5429
- // followed by userinfo is a credential, so match the shape, not a list.
5430
- { label: "url-password", secret: 1, pattern: /\b[a-zA-Z][a-zA-Z0-9+.-]{1,31}:\/\/[^:@\s/]{1,64}:([^@\s/]{1,256})(?=@)/g },
5431
- // Bare AWS secret-access-key VALUE: EXACTLY 40 base64 chars, word-bounded,
5432
- // containing upper+lower+digit. This is the gitleaks heuristic — the mixed-case
5433
- // requirement excludes 40-char lowercase-hex git SHAs, and the exact-40 bound
5434
- // excludes longer blobs/tokens. It catches the value when it appears bare in
5435
- // prose with NO `NAME=` and NO nearby context word (e.g. inside a markdown
5436
- // table or quoted in analysis) — the residual leak path the env-secret and
5437
- // contextual rules both miss. Placed AFTER env-secret/url-password so the
5438
- // keyed/URL forms keep their more-specific labels.
5439
- // The three stacked lookaheads each re-scanned the same 40 characters, which
5440
- // made this the MOST expensive rule of all 106 the collector runs (13.1% of a
5441
- // 29.5 MB scan). The mixed-case-plus-digit test is now one JS pass.
5442
- {
5443
- label: "aws-secret-key",
5444
- secret: 1,
5445
- pattern: /(?<![A-Za-z0-9/+])([A-Za-z0-9/+]{40})(?![A-Za-z0-9/+])/g,
5446
- where: (v) => /[a-z]/.test(v) && /[A-Z]/.test(v) && /[0-9]/.test(v)
5447
- },
5448
- // GitHub personal access tokens.
5449
- { label: "github-pat", pattern: /\bghp_[A-Za-z0-9]{36}\b/g },
5450
- // GitHub fine-grained PATs.
5451
- { label: "github-fine", pattern: /\bgithub_pat_[A-Za-z0-9_]{82}\b/g },
5452
- // GitHub OAuth.
5453
- { label: "github-oauth", pattern: /\bgho_[A-Za-z0-9]{36}\b/g },
5454
- // GitHub app + refresh.
5455
- { label: "github-app", pattern: /\b(?:ghs|ghu|ghr)_[A-Za-z0-9]{36}\b/g },
5456
- // Slack bot/user/app tokens.
5457
- { label: "slack-token", pattern: /\bxox[abopr]-[A-Za-z0-9-]{10,}\b/g },
5458
- // Slack incoming webhook URL.
5459
- { label: "slack-webhook", pattern: /\bhttps:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+\b/g },
5460
- // Stripe live + test keys.
5461
- { label: "stripe-key", pattern: /\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{24,}\b/g },
5462
- // Anthropic.
5463
- { label: "anthropic-key", pattern: /\bsk-ant-[a-zA-Z0-9_-]{40,}\b/g },
5464
- // OpenAI. Two shapes, deliberately kept apart. The classic key is
5465
- // `sk-` + base62 with NO hyphens; the modern scoped keys carry an explicit
5466
- // proj/svcacct/admin segment first. Allowing hyphens after a bare `sk-`
5467
- // (which is what admitted sk-proj- in the first place) also matches ordinary
5468
- // kebab-case text — `sk-fix-the-login-redirect-bug`, `.sk-button-primary`,
5469
- // `sk-2024-01-01-backup` — and this corpus is full of branch names and CSS
5470
- // classes. Requiring the segment keeps sk-proj- coverage without that.
5471
- { label: "openai-key", pattern: /\bsk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,}\b|\bsk-[A-Za-z0-9]{20,}\b/g },
5472
- // Google API key (AIza…).
5473
- { label: "google-api-key", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
5474
- // Generic JWT — three base64url segments.
5475
- { label: "jwt", pattern: /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g },
5476
- // Private key blocks (PEM). The label is a free word run rather than a fixed
5477
- // list, so ENCRYPTED PRIVATE KEY and PGP PRIVATE KEY BLOCK are covered
5478
- // without another enumeration, and \1 forces END to name the same kind as
5479
- // BEGIN.
5480
- //
5481
- // The body is [^-], NOT [\s\S]*?. That is a performance boundary, not a
5482
- // style choice. An unanchored prefix with an unbounded lazy gap re-scans to
5483
- // end-of-input from every candidate start, so text carrying many BEGIN
5484
- // markers and no matching END costs O(n^2): 500KB of that measured 810ms
5485
- // here, and this runs synchronously on the server's event loop over
5486
- // tenant-supplied text (services/secret-rescan.ts). A PEM body is base64
5487
- // plus headers and contains no '-', so excluding it makes the scan stop at
5488
- // the next delimiter instead of at EOF — same matches, linear cost.
5489
- { label: "private-key", pattern: /-----BEGIN ([A-Z0-9 ]{0,32})PRIVATE KEY(?: BLOCK)?-----[^-]{0,10000}?-----END \1PRIVATE KEY(?: BLOCK)?-----/g },
5490
- // PuTTY keys are not PEM. Match the Private-Lines payload — the actual key
5491
- // material — rather than spanning the whole file, which would need the same
5492
- // unbounded gap the rule above just removed. Line-anchored and base64-only,
5493
- // so it cannot run away.
5494
- { label: "putty-key", pattern: /^Private-Lines:[ \t]*\d{1,4}[\r\n]+(?:[A-Za-z0-9+/=]{8,120}[\r\n]+){1,400}/gm },
5495
- // npm tokens (uuid-shaped — overlap with our UUID filter; intentional).
5496
- { label: "npm-token", pattern: /\bnpm_[A-Za-z0-9]{36}\b/g },
5497
- // .npmrc auth lines: //registry.npmjs.org/:_authToken=VALUE (the key is not
5498
- // ALL-CAPS, so the env-secret rule above never matched it). The leading `:`
5499
- // is required — it is what makes this npmrc syntax rather than any shell var
5500
- // ending in _AUTH, which env-secret already owns and labels better.
5501
- // [^\s"'`]+ so the closing quote is not swallowed, and a ${VAR} placeholder
5502
- // is left alone — redacting it destroys a config example and hides nothing.
5503
- // The rule the perf profile caught the wedged daemon inside: 98.94% of samples.
5504
- { label: "npmrc-auth", secret: 1, pattern: /:_(?:authToken|password|auth)[ \t]*=[ \t]*(?!\$\{)([^\s"'`]{12,})/gi },
5505
- // HTTP Authorization headers. env-secret requires an ALL-CAPS name, and
5506
- // `Authorization` is mixed case, so both Bearer and Basic walked straight
5507
- // through — including short opaque tokens the contextual pass also misses
5508
- // (it needs 32+ chars AND mixed-case-plus-digit).
5509
- // Same two guards as npmrc-auth: stop at a quote, and skip the obvious
5510
- // placeholders that fill documentation (<YOUR_TOKEN>, ${TOKEN}, xxx…).
5511
- { label: "auth-header", secret: 1, pattern: /\bauthorization[ \t]*:[ \t]*(?:bearer|basic|token)[ \t]+(?![<$])([^\s"'`<]{8,})/gi },
5512
- // Vendor tokens with unambiguous prefixes. Each is a fixed literal, so a
5513
- // false positive is essentially impossible and the cost of listing is low.
5514
- { label: "gitlab-pat", pattern: /\bglpat-[A-Za-z0-9_-]{20,}\b/g },
5515
- { label: "slack-app-token", pattern: /\bxapp-\d-[A-Z0-9]{8,}-\d{10,}-[a-f0-9]{32,}\b/g },
5516
- { label: "sendgrid-key", pattern: /\bSG\.[A-Za-z0-9_-]{20,24}\.[A-Za-z0-9_-]{40,50}\b/g },
5517
- // (?<![-\w]) so `cache-key-<md5>` is not read as a mailgun key.
5518
- { label: "mailgun-key", pattern: /(?<![-\w])key-[a-f0-9]{32}\b/g },
5519
- { label: "digitalocean-pat", pattern: /\bdop_v1_[a-f0-9]{64}\b/g },
5520
- { label: "huggingface-token", pattern: /\bhf_[A-Za-z0-9]{30,}\b/g },
5521
- { label: "shopify-token", pattern: /\bshp(?:at|ca|pa|ss)_[a-fA-F0-9]{32}\b/g },
5522
- { label: "pypi-token", pattern: /\bpypi-AgEIcHlwaS5vcmc[A-Za-z0-9_-]{50,}\b/g },
5523
- // No twilio-sid rule: AC…/SK… are Twilio SIDs, which are public account and
5524
- // key identifiers, not the auth token. Redacting them hid nothing and
5525
- // collided with 32-hex etags and MD5 sums.
5526
- // No discord-bot-token rule either: the shape (word.word.word) matched
5527
- // ordinary dotted symbols such as a.b.ApplicationContextProviderImpl.
5528
- { label: "telegram-bot-token", pattern: /\b\d{8,10}:AA[A-Za-z0-9_-]{32,}\b/g },
5529
- { label: "doppler-token", pattern: /\bdp\.(?:pt|st|sa|ct)\.[A-Za-z0-9_-]{20,}\b/g },
5530
- // Azure Storage SAS signature — the `sig=` parameter is the actual secret.
5531
- { label: "azure-sas", secret: 1, pattern: /[?&]sig=([A-Za-z0-9%/+=]{20,})/g }
5532
- ];
5533
- SECRET_CONTEXT_RE = /\b(?:secret|password|passwd|credential|access[ _-]?key|private[ _-]?key|api[ _-]?key|auth(?:oriz\w*)?|bearer|token)\b/gi;
5534
- HIGH_ENTROPY_TOKEN_RE = /[A-Za-z0-9/+_-]{32,}/g;
5535
- cache = null;
5536
- CANARY = "the quick brown fox jumps over the lazy dog 0123456789 commit 8ba1f109551bd432803012645ac136ddd475d0fb src/index.ts:42";
5537
- MAX_PACK_RULES = 200;
5538
- MAX_PATTERN_LEN = 512;
5539
- serverPack = null;
5540
- globalRegexCache = /* @__PURE__ */ new Map();
5541
- privacyCache = null;
5542
- PATH_REGEX = /(?<![A-Za-z0-9_])(?:\/[^\s'"`<>]+|[A-Za-z]:\\[^\s'"`<>]+)/g;
5543
- }
5544
- });
5545
-
5546
- // ../engine/dist/parsers/chunker.js
5547
- function stripInjectedBanners(text) {
5548
- let result = text;
5549
- for (const re of INJECTED_BANNERS)
5550
- result = result.replace(re, " ");
5551
- return result;
5552
- }
5553
- function cleanText(text) {
5554
- let result = text.replace(/<system-reminder>.*?<\/system-reminder>/gs, "");
5555
- result = stripInjectedBanners(result);
5556
- result = result.replace(/\x1b\[[0-9;]*m/g, "");
5557
- result = result.replace(/\s+/g, " ");
5558
- return result.trim();
5559
- }
5560
- function windowText(text, maxChars, maxWindows = 12) {
5561
- const clean = cleanText(text);
5562
- if (clean.length <= maxChars)
5563
- return clean ? [clean] : [];
5564
- const out = [];
5565
- const overlap = 80;
5566
- const step = Math.max(1, maxChars - overlap);
5567
- for (let i = 0; i < clean.length && out.length < maxWindows; i += step) {
5568
- out.push(clean.slice(i, i + maxChars));
5569
- }
5570
- return out;
5571
- }
5572
- function chunkSession(entry, content, maxChunks = 80, maxTokensPerChunk = 500) {
5573
- const chunks = [];
5574
- const maxChars = maxTokensPerChunk * 4;
5575
- const baseData = {
5576
- sessionId: entry.sessionId,
5577
- projectPath: entry.projectPath,
5578
- created: entry.created,
5579
- modified: entry.modified,
5580
- mtime: entry.fileMtime,
5581
- firstPrompt: entry.firstPrompt || content.firstPrompt,
5582
- sourceFile: content.sessionPath
5583
- };
5584
- for (let i = 0; i < Math.min(2, content.summaries.length); i++) {
5585
- const summary = content.summaries[i];
5586
- if (!summary?.trim())
5587
- continue;
5588
- const chunkText = cleanText(summary.slice(0, maxChars));
5589
- if (chunkText.length > 8e3) {
5590
- chunks.push({
5591
- chunkId: `${entry.sessionId}_summary_${i}`,
5592
- chunkType: "summary",
5593
- text: chunkText.slice(0, 8e3),
5594
- sourceLine: 0,
5595
- ...baseData
5596
- });
5597
- } else {
5598
- chunks.push({
5599
- chunkId: `${entry.sessionId}_summary_${i}`,
5600
- chunkType: "summary",
5601
- text: chunkText,
5602
- sourceLine: 0,
5603
- ...baseData
5604
- });
5605
- }
5606
- }
5607
- if (chunks.length >= maxChunks) {
5608
- return chunks.slice(0, maxChunks);
5609
- }
5610
- const firstPrompt = entry.firstPrompt || content.firstPrompt;
5611
- if (firstPrompt?.trim()) {
5612
- const firstLine = content.userMessages[0]?.lineNumber || 0;
5613
- chunks.push({
5614
- chunkId: `${entry.sessionId}_first_prompt`,
5615
- chunkType: "first_prompt",
5616
- text: cleanText(firstPrompt.slice(0, maxChars)),
5617
- sourceLine: firstLine,
5618
- ...baseData
5619
- });
5620
- }
5621
- if (chunks.length >= maxChunks) {
5622
- return chunks.slice(0, maxChunks);
5623
- }
5624
- const userStart = firstPrompt ? 1 : 0;
5625
- for (let i = userStart; i < content.userMessages.length; i++) {
5626
- if (chunks.length >= maxChunks)
5627
- break;
5628
- const msg = content.userMessages[i];
5629
- if (!msg.text.trim())
5630
- continue;
5631
- const windows = windowText(msg.text, maxChars);
5632
- for (let w = 0; w < windows.length; w++) {
5633
- if (chunks.length >= maxChunks)
5634
- break;
5635
- chunks.push({
5636
- chunkId: `${entry.sessionId}_user_${i}_${w}`,
5637
- chunkType: "user_context",
5638
- text: windows[w],
5639
- sourceLine: msg.lineNumber,
5640
- ...baseData
5641
- });
5642
- }
5643
- }
5644
- if (chunks.length >= maxChunks) {
5645
- return chunks.slice(0, maxChunks);
5646
- }
5647
- for (let i = 0; i < content.assistantMessages.length; i++) {
5648
- if (chunks.length >= maxChunks)
5649
- break;
5650
- const msg = content.assistantMessages[i];
5651
- if (!msg.text.trim() || msg.text.length <= 50)
5652
- continue;
5653
- const windows = windowText(msg.text, maxChars);
5654
- for (let w = 0; w < windows.length; w++) {
5655
- if (chunks.length >= maxChunks)
5656
- break;
5657
- chunks.push({
5658
- chunkId: `${entry.sessionId}_assistant_${i}_${w}`,
5659
- chunkType: "assistant",
5660
- text: windows[w],
5661
- sourceLine: msg.lineNumber,
5662
- ...baseData
5663
- });
5664
- }
5665
- }
5666
- if (chunks.length >= maxChunks) {
5667
- return chunks.slice(0, maxChunks);
5668
- }
5669
- if (content.toolResults.length > 0) {
5670
- const webSearches = content.toolResults.filter((r) => r.contentType === "web_search");
5671
- const otherResults = content.toolResults.filter((r) => r.contentType !== "web_search");
5672
- const resultsToProcess = [...webSearches.slice(0, 5), ...otherResults.slice(0, 10)];
5673
- const stripBodies = isToolOutputRedactionEnabled();
5674
- for (let i = 0; i < resultsToProcess.length; i++) {
5675
- if (chunks.length >= maxChunks)
5676
- break;
5677
- const result = resultsToProcess[i];
5678
- if (result.text.trim()) {
5679
- const body = stripBodies && result.contentType !== "web_search" ? redactToolResultBody(result.contentType, result.text) : result.text.slice(0, maxChars);
5680
- chunks.push({
5681
- chunkId: `${entry.sessionId}_${result.contentType}_${i}`,
5682
- chunkType: result.contentType,
5683
- text: cleanText(body),
5684
- sourceLine: result.lineNumber,
5685
- ...baseData
5686
- });
5687
- }
5688
- }
5689
- }
5690
- return chunks.slice(0, maxChunks);
5691
- }
5692
- var INJECTED_BANNERS;
5693
- var init_chunker = __esm({
5694
- "../engine/dist/parsers/chunker.js"() {
5695
- "use strict";
5696
- init_secret_redactor();
5697
- INJECTED_BANNERS = [
5698
- /MCP issues detected\. ?Run \/mcp list for status\.?/g,
5699
- /Context low[^\n]*Run \/compact[^\n]*/g,
5700
- /API Error:[^\n]{0,120}/g
5701
- ];
5702
- }
5703
- });
5704
-
5705
- // ../engine/dist/core/home-approval.js
5706
- var home_approval_exports = {};
5707
- __export(home_approval_exports, {
5708
- approveHome: () => approveHome,
5709
- declineHome: () => declineHome,
5710
- grandfatherLegacyHomes: () => grandfatherLegacyHomes,
5711
- homeDecision: () => homeDecision,
5712
- isHomeSynced: () => isHomeSynced,
5713
- normalizeHomePath: () => normalizeHomePath,
5714
- resetHomeDecision: () => resetHomeDecision
5715
- });
5716
- import { existsSync as existsSync3, readdirSync as readdirSync2, realpathSync } from "fs";
5717
- import { homedir as homedir2 } from "os";
5718
- import { join as join3, dirname as dirname3 } from "path";
5719
- function normalizeHomePath(p) {
5720
- const expanded = p.startsWith("~") ? join3(homedir2(), p.slice(1)) : p;
5721
- const trimmed = expanded.replace(/[/\\]+$/, "");
5722
- try {
5723
- return realpathSync(trimmed);
5724
- } catch {
5725
- return trimmed;
5726
- }
5727
- }
5728
- function legacyAutoSyncedHomes() {
5729
- const home = homedir2();
5730
- const out = [];
5731
- const add = (dir, marker) => {
5732
- if (!existsSync3(join3(dir, marker)))
5733
- return;
5734
- const n = normalizeHomePath(dir);
5735
- if (!out.includes(n))
5736
- out.push(n);
5737
- };
5738
- const bases = [
5739
- { base: join3(home, ".claude"), marker: "projects" },
5740
- { base: join3(home, ".gemini"), marker: "tmp" },
5741
- { base: join3(home, ".codex"), marker: "sessions" },
5742
- { base: join3(home, ".gemini", "antigravity-cli"), marker: "brain" },
5743
- { base: join3(home, ".local", "share", "opencode"), marker: "opencode.db" },
5744
- { base: join3(home, ".cursor"), marker: "chats" }
5745
- ];
5746
- for (const { base: base3, marker } of bases) {
5747
- add(base3, marker);
5748
- const parent = dirname3(base3);
5749
- const leaf = base3.slice(parent.length + 1);
5750
- try {
5751
- for (const e of readdirSync2(parent, { withFileTypes: true })) {
5752
- if (!e.isDirectory())
5753
- continue;
5754
- if (e.name === leaf || !e.name.startsWith(leaf + "-"))
5755
- continue;
5756
- if (e.name === ".claude-code")
5757
- continue;
5758
- add(join3(parent, e.name), marker);
5759
- }
5760
- } catch {
5761
- }
5762
- }
5763
- return out;
5764
- }
5765
- function grandfatherLegacyHomes() {
5766
- let s;
5767
- try {
5768
- s = loadSettings();
5769
- } catch {
5770
- return { seeded: [] };
5771
- }
5772
- if (s.sources.homesGrandfathered)
5773
- return { seeded: [] };
5774
- const legacy = legacyAutoSyncedHomes();
5775
- const approved = new Set((s.sources.approvedHomes ?? []).map(normalizeHomePath));
5776
- const seeded = [];
5777
- for (const p of legacy) {
5778
- if (!approved.has(p)) {
5779
- approved.add(p);
5780
- seeded.push(p);
5781
- }
5782
- }
5783
- s.sources.approvedHomes = [...approved];
5784
- s.sources.homesGrandfathered = true;
5785
- try {
5786
- saveSettings(s);
5787
- } catch {
5788
- return { seeded: [] };
5789
- }
5790
- return { seeded };
5791
- }
5792
- function primaryHomes() {
5793
- const home = homedir2();
5794
- const env = process.env;
5795
- let s;
5796
- try {
5797
- s = loadSettings().sources;
5798
- } catch {
5799
- s = void 0;
5800
- }
5801
- const opencodeDb = env.CHAT_RECALL_OPENCODE_DB || s?.opencodeDbPath || join3(home, ".local", "share", "opencode", "opencode.db");
5802
- const candidates2 = [
5803
- env.CHAT_RECALL_CLAUDE_HOME || s?.claudeHome || join3(home, ".claude"),
5804
- env.CHAT_RECALL_GEMINI_HOME || s?.geminiHome || join3(home, ".gemini"),
5805
- env.CHAT_RECALL_CODEX_HOME || s?.codexHome || join3(home, ".codex"),
5806
- env.CHAT_RECALL_AGY_HOME || s?.agyHome || join3(home, ".gemini", "antigravity-cli"),
5807
- env.CHAT_RECALL_CURSOR_HOME || s?.cursorHome || join3(home, ".cursor"),
5808
- dirOf(opencodeDb)
5809
- ];
5810
- return candidates2.map(normalizeHomePath);
5811
- }
5812
- function homeDecision(path) {
5813
- const p = normalizeHomePath(path);
5814
- if (primaryHomes().includes(p))
5815
- return "primary";
5816
- let s;
5817
- try {
5818
- s = loadSettings().sources;
5819
- } catch {
5820
- return "pending";
5821
- }
5822
- if ((s.declinedHomes ?? []).map(normalizeHomePath).includes(p))
5823
- return "declined";
5824
- if ((s.approvedHomes ?? []).map(normalizeHomePath).includes(p))
5825
- return "approved";
5826
- if ((s.extraClaudeHomes ?? []).map(normalizeHomePath).includes(p))
5827
- return "approved";
5828
- return "pending";
5829
- }
5830
- function isHomeSynced(path) {
5831
- const d = homeDecision(path);
5832
- return d === "primary" || d === "approved";
5230
+ function isHomeSynced(path) {
5231
+ const d = homeDecision(path);
5232
+ return d === "primary" || d === "approved";
5833
5233
  }
5834
5234
  function setDecision(path, decision) {
5835
5235
  const p = normalizeHomePath(path);
@@ -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 statSync4, openSync, readSync, closeSync } from "fs";
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,44 +5734,6 @@ 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"));
@@ -6382,40 +5744,6 @@ function getSubagentPaths(sessionPath) {
6382
5744
  return [];
6383
5745
  return readdirSync5(subagentsDir).filter((f) => f.endsWith(".jsonl")).sort().map((f) => join5(subagentsDir, f));
6384
5746
  }
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;
6418
- }
6419
5747
  function readCwdFromJsonl(sessionPath) {
6420
5748
  try {
6421
5749
  if (!existsSync5(sessionPath))
@@ -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 = {
@@ -6675,98 +5987,32 @@ ${resultText}` : resultText;
6675
5987
  content.toolResults.push({
6676
5988
  text: searchText.slice(0, 2e3),
6677
5989
  lineNumber: lineNum,
6678
- contentType: "web_search"
6679
- });
6680
- toolCount++;
6681
- }
6682
- }
6683
- } catch {
6684
- continue;
6685
- }
6686
- }
6687
- }
6688
- content.metadata.toolsUsed = [...content.toolsUsed];
6689
- content.metadata.modelsUsed = [...modelsUsed];
6690
- content.metadata.filesModified = [...filesModified];
6691
- content.metadata.messageCount = content.userMessages.length + content.assistantMessages.length;
6692
- content.metadata.costUsd = estimateCostUsd(content.metadata.modelsUsed, {
6693
- inputTokens: content.metadata.inputTokens,
6694
- outputTokens: content.metadata.outputTokens,
6695
- cacheReadTokens: content.metadata.cacheReadTokens,
6696
- cacheCreationTokens: content.metadata.cacheCreationTokens
6697
- });
6698
- return content;
6699
- }
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;
5990
+ contentType: "web_search"
5991
+ });
5992
+ toolCount++;
6742
5993
  }
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
5994
  }
5995
+ } catch {
5996
+ continue;
6762
5997
  }
6763
5998
  }
6764
5999
  }
6000
+ content.metadata.toolsUsed = [...content.toolsUsed];
6001
+ content.metadata.modelsUsed = [...modelsUsed];
6002
+ content.metadata.filesModified = [...filesModified];
6003
+ content.metadata.messageCount = content.userMessages.length + content.assistantMessages.length;
6004
+ content.metadata.costUsd = estimateCostUsd(content.metadata.modelsUsed, {
6005
+ inputTokens: content.metadata.inputTokens,
6006
+ outputTokens: content.metadata.outputTokens,
6007
+ cacheReadTokens: content.metadata.cacheReadTokens,
6008
+ cacheCreationTokens: content.metadata.cacheCreationTokens
6009
+ });
6010
+ return content;
6765
6011
  }
6766
6012
  var init_session = __esm({
6767
6013
  "../engine/dist/parsers/session.js"() {
6768
6014
  "use strict";
6769
- init_chunker();
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 as createHash2 } from "crypto";
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 = createHash2("sha1");
6548
+ const h = createHash("sha1");
7303
6549
  h.update(c.tool);
7304
6550
  for (const f of c.files) {
7305
6551
  h.update("\0");
@@ -8846,11 +8092,11 @@ var init_opencode = __esm({
8846
8092
  });
8847
8093
 
8848
8094
  // ../engine/dist/core/backends/tail-read.js
8849
- import { openSync as openSync2, readSync as readSync2, closeSync as closeSync2, statSync as statSync5 } from "node:fs";
8095
+ import { openSync as openSync2, readSync as readSync2, closeSync as closeSync2, statSync as statSync4 } from "node:fs";
8850
8096
  function readTailFromOffset(path, offset) {
8851
8097
  let size = 0;
8852
8098
  try {
8853
- size = statSync5(path).size;
8099
+ size = statSync4(path).size;
8854
8100
  } catch {
8855
8101
  return { text: "", newOffset: offset };
8856
8102
  }
@@ -8895,7 +8141,7 @@ var init_tail_read = __esm({
8895
8141
  });
8896
8142
 
8897
8143
  // ../engine/dist/core/backends/codex.js
8898
- import { readdirSync as readdirSync6, readFileSync as readFileSync3, statSync as statSync6, openSync as openSync3, readSync as readSync3, closeSync as closeSync3 } from "fs";
8144
+ import { readdirSync as readdirSync6, readFileSync as readFileSync3, statSync as statSync5, openSync as openSync3, readSync as readSync3, closeSync as closeSync3 } from "fs";
8899
8145
  import { basename as basename7, dirname as dirname9, join as join9 } from "path";
8900
8146
  function parseShellWrites(cmd) {
8901
8147
  const out = [];
@@ -9103,7 +8349,7 @@ var init_codex2 = __esm({
9103
8349
  return null;
9104
8350
  let mtime = 0;
9105
8351
  try {
9106
- mtime = statSync6(located.path).mtimeMs;
8352
+ mtime = statSync5(located.path).mtimeMs;
9107
8353
  } catch {
9108
8354
  }
9109
8355
  return {
@@ -9123,7 +8369,7 @@ var init_codex2 = __esm({
9123
8369
  for (const filePath of walkRollouts(root)) {
9124
8370
  let stat3;
9125
8371
  try {
9126
- stat3 = statSync6(filePath);
8372
+ stat3 = statSync5(filePath);
9127
8373
  } catch {
9128
8374
  continue;
9129
8375
  }
@@ -9319,7 +8565,7 @@ var init_codex2 = __esm({
9319
8565
  let mtime = 0;
9320
8566
  try {
9321
8567
  raw = readFileSync3(path, "utf-8");
9322
- mtime = statSync6(path).mtimeMs;
8568
+ mtime = statSync5(path).mtimeMs;
9323
8569
  } catch {
9324
8570
  return [];
9325
8571
  }
@@ -9419,7 +8665,7 @@ var init_codex2 = __esm({
9419
8665
  const events = this.readEvents(this.toRawId(id));
9420
8666
  let fileMtime = 0;
9421
8667
  try {
9422
- fileMtime = statSync6(located.path).mtimeMs;
8668
+ fileMtime = statSync5(located.path).mtimeMs;
9423
8669
  } catch {
9424
8670
  }
9425
8671
  return liveScanEditsFromEvents(events, this.fileToolMap, {
@@ -9483,7 +8729,7 @@ var init_codex2 = __esm({
9483
8729
  const p = join9(dir, entry.name);
9484
8730
  let st;
9485
8731
  try {
9486
- st = statSync6(p);
8732
+ st = statSync5(p);
9487
8733
  } catch {
9488
8734
  continue;
9489
8735
  }
@@ -9550,7 +8796,7 @@ var init_codex2 = __esm({
9550
8796
  let mtime = 0;
9551
8797
  const push = (path, name) => {
9552
8798
  try {
9553
- const st = statSync6(path);
8799
+ const st = statSync5(path);
9554
8800
  files.push({ name, bytes: readFileSync3(path) });
9555
8801
  if (st.mtimeMs > mtime)
9556
8802
  mtime = st.mtimeMs;
@@ -9592,7 +8838,7 @@ var init_codex2 = __esm({
9592
8838
  if (!loc)
9593
8839
  return 0;
9594
8840
  try {
9595
- return statSync6(loc.path).size;
8841
+ return statSync5(loc.path).size;
9596
8842
  } catch {
9597
8843
  return 0;
9598
8844
  }
@@ -9609,7 +8855,7 @@ var init_codex2 = __esm({
9609
8855
  });
9610
8856
 
9611
8857
  // ../engine/dist/core/backends/agy.js
9612
- import { existsSync as existsSync8, readdirSync as readdirSync7, readFileSync as readFileSync4, statSync as statSync7 } from "fs";
8858
+ import { existsSync as existsSync8, readdirSync as readdirSync7, readFileSync as readFileSync4, statSync as statSync6 } from "fs";
9613
8859
  import { basename as basename8, dirname as dirname10, join as join10 } from "path";
9614
8860
  function agyTranscriptPath(logsDir) {
9615
8861
  const full = join10(logsDir, "transcript_full.jsonl");
@@ -9802,7 +9048,7 @@ var init_agy = __esm({
9802
9048
  return null;
9803
9049
  let mtime = 0;
9804
9050
  try {
9805
- mtime = statSync7(path).mtimeMs;
9051
+ mtime = statSync6(path).mtimeMs;
9806
9052
  } catch {
9807
9053
  }
9808
9054
  const projectPath = resolveAgyProject(dirname10(path), this.homeDir());
@@ -9830,7 +9076,7 @@ var init_agy = __esm({
9830
9076
  for (const rawId of sessionDirs) {
9831
9077
  const sessionPath = join10(brainDir, rawId);
9832
9078
  try {
9833
- if (!statSync7(sessionPath).isDirectory())
9079
+ if (!statSync6(sessionPath).isDirectory())
9834
9080
  continue;
9835
9081
  } catch {
9836
9082
  continue;
@@ -9840,7 +9086,7 @@ var init_agy = __esm({
9840
9086
  continue;
9841
9087
  let stat3;
9842
9088
  try {
9843
- stat3 = statSync7(filePath);
9089
+ stat3 = statSync6(filePath);
9844
9090
  } catch {
9845
9091
  continue;
9846
9092
  }
@@ -10105,7 +9351,7 @@ var init_agy = __esm({
10105
9351
  for (const rawId of sessionDirs) {
10106
9352
  const sessionPath = join10(brainDir, rawId);
10107
9353
  try {
10108
- if (!statSync7(sessionPath).isDirectory())
9354
+ if (!statSync6(sessionPath).isDirectory())
10109
9355
  continue;
10110
9356
  } catch {
10111
9357
  continue;
@@ -10114,7 +9360,7 @@ var init_agy = __esm({
10114
9360
  if (!filePath)
10115
9361
  continue;
10116
9362
  try {
10117
- const st = statSync7(filePath);
9363
+ const st = statSync6(filePath);
10118
9364
  if (st.mtimeMs < opts.sinceMs)
10119
9365
  continue;
10120
9366
  candidates2.push({ rawId, mtime: st.mtimeMs });
@@ -10142,7 +9388,7 @@ var init_agy = __esm({
10142
9388
  try {
10143
9389
  return {
10144
9390
  tool: "agy",
10145
- mtime: statSync7(loc.path).mtimeMs,
9391
+ mtime: statSync6(loc.path).mtimeMs,
10146
9392
  files: [{ name: basename8(loc.path), bytes: readFileSync4(loc.path) }]
10147
9393
  };
10148
9394
  } catch {
@@ -10157,7 +9403,7 @@ var init_agy = __esm({
10157
9403
  if (!loc)
10158
9404
  return 0;
10159
9405
  try {
10160
- return statSync7(loc.path).size;
9406
+ return statSync6(loc.path).size;
10161
9407
  } catch {
10162
9408
  return 0;
10163
9409
  }
@@ -10174,7 +9420,7 @@ var init_agy = __esm({
10174
9420
  });
10175
9421
 
10176
9422
  // ../engine/dist/core/backends/cursor-store.js
10177
- import { existsSync as existsSync9, readdirSync as readdirSync8, readFileSync as readFileSync5, statSync as statSync8 } from "fs";
9423
+ import { existsSync as existsSync9, readdirSync as readdirSync8, readFileSync as readFileSync5, statSync as statSync7 } from "fs";
10178
9424
  import { join as join11 } from "path";
10179
9425
  function cursorProjectSlug(absPath) {
10180
9426
  return absPath.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
@@ -10191,7 +9437,7 @@ function listChatsIn(chatsRoot) {
10191
9437
  const hashPath3 = join11(chatsRoot, workspaceHash);
10192
9438
  let chatIds;
10193
9439
  try {
10194
- if (!statSync8(hashPath3).isDirectory())
9440
+ if (!statSync7(hashPath3).isDirectory())
10195
9441
  continue;
10196
9442
  chatIds = readdirSync8(hashPath3);
10197
9443
  } catch {
@@ -10213,7 +9459,7 @@ function listChatsIn(chatsRoot) {
10213
9459
  let created = meta.createdAtMs ?? 0;
10214
9460
  if (!mtime || !created) {
10215
9461
  try {
10216
- const st = statSync8(dbPath);
9462
+ const st = statSync7(dbPath);
10217
9463
  mtime = mtime || st.mtimeMs;
10218
9464
  created = created || st.birthtimeMs || st.mtimeMs;
10219
9465
  } catch {
@@ -10256,7 +9502,7 @@ function findChat(chatsRoots, chatId) {
10256
9502
  let created = meta.createdAtMs ?? 0;
10257
9503
  if (!mtime || !created) {
10258
9504
  try {
10259
- const st = statSync8(dbPath);
9505
+ const st = statSync7(dbPath);
10260
9506
  mtime = mtime || st.mtimeMs;
10261
9507
  created = created || st.birthtimeMs || st.mtimeMs;
10262
9508
  } catch {
@@ -10559,7 +9805,7 @@ var init_cursor_store = __esm({
10559
9805
  });
10560
9806
 
10561
9807
  // ../engine/dist/core/backends/cursor-ide.js
10562
- import { existsSync as existsSync10, readFileSync as readFileSync6, readdirSync as readdirSync9, statSync as statSync9 } from "fs";
9808
+ import { existsSync as existsSync10, readFileSync as readFileSync6, readdirSync as readdirSync9, statSync as statSync8 } from "fs";
10563
9809
  import { dirname as dirname11, join as join12 } from "path";
10564
9810
  function jsonOf(value) {
10565
9811
  if (value == null)
@@ -10629,7 +9875,7 @@ function workspaceIndex(workspaceRoots) {
10629
9875
  for (const id of entries) {
10630
9876
  const dir = join12(root, id);
10631
9877
  try {
10632
- if (!statSync9(dir).isDirectory())
9878
+ if (!statSync8(dir).isDirectory())
10633
9879
  continue;
10634
9880
  } catch {
10635
9881
  continue;
@@ -10975,7 +10221,7 @@ var init_cursor_ide = __esm({
10975
10221
  });
10976
10222
 
10977
10223
  // ../engine/dist/core/backends/cursor.js
10978
- import { existsSync as existsSync11, readFileSync as readFileSync7, statSync as statSync10 } from "fs";
10224
+ import { existsSync as existsSync11, readFileSync as readFileSync7, statSync as statSync9 } from "fs";
10979
10225
  import { join as join13 } from "path";
10980
10226
  var PREFIX4, CursorBackend, cursorBackend;
10981
10227
  var init_cursor = __esm({
@@ -11028,7 +10274,7 @@ var init_cursor = __esm({
11028
10274
  if (r.surface === "cli") {
11029
10275
  let mtime2 = r.chat.updatedAtMs;
11030
10276
  try {
11031
- mtime2 = statSync10(r.chat.dbPath).mtimeMs || mtime2;
10277
+ mtime2 = statSync9(r.chat.dbPath).mtimeMs || mtime2;
11032
10278
  } catch {
11033
10279
  }
11034
10280
  return {
@@ -11041,7 +10287,7 @@ var init_cursor = __esm({
11041
10287
  }
11042
10288
  let mtime = r.composer.updatedAtMs;
11043
10289
  try {
11044
- mtime = statSync10(r.composer.globalDbPath).mtimeMs || mtime;
10290
+ mtime = statSync9(r.composer.globalDbPath).mtimeMs || mtime;
11045
10291
  } catch {
11046
10292
  }
11047
10293
  return {
@@ -11287,7 +10533,7 @@ var init_cursor = __esm({
11287
10533
  if (existsSync11(metaPath)) {
11288
10534
  files.push({ name: "meta.json", bytes: readFileSync7(metaPath) });
11289
10535
  }
11290
- return { tool: "cursor", mtime: statSync10(r.chat.dbPath).mtimeMs, files };
10536
+ return { tool: "cursor", mtime: statSync9(r.chat.dbPath).mtimeMs, files };
11291
10537
  }
11292
10538
  const events = readComposerEvents(r.composer);
11293
10539
  if (events.length === 0)
@@ -11295,7 +10541,7 @@ var init_cursor = __esm({
11295
10541
  const body = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
11296
10542
  return {
11297
10543
  tool: "cursor",
11298
- mtime: statSync10(r.composer.globalDbPath).mtimeMs,
10544
+ mtime: statSync9(r.composer.globalDbPath).mtimeMs,
11299
10545
  files: [{ name: `${r.composer.composerId}.jsonl`, bytes: Buffer.from(body, "utf8") }]
11300
10546
  };
11301
10547
  } catch {
@@ -11361,8 +10607,8 @@ var init_backends = __esm({
11361
10607
 
11362
10608
  // ../engine/dist/transcript/shadow.js
11363
10609
  import { gzipSync as gzipSync2, gunzipSync as gunzipSync2 } from "zlib";
11364
- import { createHash as createHash3 } from "crypto";
11365
- import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync2, renameSync as renameSync2, statSync as statSync11 } from "fs";
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";
11366
10612
  import { dirname as dirname12, join as join14 } from "path";
11367
10613
  function shadowRoot() {
11368
10614
  return join14(getDataDir(), "shadow");
@@ -11392,7 +10638,7 @@ function writeShadowContainer(tool, rawId, container) {
11392
10638
  renameSync2(tmp, path);
11393
10639
  }
11394
10640
  function sha1(s) {
11395
- return createHash3("sha1").update(s).digest("hex");
10641
+ return createHash2("sha1").update(s).digest("hex");
11396
10642
  }
11397
10643
  function lineKey(line) {
11398
10644
  try {
@@ -11570,7 +10816,7 @@ function liveFingerprint(backend, rawId) {
11570
10816
  const loc = backend.findSession(rawId);
11571
10817
  if (!loc?.path)
11572
10818
  return "";
11573
- const st = statSync11(loc.path);
10819
+ const st = statSync10(loc.path);
11574
10820
  return `${st.mtimeMs}:${st.size}`;
11575
10821
  } catch {
11576
10822
  return "";
@@ -11588,7 +10834,7 @@ function seedShadow(sessionId, container) {
11588
10834
  }
11589
10835
  function shadowSizeOnDisk(tool, rawId) {
11590
10836
  try {
11591
- return statSync11(shadowFileFor(tool, rawId)).size;
10837
+ return statSync10(shadowFileFor(tool, rawId)).size;
11592
10838
  } catch {
11593
10839
  return 0;
11594
10840
  }
@@ -11606,7 +10852,7 @@ var init_shadow = __esm({
11606
10852
  });
11607
10853
 
11608
10854
  // ../engine/dist/core/live-session-scan.js
11609
- import { existsSync as existsSync13, readdirSync as readdirSync10, readFileSync as readFileSync9, statSync as statSync12, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
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";
11610
10856
  import { join as join15, basename as basename9 } from "path";
11611
10857
  function detectTool(sessionId) {
11612
10858
  return getBackendForId(sessionId)?.id ?? "claude";
@@ -11724,7 +10970,7 @@ function readSessionGroupText(group) {
11724
10970
  const parts = [];
11725
10971
  for (const p of group.paths) {
11726
10972
  try {
11727
- const st = statSync12(p);
10973
+ const st = statSync11(p);
11728
10974
  parts.push(`${p}:${st.mtimeMs}:${st.size}`);
11729
10975
  } catch {
11730
10976
  parts.push(`${p}:missing`);
@@ -11744,7 +10990,7 @@ function readSessionGroupText(group) {
11744
10990
  continue;
11745
10991
  }
11746
10992
  try {
11747
- mtime = Math.max(mtime, statSync12(p).mtimeMs);
10993
+ mtime = Math.max(mtime, statSync11(p).mtimeMs);
11748
10994
  } catch {
11749
10995
  }
11750
10996
  text = text ? mergeLineText(text, raw).text : raw;
@@ -11961,7 +11207,7 @@ var init_first_prompt = __esm({
11961
11207
  });
11962
11208
 
11963
11209
  // ../engine/dist/core/backends/claude.js
11964
- import { existsSync as existsSync15, readdirSync as readdirSync11, readFileSync as readFileSync10, statSync as statSync13, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
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";
11965
11211
  import { join as join16, basename as basename10 } from "path";
11966
11212
  function stripReminders(text) {
11967
11213
  return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
@@ -12091,7 +11337,7 @@ var init_claude2 = __esm({
12091
11337
  let mtime = 0;
12092
11338
  for (const c of copies) {
12093
11339
  try {
12094
- mtime = Math.max(mtime, statSync13(c.path).mtimeMs);
11340
+ mtime = Math.max(mtime, statSync12(c.path).mtimeMs);
12095
11341
  } catch {
12096
11342
  }
12097
11343
  }
@@ -12167,7 +11413,7 @@ var init_claude2 = __esm({
12167
11413
  const fullPath = join16(projPath, f);
12168
11414
  let stat3;
12169
11415
  try {
12170
- stat3 = statSync13(fullPath);
11416
+ stat3 = statSync12(fullPath);
12171
11417
  } catch {
12172
11418
  continue;
12173
11419
  }
@@ -12388,7 +11634,7 @@ var init_claude2 = __esm({
12388
11634
  const events = this.readEvents(rawId);
12389
11635
  let fileMtime = 0;
12390
11636
  try {
12391
- fileMtime = statSync13(located.path).mtimeMs;
11637
+ fileMtime = statSync12(located.path).mtimeMs;
12392
11638
  } catch {
12393
11639
  }
12394
11640
  return liveScanEditsFromEvents(events, this.fileToolMap, {
@@ -12443,13 +11689,13 @@ var init_claude2 = __esm({
12443
11689
  continue;
12444
11690
  const fp = join16(projPath, f);
12445
11691
  try {
12446
- const st = statSync13(fp);
11692
+ const st = statSync12(fp);
12447
11693
  let mtime = st.mtimeMs;
12448
11694
  const subDir = join16(projPath, f.slice(0, -6), "subagents");
12449
11695
  if (existsSync15(subDir)) {
12450
11696
  for (const sub of readdirSync11(subDir)) {
12451
11697
  try {
12452
- const subSt = statSync13(join16(subDir, sub));
11698
+ const subSt = statSync12(join16(subDir, sub));
12453
11699
  if (subSt.mtimeMs > mtime)
12454
11700
  mtime = subSt.mtimeMs;
12455
11701
  } catch {
@@ -12514,7 +11760,7 @@ var init_claude2 = __esm({
12514
11760
  let total = 0;
12515
11761
  for (const located of findSessionFiles(this.toRawId(prefixedId))) {
12516
11762
  try {
12517
- total += statSync13(located.path).size;
11763
+ total += statSync12(located.path).size;
12518
11764
  } catch {
12519
11765
  }
12520
11766
  }
@@ -12563,8 +11809,8 @@ __export(project_resolver_exports, {
12563
11809
  resolveWorkspaceId: () => resolveWorkspaceId
12564
11810
  });
12565
11811
  import { execSync } from "child_process";
12566
- import { createHash as createHash4 } from "crypto";
12567
- import { existsSync as existsSync16, readFileSync as readFileSync11, realpathSync as realpathSync2, readdirSync as readdirSync12, statSync as statSync14 } from "fs";
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";
12568
11814
  import { basename as basename11, dirname as dirname13, isAbsolute, join as join17, resolve, sep } from "path";
12569
11815
  function getProjectsConfigPath() {
12570
11816
  return join17(getDataDir(), "projects.json");
@@ -12578,7 +11824,7 @@ function loadProjectsConfig() {
12578
11824
  cachedConfig = { mtime: 0, cfg: {} };
12579
11825
  return cachedConfig.cfg;
12580
11826
  }
12581
- const mtime = statSync14(path).mtimeMs;
11827
+ const mtime = statSync13(path).mtimeMs;
12582
11828
  if (cachedConfig && cachedConfig.mtime === mtime)
12583
11829
  return cachedConfig.cfg;
12584
11830
  try {
@@ -12735,7 +11981,7 @@ function resolveGit(realPath) {
12735
11981
  if (probe.kind === "failed") {
12736
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}`);
12737
11983
  }
12738
- const hash = createHash4("sha1").update(toplevel).digest("hex").slice(0, 12);
11984
+ const hash = createHash3("sha1").update(toplevel).digest("hex").slice(0, 12);
12739
11985
  return {
12740
11986
  id: `git-local:${hash}`,
12741
11987
  displayName: basename11(toplevel),
@@ -12799,154 +12045,614 @@ function parseGitRemote(url) {
12799
12045
  return { host: normaliseHost(url1[1]), owner: url1[2], repo: url1[3] };
12800
12046
  return null;
12801
12047
  }
12802
- function normaliseHost(host) {
12803
- for (const known of ["github.com", "gitlab.com", "bitbucket.org", "codeberg.org", "gitea.com"]) {
12804
- if (host === known)
12805
- return known;
12806
- if (host.startsWith(known + "-") || host.startsWith(known + "."))
12807
- return known;
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" };
12074
+ }
12075
+ }
12076
+ last = cur;
12077
+ cur = dirname13(cur);
12078
+ depth++;
12079
+ }
12080
+ return null;
12081
+ }
12082
+ function countGitChildren(dir) {
12083
+ let count = 0;
12084
+ let entries;
12085
+ try {
12086
+ entries = readdirSync12(dir);
12087
+ } catch {
12088
+ return 0;
12089
+ }
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
+ }
12102
+ }
12103
+ return count;
12104
+ }
12105
+ function safeRealpath(p) {
12106
+ try {
12107
+ return realpathSync2(p);
12108
+ } catch {
12109
+ return resolve(p);
12110
+ }
12111
+ }
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);
12120
+ }
12121
+ function globMatch(pattern, path) {
12122
+ const re = new RegExp("^" + globToRegex(toPosix(pattern)) + "$");
12123
+ return re.test(toPosix(path));
12124
+ }
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
+ }
12158
+ }
12159
+ return out;
12160
+ }
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)
12186
+ return null;
12187
+ if (!/^https?:\/\//i.test(server))
12188
+ return null;
12189
+ return { serverUrl: normalise(server), token };
12190
+ }
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(/\/+$/, "");
12196
+ }
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;
12228
+ }
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);
12235
+ }
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
+ }
12256
+ }
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;
12268
+ }
12269
+ function isEnvRedactionRequested() {
12270
+ const v = process.env.CHAT_RECALL_REDACT_INDEX;
12271
+ if (v === void 0)
12272
+ return null;
12273
+ return v === "1" || v === "true" || v === "yes";
12274
+ }
12275
+ function settingsView() {
12276
+ let mtimeMs = -1;
12277
+ try {
12278
+ mtimeMs = statSync14(settingsFilePath()).mtimeMs;
12279
+ } catch {
12808
12280
  }
12809
- return host;
12810
- }
12811
- function resolveAutoWorkspace(realPath, minRepos) {
12812
- let cur = realPath;
12813
- let last = "";
12814
- let depth = 0;
12815
- while (cur && cur !== last && depth < AUTO_WORKSPACE_MAX_DEPTH) {
12816
- if (!existsSync16(join17(cur, ".git"))) {
12817
- const cached = workspaceCache.get(cur);
12818
- let qualifies;
12819
- if (cached === void 0) {
12820
- qualifies = countGitChildren(cur) >= minRepos;
12821
- workspaceCache.set(cur, qualifies ? cur : null);
12822
- } else {
12823
- qualifies = cached !== null;
12824
- }
12825
- if (qualifies) {
12826
- const name = basename11(cur);
12827
- return { id: `ws:${name}`, displayName: name, source: "auto-workspace" };
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 {
12828
12292
  }
12829
12293
  }
12830
- last = cur;
12831
- cur = dirname13(cur);
12832
- depth++;
12294
+ } catch {
12833
12295
  }
12834
- return null;
12296
+ cache = { mtimeMs, enabled, rules: [...DEFAULT_REDACTION_RULES, ...extra] };
12297
+ return cache;
12835
12298
  }
12836
- function countGitChildren(dir) {
12837
- let count = 0;
12838
- let entries;
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;
12839
12306
  try {
12840
- entries = readdirSync12(dir);
12841
- } catch {
12842
- return 0;
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}` };
12843
12311
  }
12844
- for (const name of entries) {
12845
- if (name.startsWith("."))
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 };
12319
+ }
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` });
12846
12330
  continue;
12847
- const child = join17(dir, name);
12848
- try {
12849
- const st = statSync14(child);
12850
- if (!st.isDirectory())
12851
- continue;
12852
- if (existsSync16(join17(child, ".git")))
12853
- count++;
12854
- } catch {
12855
12331
  }
12332
+ const out = compileServerRule(spec);
12333
+ if ("reason" in out)
12334
+ rejected.push(out);
12335
+ else
12336
+ rules.push(out);
12856
12337
  }
12857
- return count;
12338
+ const version2 = pack.version || "";
12339
+ serverPack = { version: version2, rules };
12340
+ return { version: version2, accepted: rules.length, rejected };
12858
12341
  }
12859
- function safeRealpath(p) {
12860
- try {
12861
- return realpathSync2(p);
12862
- } catch {
12863
- return resolve(p);
12342
+ function serverRulePackVersion() {
12343
+ return serverPack ? serverPack.version || "unversioned" : null;
12344
+ }
12345
+ function _clearServerRulePack() {
12346
+ serverPack = null;
12347
+ }
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
+ });
12379
+ }
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);
12864
12386
  }
12387
+ return out;
12865
12388
  }
12866
- function isInside(child, parent) {
12867
- if (!parent)
12868
- return false;
12869
- if (child === parent)
12870
- return true;
12871
- const c = child.endsWith(sep) ? child : child + sep;
12872
- const p = parent.endsWith(sep) ? parent : parent + sep;
12873
- return c.startsWith(p);
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);
12874
12395
  }
12875
- function globMatch(pattern, path) {
12876
- const re = new RegExp("^" + globToRegex(toPosix(pattern)) + "$");
12877
- return re.test(toPosix(path));
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);
12403
+ }
12404
+ return re;
12878
12405
  }
12879
- function toPosix(p) {
12880
- return p.replace(/\\/g, "/");
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
+ };
12881
12421
  }
12882
- function globToRegex(glob) {
12883
- let out = "";
12884
- let i = 0;
12885
- while (i < glob.length) {
12886
- const c = glob[i];
12887
- if (c === "*" && glob[i + 1] === "*") {
12888
- out += ".*";
12889
- i += 2;
12890
- if (glob[i] === "/")
12891
- i++;
12892
- } else if (c === "*") {
12893
- out += "[^/]*";
12894
- i++;
12895
- } else if (c === "?") {
12896
- out += "[^/]";
12897
- i++;
12898
- } else if (c === "{") {
12899
- const end = glob.indexOf("}", i);
12900
- if (end === -1) {
12901
- out += "\\{";
12902
- i++;
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++;
12903
12438
  continue;
12904
12439
  }
12905
- const opts = glob.slice(i + 1, end).split(",").map(escapeRe).join("|");
12906
- out += "(?:" + opts + ")";
12907
- i = end + 1;
12908
- } else {
12909
- out += escapeRe(c);
12910
- i++;
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) });
12445
+ }
12446
+ if (re.lastIndex === m.index)
12447
+ re.lastIndex++;
12911
12448
  }
12912
12449
  }
12913
- return out;
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;
12455
+ }
12456
+ function privacyView() {
12457
+ let mtimeMs = -1;
12458
+ try {
12459
+ mtimeMs = statSync14(settingsFilePath()).mtimeMs;
12460
+ } catch {
12461
+ }
12462
+ if (privacyCache && privacyCache.mtimeMs === mtimeMs)
12463
+ return privacyCache;
12464
+ let redactToolOutputs = false, redactFilePaths2 = false;
12465
+ try {
12466
+ const s = loadSettings();
12467
+ redactToolOutputs = !!s.privacy?.redactToolOutputs;
12468
+ redactFilePaths2 = !!s.privacy?.redactFilePaths;
12469
+ } catch {
12470
+ }
12471
+ privacyCache = { mtimeMs, redactToolOutputs, redactFilePaths: redactFilePaths2 };
12472
+ return privacyCache;
12914
12473
  }
12915
- function escapeRe(s) {
12916
- return s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
12474
+ function _resetPrivacyCache() {
12475
+ privacyCache = null;
12917
12476
  }
12918
- var DEFAULT_AUTO_WORKSPACE_MIN_REPOS, BOT_WORKTREE_MARKERS, resolveCache, gitCache, workspaceCache, wsIdCache, cachedConfig, GIT_TIMEOUT_MS, AUTO_WORKSPACE_MAX_DEPTH;
12919
- var init_project_resolver = __esm({
12920
- "../engine/dist/core/project-resolver.js"() {
12921
- "use strict";
12922
- init_paths();
12923
- DEFAULT_AUTO_WORKSPACE_MIN_REPOS = 3;
12924
- BOT_WORKTREE_MARKERS = [".claude-pr-bot"];
12925
- resolveCache = /* @__PURE__ */ new Map();
12926
- gitCache = /* @__PURE__ */ new Map();
12927
- workspaceCache = /* @__PURE__ */ new Map();
12928
- wsIdCache = /* @__PURE__ */ new Map();
12929
- cachedConfig = null;
12930
- GIT_TIMEOUT_MS = 1e4;
12931
- AUTO_WORKSPACE_MAX_DEPTH = 2;
12932
- }
12933
- });
12934
-
12935
- // ../engine/dist/core/credentials-env.js
12936
- function envCredentials(env = process.env) {
12937
- const token = (env.CHAT_RECALL_TOKEN || "").trim();
12938
- const server = (env.CHAT_RECALL_SERVER || "").trim();
12939
- if (!token || !server)
12940
- return null;
12941
- if (!/^https?:\/\//i.test(server))
12942
- return null;
12943
- return { serverUrl: normalise(server), token };
12477
+ function isToolOutputRedactionEnabled() {
12478
+ return privacyView().redactToolOutputs;
12944
12479
  }
12945
- var normalise;
12946
- var init_credentials_env = __esm({
12947
- "../engine/dist/core/credentials-env.js"() {
12480
+ function isFilePathRedactionEnabled() {
12481
+ return privacyView().redactFilePaths;
12482
+ }
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
+ });
12492
+ }
12493
+ function applyChunkPrivacy(text) {
12494
+ let out = redactSecrets(text);
12495
+ if (privacyView().redactFilePaths)
12496
+ out = redactFilePaths(out);
12497
+ return out;
12498
+ }
12499
+ function redactToolResultBody(toolName, body) {
12500
+ if (!body)
12501
+ return body;
12502
+ return `[redacted: ${toolName} output, ${body.length} chars]`;
12503
+ }
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"() {
12948
12507
  "use strict";
12949
- normalise = (u) => u.trim().replace(/\/+$/, "");
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;
12950
12656
  }
12951
12657
  });
12952
12658
 
@@ -14929,111 +14635,6 @@ var init_source_registry = __esm({
14929
14635
  }
14930
14636
  });
14931
14637
 
14932
- // ../engine/dist/parsers/session-source.js
14933
- var SessionSource;
14934
- var init_session_source = __esm({
14935
- "../engine/dist/parsers/session-source.js"() {
14936
- "use strict";
14937
- init_session();
14938
- init_chunker();
14939
- init_settings();
14940
- SessionSource = class {
14941
- sourceType = "session";
14942
- claudeDir;
14943
- constructor(claudeDir) {
14944
- this.claudeDir = claudeDir;
14945
- }
14946
- async *discover() {
14947
- if (!isSourceEnabled("claude", "sessions"))
14948
- return;
14949
- for (const [entry, sessionPath] of getAllSessions(this.claudeDir)) {
14950
- yield this.entryToItem(entry, sessionPath);
14951
- }
14952
- }
14953
- async parse(item) {
14954
- const content = await parseSessionFile(item.filePath);
14955
- const entry = this.itemToEntry(item);
14956
- const sessionChunks = chunkSession(entry, content);
14957
- item.extra = {
14958
- ...item.extra || {},
14959
- ...content.metadata
14960
- };
14961
- return sessionChunks.map((sc) => ({
14962
- chunkId: sc.chunkId,
14963
- itemId: sc.sessionId,
14964
- sourceType: "session",
14965
- // Empty title is preferable to "Session abc12345" placeholder text:
14966
- // the latter pollutes search results and gets surfaced in the UI as
14967
- // a misleading heading. Downstream renderers fall back to
14968
- // project path / time when title is empty.
14969
- title: sc.firstPrompt.slice(0, 100),
14970
- text: sc.text,
14971
- chunkType: sc.chunkType,
14972
- projectPath: sc.projectPath,
14973
- filePath: sc.sourceFile,
14974
- mtime: sc.mtime
14975
- }));
14976
- }
14977
- async extractLinks(item) {
14978
- if (item.filePath) {
14979
- try {
14980
- const { detectForkPredecessor: detectForkPredecessor2 } = await Promise.resolve().then(() => (init_raw(), raw_exports));
14981
- const predecessor = detectForkPredecessor2(item.filePath);
14982
- if (predecessor) {
14983
- return [{
14984
- sourceType: "session",
14985
- sourceId: item.id,
14986
- targetType: "session",
14987
- targetId: predecessor,
14988
- linkType: "forked_from",
14989
- confidence: 1
14990
- }];
14991
- }
14992
- } catch {
14993
- }
14994
- }
14995
- return [];
14996
- }
14997
- entryToItem(entry, sessionPath) {
14998
- return {
14999
- id: entry.sessionId,
15000
- sourceType: "session",
15001
- // Empty title when there's no first prompt — the UI then knows to
15002
- // fall back to summary or project path instead of rendering the
15003
- // useless "Session abc12345" placeholder as a heading.
15004
- title: entry.firstPrompt ? entry.firstPrompt.replace(/\n/g, " ").trim().slice(0, 100) : "",
15005
- projectPath: entry.projectPath,
15006
- filePath: sessionPath,
15007
- mtime: entry.fileMtime,
15008
- contentPreview: entry.firstPrompt?.slice(0, 200),
15009
- extra: {
15010
- messageCount: entry.messageCount,
15011
- gitBranch: entry.gitBranch,
15012
- created: entry.created,
15013
- modified: entry.modified,
15014
- isSidechain: entry.isSidechain
15015
- }
15016
- };
15017
- }
15018
- itemToEntry(item) {
15019
- const extra = item.extra || {};
15020
- return {
15021
- sessionId: item.id,
15022
- fullPath: item.filePath,
15023
- fileMtime: item.mtime,
15024
- firstPrompt: item.contentPreview || "",
15025
- messageCount: extra.messageCount || 0,
15026
- created: extra.created || "",
15027
- modified: extra.modified || "",
15028
- gitBranch: extra.gitBranch || "",
15029
- projectPath: item.projectPath,
15030
- isSidechain: extra.isSidechain || false
15031
- };
15032
- }
15033
- };
15034
- }
15035
- });
15036
-
15037
14638
  // ../engine/dist/core/utils.js
15038
14639
  import { existsSync as existsSync21, readdirSync as readdirSync15 } from "fs";
15039
14640
  import { homedir as homedir6 } from "os";
@@ -18390,7 +17991,6 @@ var init_plugins_source = __esm({
18390
17991
 
18391
17992
  // ../engine/dist/parsers/all-sources.js
18392
17993
  function buildSourceRegistry(registry = new SourceRegistry()) {
18393
- registry.register(new SessionSource());
18394
17994
  registry.register(new PlanSource());
18395
17995
  registry.register(new TaskSource());
18396
17996
  registry.register(new ClaudeMdSource());
@@ -18414,7 +18014,6 @@ var init_all_sources = __esm({
18414
18014
  "../engine/dist/parsers/all-sources.js"() {
18415
18015
  "use strict";
18416
18016
  init_source_registry();
18417
- init_session_source();
18418
18017
  init_plan_source();
18419
18018
  init_task_source();
18420
18019
  init_claude_md_source();
@@ -18858,7 +18457,7 @@ var init_http = __esm({
18858
18457
  CLI_VERSION = JSON.parse(readFileSync31(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
18859
18458
  } catch {
18860
18459
  try {
18861
- if (true) CLI_VERSION = "0.6.9";
18460
+ if (true) CLI_VERSION = "0.7.0";
18862
18461
  } catch {
18863
18462
  }
18864
18463
  }
@@ -19357,7 +18956,7 @@ var init_telemetry = __esm({
19357
18956
  init_sync_client();
19358
18957
  cliVersion2 = () => {
19359
18958
  try {
19360
- return true ? "0.6.9" : "dev";
18959
+ return true ? "0.7.0" : "dev";
19361
18960
  } catch {
19362
18961
  return "dev";
19363
18962
  }
@@ -21525,7 +21124,7 @@ var init_sync_client = __esm({
21525
21124
  ownVersion = JSON.parse(readFileSync35(join42(dir, "../package.json"), "utf-8")).version || ownVersion;
21526
21125
  } catch {
21527
21126
  try {
21528
- if (true) ownVersion = "0.6.9";
21127
+ if (true) ownVersion = "0.7.0";
21529
21128
  } catch {
21530
21129
  }
21531
21130
  }
@@ -25641,7 +25240,7 @@ var init_intent_drain = __esm({
25641
25240
  ownVersion2 = JSON.parse(readFileSync45(join56(dir, "../package.json"), "utf-8")).version || ownVersion2;
25642
25241
  } catch {
25643
25242
  try {
25644
- if (true) ownVersion2 = "0.6.9";
25243
+ if (true) ownVersion2 = "0.7.0";
25645
25244
  } catch {
25646
25245
  }
25647
25246
  }
@@ -28013,6 +27612,7 @@ var DEFAULT_ALLOW = [
28013
27612
  "recall_user_prompts",
28014
27613
  "recall_decision_record",
28015
27614
  "recall_decisions",
27615
+ "recall_decision_scan",
28016
27616
  "recall_analytics_summary",
28017
27617
  "recall_outcome_summary",
28018
27618
  "recall_regenerate_summary",