chat-recall 0.6.8 → 0.6.9
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 +966 -909
- package/dist/cli.js.map +1 -1
- package/dist/mcp-relay.js +1 -1
- package/dist/mcp.js +1043 -775
- package/dist/scan-worker.js +325 -25
- package/dist/scan-worker.js.map +1 -1
- package/dist/sync-client.d.ts +13 -0
- package/dist/sync-client.d.ts.map +1 -1
- package/dist/sync-client.js +38 -4
- package/dist/sync-client.js.map +1 -1
- package/dist/watch.js +895 -856
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5083,978 +5083,978 @@ var init_settings = __esm({
|
|
|
5083
5083
|
}
|
|
5084
5084
|
});
|
|
5085
5085
|
|
|
5086
|
-
// ../engine/dist/core/
|
|
5087
|
-
var
|
|
5088
|
-
__export(
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
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
|
|
5096
5105
|
});
|
|
5097
|
-
import {
|
|
5098
|
-
import {
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
return
|
|
5105
|
-
|
|
5106
|
-
return trimmed;
|
|
5107
|
-
}
|
|
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;
|
|
5108
5115
|
}
|
|
5109
|
-
function
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
const
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
for (const e of readdirSync2(parent, { withFileTypes: true })) {
|
|
5133
|
-
if (!e.isDirectory())
|
|
5134
|
-
continue;
|
|
5135
|
-
if (e.name === leaf || !e.name.startsWith(leaf + "-"))
|
|
5136
|
-
continue;
|
|
5137
|
-
if (e.name === ".claude-code")
|
|
5138
|
-
continue;
|
|
5139
|
-
add(join3(parent, e.name), marker);
|
|
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;
|
|
5140
5139
|
}
|
|
5141
|
-
|
|
5140
|
+
ranges.push({ start: winStart + tm.index, end: winStart + tm.index + tm[0].length });
|
|
5141
|
+
break;
|
|
5142
5142
|
}
|
|
5143
5143
|
}
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
return { seeded: [] };
|
|
5152
|
-
}
|
|
5153
|
-
if (s.sources.homesGrandfathered)
|
|
5154
|
-
return { seeded: [] };
|
|
5155
|
-
const legacy = legacyAutoSyncedHomes();
|
|
5156
|
-
const approved = new Set((s.sources.approvedHomes ?? []).map(normalizeHomePath));
|
|
5157
|
-
const seeded = [];
|
|
5158
|
-
for (const p of legacy) {
|
|
5159
|
-
if (!approved.has(p)) {
|
|
5160
|
-
approved.add(p);
|
|
5161
|
-
seeded.push(p);
|
|
5144
|
+
ranges.sort((a, b) => a.start - b.start);
|
|
5145
|
+
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;
|
|
5162
5151
|
}
|
|
5152
|
+
out.push({ ...r });
|
|
5163
5153
|
}
|
|
5164
|
-
|
|
5165
|
-
|
|
5154
|
+
return out;
|
|
5155
|
+
}
|
|
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;
|
|
5166
5164
|
try {
|
|
5167
|
-
|
|
5165
|
+
mtimeMs = statSync3(settingsFilePath()).mtimeMs;
|
|
5168
5166
|
} catch {
|
|
5169
|
-
return { seeded: [] };
|
|
5170
5167
|
}
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
const
|
|
5175
|
-
const env = process.env;
|
|
5176
|
-
let s;
|
|
5168
|
+
if (cache && cache.mtimeMs === mtimeMs)
|
|
5169
|
+
return cache;
|
|
5170
|
+
let enabled = false;
|
|
5171
|
+
const extra = [];
|
|
5177
5172
|
try {
|
|
5178
|
-
s = loadSettings()
|
|
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
|
+
}
|
|
5180
|
+
}
|
|
5179
5181
|
} catch {
|
|
5180
|
-
s = void 0;
|
|
5181
5182
|
}
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
env.CHAT_RECALL_CLAUDE_HOME || s?.claudeHome || join3(home, ".claude"),
|
|
5185
|
-
env.CHAT_RECALL_GEMINI_HOME || s?.geminiHome || join3(home, ".gemini"),
|
|
5186
|
-
env.CHAT_RECALL_CODEX_HOME || s?.codexHome || join3(home, ".codex"),
|
|
5187
|
-
env.CHAT_RECALL_AGY_HOME || s?.agyHome || join3(home, ".gemini", "antigravity-cli"),
|
|
5188
|
-
env.CHAT_RECALL_CURSOR_HOME || s?.cursorHome || join3(home, ".cursor"),
|
|
5189
|
-
dirOf(opencodeDb)
|
|
5190
|
-
];
|
|
5191
|
-
return candidates2.map(normalizeHomePath);
|
|
5183
|
+
cache = { mtimeMs, enabled, rules: [...DEFAULT_REDACTION_RULES, ...extra] };
|
|
5184
|
+
return cache;
|
|
5192
5185
|
}
|
|
5193
|
-
function
|
|
5194
|
-
const
|
|
5195
|
-
if (
|
|
5196
|
-
return "
|
|
5197
|
-
|
|
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;
|
|
5198
5193
|
try {
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
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}` };
|
|
5202
5198
|
}
|
|
5203
|
-
if (
|
|
5204
|
-
return "
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
return "
|
|
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 };
|
|
5210
5206
|
}
|
|
5211
|
-
function
|
|
5212
|
-
const
|
|
5213
|
-
return
|
|
5207
|
+
function validateRedactionRule(spec) {
|
|
5208
|
+
const out = compileServerRule(spec);
|
|
5209
|
+
return "reason" in out ? { ok: false, reason: out.reason } : { ok: true };
|
|
5214
5210
|
}
|
|
5215
|
-
function
|
|
5216
|
-
const
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
s.sources.approvedHomes = [...approved];
|
|
5229
|
-
s.sources.declinedHomes = [...declined];
|
|
5230
|
-
try {
|
|
5231
|
-
saveSettings(s);
|
|
5232
|
-
} catch {
|
|
5233
|
-
return false;
|
|
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);
|
|
5234
5224
|
}
|
|
5235
|
-
|
|
5225
|
+
const version2 = pack.version || "";
|
|
5226
|
+
serverPack = { version: version2, rules };
|
|
5227
|
+
return { version: version2, accepted: rules.length, rejected };
|
|
5236
5228
|
}
|
|
5237
|
-
function
|
|
5238
|
-
return
|
|
5229
|
+
function serverRulePackVersion() {
|
|
5230
|
+
return serverPack ? serverPack.version || "unversioned" : null;
|
|
5239
5231
|
}
|
|
5240
|
-
function
|
|
5241
|
-
|
|
5232
|
+
function _clearServerRulePack() {
|
|
5233
|
+
serverPack = null;
|
|
5242
5234
|
}
|
|
5243
|
-
function
|
|
5244
|
-
const
|
|
5245
|
-
|
|
5246
|
-
try {
|
|
5247
|
-
s = loadSettings();
|
|
5248
|
-
} catch {
|
|
5249
|
-
return false;
|
|
5250
|
-
}
|
|
5251
|
-
s.sources.approvedHomes = (s.sources.approvedHomes ?? []).filter((x) => normalizeHomePath(x) !== p);
|
|
5252
|
-
s.sources.declinedHomes = (s.sources.declinedHomes ?? []).filter((x) => normalizeHomePath(x) !== p);
|
|
5253
|
-
try {
|
|
5254
|
-
saveSettings(s);
|
|
5255
|
-
} catch {
|
|
5256
|
-
return false;
|
|
5257
|
-
}
|
|
5258
|
-
return true;
|
|
5235
|
+
function activeRules() {
|
|
5236
|
+
const base3 = settingsView().rules;
|
|
5237
|
+
return serverPack && serverPack.rules.length > 0 ? [...base3, ...serverPack.rules] : base3;
|
|
5259
5238
|
}
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
dirOf = (filePath) => dirname3(filePath);
|
|
5266
|
-
}
|
|
5267
|
-
});
|
|
5268
|
-
|
|
5269
|
-
// ../engine/dist/core/tool-paths.js
|
|
5270
|
-
import { existsSync as existsSync4, readdirSync as readdirSync3 } from "fs";
|
|
5271
|
-
import { homedir as homedir3 } from "os";
|
|
5272
|
-
import { join as join4, dirname as dirname4, basename } from "path";
|
|
5273
|
-
function claudeHomeDir() {
|
|
5274
|
-
return process.env.CHAT_RECALL_CLAUDE_HOME || sources().claudeHome || join4(homedir3(), ".claude");
|
|
5239
|
+
function isRedactionEnabled() {
|
|
5240
|
+
const env = isEnvRedactionRequested();
|
|
5241
|
+
if (env !== null)
|
|
5242
|
+
return env;
|
|
5243
|
+
return settingsView().enabled;
|
|
5275
5244
|
}
|
|
5276
|
-
function
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
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
|
+
});
|
|
5295
5266
|
}
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
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);
|
|
5302
5273
|
}
|
|
5303
|
-
|
|
5304
|
-
dirs.push(join4(claudeHomeDir(), "projects"));
|
|
5305
|
-
return applyDecisions(dirs, opts.includeExcluded);
|
|
5306
|
-
}
|
|
5307
|
-
function _setSourceExclusionFilter(fn) {
|
|
5308
|
-
_sourceExclusionFilter = fn;
|
|
5309
|
-
}
|
|
5310
|
-
function geminiHomeDir() {
|
|
5311
|
-
return process.env.CHAT_RECALL_GEMINI_HOME || sources().geminiHome || join4(homedir3(), ".gemini");
|
|
5312
|
-
}
|
|
5313
|
-
function opencodeDbPath() {
|
|
5314
|
-
return process.env.CHAT_RECALL_OPENCODE_DB || sources().opencodeDbPath || join4(homedir3(), ".local", "share", "opencode", "opencode.db");
|
|
5315
|
-
}
|
|
5316
|
-
function codexHomeDir() {
|
|
5317
|
-
return process.env.CHAT_RECALL_CODEX_HOME || sources().codexHome || join4(homedir3(), ".codex");
|
|
5318
|
-
}
|
|
5319
|
-
function agyHomeDir() {
|
|
5320
|
-
return process.env.CHAT_RECALL_AGY_HOME || sources().agyHome || join4(homedir3(), ".gemini", "antigravity-cli");
|
|
5274
|
+
return out;
|
|
5321
5275
|
}
|
|
5322
|
-
function
|
|
5323
|
-
|
|
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);
|
|
5324
5282
|
}
|
|
5325
|
-
function
|
|
5326
|
-
|
|
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;
|
|
5327
5292
|
}
|
|
5328
|
-
function
|
|
5329
|
-
const
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
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;
|
|
5334
5307
|
};
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
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;
|
|
5346
5326
|
}
|
|
5347
|
-
|
|
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++;
|
|
5348
5335
|
}
|
|
5349
5336
|
}
|
|
5350
|
-
|
|
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;
|
|
5351
5342
|
}
|
|
5352
|
-
function
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
try {
|
|
5358
|
-
grandfatherLegacyHomes();
|
|
5359
|
-
const approved = kept.filter((r) => isHomeSynced(dirname4(r)));
|
|
5360
|
-
if (approved.length > 0)
|
|
5361
|
-
kept = approved;
|
|
5362
|
-
} catch {
|
|
5363
|
-
}
|
|
5343
|
+
function privacyView() {
|
|
5344
|
+
let mtimeMs = -1;
|
|
5345
|
+
try {
|
|
5346
|
+
mtimeMs = statSync3(settingsFilePath()).mtimeMs;
|
|
5347
|
+
} catch {
|
|
5364
5348
|
}
|
|
5365
|
-
if (
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
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 {
|
|
5369
5357
|
}
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
function applyExclusions(roots, includeExcluded) {
|
|
5373
|
-
return applyDecisions(roots, includeExcluded);
|
|
5374
|
-
}
|
|
5375
|
-
function codexSessionDirs(opts = {}) {
|
|
5376
|
-
const overridden = !!(process.env.CHAT_RECALL_CODEX_HOME || sources().codexHome);
|
|
5377
|
-
return applyExclusions(siblingHomes(codexHomeDir(), overridden, "sessions"), opts.includeExcluded);
|
|
5358
|
+
privacyCache = { mtimeMs, redactToolOutputs, redactFilePaths: redactFilePaths2 };
|
|
5359
|
+
return privacyCache;
|
|
5378
5360
|
}
|
|
5379
|
-
function
|
|
5380
|
-
|
|
5381
|
-
return applyExclusions(siblingHomes(agyHomeDir(), overridden, "brain"), opts.includeExcluded);
|
|
5361
|
+
function _resetPrivacyCache() {
|
|
5362
|
+
privacyCache = null;
|
|
5382
5363
|
}
|
|
5383
|
-
function
|
|
5384
|
-
|
|
5385
|
-
return applyExclusions(siblingHomes(cursorHomeDir(), overridden, "chats"), opts.includeExcluded);
|
|
5364
|
+
function isToolOutputRedactionEnabled() {
|
|
5365
|
+
return privacyView().redactToolOutputs;
|
|
5386
5366
|
}
|
|
5387
|
-
function
|
|
5388
|
-
|
|
5389
|
-
return applyExclusions(siblingHomes(cursorIdeHomeDir(), overridden, join4("User", "workspaceStorage")), opts.includeExcluded);
|
|
5367
|
+
function isFilePathRedactionEnabled() {
|
|
5368
|
+
return privacyView().redactFilePaths;
|
|
5390
5369
|
}
|
|
5391
|
-
function
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
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
|
+
}
|
|
5399
5643
|
}
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
function opencodeDbPaths(opts = {}) {
|
|
5403
|
-
const primary = opencodeDbPath();
|
|
5404
|
-
const overridden = !!(process.env.CHAT_RECALL_OPENCODE_DB || sources().opencodeDbPath);
|
|
5405
|
-
const dbFile = basename(primary);
|
|
5406
|
-
const dirs = siblingHomes(dirname4(primary), overridden, "");
|
|
5407
|
-
const dbs = [];
|
|
5408
|
-
for (const d of dirs) {
|
|
5409
|
-
const p = join4(d, dbFile);
|
|
5410
|
-
if (existsSync4(p) && !dbs.includes(p))
|
|
5411
|
-
dbs.push(p);
|
|
5644
|
+
if (chunks.length >= maxChunks) {
|
|
5645
|
+
return chunks.slice(0, maxChunks);
|
|
5412
5646
|
}
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
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
|
+
}
|
|
5428
5665
|
}
|
|
5429
|
-
|
|
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);
|
|
5430
5691
|
}
|
|
5431
|
-
var
|
|
5432
|
-
var
|
|
5433
|
-
"../engine/dist/
|
|
5692
|
+
var INJECTED_BANNERS;
|
|
5693
|
+
var init_chunker = __esm({
|
|
5694
|
+
"../engine/dist/parsers/chunker.js"() {
|
|
5434
5695
|
"use strict";
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
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
|
+
];
|
|
5439
5702
|
}
|
|
5440
5703
|
});
|
|
5441
5704
|
|
|
5442
|
-
// ../engine/dist/core/
|
|
5443
|
-
var
|
|
5444
|
-
__export(
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
isFilePathRedactionEnabled: () => isFilePathRedactionEnabled,
|
|
5453
|
-
isRedactionEnabled: () => isRedactionEnabled,
|
|
5454
|
-
isToolOutputRedactionEnabled: () => isToolOutputRedactionEnabled,
|
|
5455
|
-
redactFilePaths: () => redactFilePaths,
|
|
5456
|
-
redactSecrets: () => redactSecrets,
|
|
5457
|
-
redactToolResultBody: () => redactToolResultBody,
|
|
5458
|
-
scanTextForFindings: () => scanTextForFindings,
|
|
5459
|
-
serverRulePackVersion: () => serverRulePackVersion,
|
|
5460
|
-
validateRedactionRule: () => validateRedactionRule
|
|
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
|
|
5461
5715
|
});
|
|
5462
|
-
import {
|
|
5463
|
-
import {
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
return
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
function looksLikeSecretToken(t) {
|
|
5473
|
-
if (t.length < 32)
|
|
5474
|
-
return false;
|
|
5475
|
-
if (/^[a-f0-9]+$/i.test(t))
|
|
5476
|
-
return true;
|
|
5477
|
-
return /[A-Z]/.test(t) && /[a-z]/.test(t) && /[0-9]/.test(t);
|
|
5478
|
-
}
|
|
5479
|
-
function findContextualSecrets(text) {
|
|
5480
|
-
if (!text)
|
|
5481
|
-
return [];
|
|
5482
|
-
const ranges = [];
|
|
5483
|
-
let m;
|
|
5484
|
-
SECRET_CONTEXT_RE.lastIndex = 0;
|
|
5485
|
-
while ((m = SECRET_CONTEXT_RE.exec(text)) !== null) {
|
|
5486
|
-
const winStart = m.index + m[0].length;
|
|
5487
|
-
const window = text.slice(winStart, winStart + 80);
|
|
5488
|
-
HIGH_ENTROPY_TOKEN_RE.lastIndex = 0;
|
|
5489
|
-
let tm;
|
|
5490
|
-
while ((tm = HIGH_ENTROPY_TOKEN_RE.exec(window)) !== null) {
|
|
5491
|
-
if (!looksLikeSecretToken(tm[0]))
|
|
5492
|
-
continue;
|
|
5493
|
-
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))) {
|
|
5494
|
-
continue;
|
|
5495
|
-
}
|
|
5496
|
-
ranges.push({ start: winStart + tm.index, end: winStart + tm.index + tm[0].length });
|
|
5497
|
-
break;
|
|
5498
|
-
}
|
|
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;
|
|
5499
5726
|
}
|
|
5500
|
-
|
|
5727
|
+
}
|
|
5728
|
+
function legacyAutoSyncedHomes() {
|
|
5729
|
+
const home = homedir2();
|
|
5501
5730
|
const out = [];
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
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 {
|
|
5507
5761
|
}
|
|
5508
|
-
out.push({ ...r });
|
|
5509
5762
|
}
|
|
5510
5763
|
return out;
|
|
5511
5764
|
}
|
|
5512
|
-
function
|
|
5513
|
-
|
|
5514
|
-
if (v === void 0)
|
|
5515
|
-
return null;
|
|
5516
|
-
return v === "1" || v === "true" || v === "yes";
|
|
5517
|
-
}
|
|
5518
|
-
function settingsView() {
|
|
5519
|
-
let mtimeMs = -1;
|
|
5765
|
+
function grandfatherLegacyHomes() {
|
|
5766
|
+
let s;
|
|
5520
5767
|
try {
|
|
5521
|
-
|
|
5768
|
+
s = loadSettings();
|
|
5522
5769
|
} catch {
|
|
5770
|
+
return { seeded: [] };
|
|
5523
5771
|
}
|
|
5524
|
-
if (
|
|
5525
|
-
return
|
|
5526
|
-
|
|
5527
|
-
const
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
extra.push({ label: r.label, pattern: new RegExp(r.pattern, "g") });
|
|
5534
|
-
} catch {
|
|
5535
|
-
}
|
|
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);
|
|
5536
5781
|
}
|
|
5782
|
+
}
|
|
5783
|
+
s.sources.approvedHomes = [...approved];
|
|
5784
|
+
s.sources.homesGrandfathered = true;
|
|
5785
|
+
try {
|
|
5786
|
+
saveSettings(s);
|
|
5537
5787
|
} catch {
|
|
5788
|
+
return { seeded: [] };
|
|
5538
5789
|
}
|
|
5539
|
-
|
|
5540
|
-
return cache;
|
|
5790
|
+
return { seeded };
|
|
5541
5791
|
}
|
|
5542
|
-
function
|
|
5543
|
-
const
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
if (spec.regex.length > MAX_PATTERN_LEN)
|
|
5547
|
-
return { name, reason: `pattern longer than ${MAX_PATTERN_LEN} chars` };
|
|
5548
|
-
let re;
|
|
5792
|
+
function primaryHomes() {
|
|
5793
|
+
const home = homedir2();
|
|
5794
|
+
const env = process.env;
|
|
5795
|
+
let s;
|
|
5549
5796
|
try {
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
return { name, reason: `invalid regex: ${e instanceof Error ? e.message : e}` };
|
|
5797
|
+
s = loadSettings().sources;
|
|
5798
|
+
} catch {
|
|
5799
|
+
s = void 0;
|
|
5554
5800
|
}
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
return "reason" in out ? { ok: false, reason: out.reason } : { ok: true };
|
|
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);
|
|
5566
5811
|
}
|
|
5567
|
-
function
|
|
5568
|
-
const
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
if ("reason" in out)
|
|
5577
|
-
rejected.push(out);
|
|
5578
|
-
else
|
|
5579
|
-
rules.push(out);
|
|
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";
|
|
5580
5821
|
}
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
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";
|
|
5587
5829
|
}
|
|
5588
|
-
function
|
|
5589
|
-
|
|
5830
|
+
function isHomeSynced(path) {
|
|
5831
|
+
const d = homeDecision(path);
|
|
5832
|
+
return d === "primary" || d === "approved";
|
|
5590
5833
|
}
|
|
5591
|
-
function
|
|
5592
|
-
const
|
|
5593
|
-
|
|
5834
|
+
function setDecision(path, decision) {
|
|
5835
|
+
const p = normalizeHomePath(path);
|
|
5836
|
+
let s;
|
|
5837
|
+
try {
|
|
5838
|
+
s = loadSettings();
|
|
5839
|
+
} catch {
|
|
5840
|
+
return false;
|
|
5841
|
+
}
|
|
5842
|
+
const approved = new Set((s.sources.approvedHomes ?? []).map(normalizeHomePath));
|
|
5843
|
+
const declined = new Set((s.sources.declinedHomes ?? []).map(normalizeHomePath));
|
|
5844
|
+
approved.delete(p);
|
|
5845
|
+
declined.delete(p);
|
|
5846
|
+
(decision === "approved" ? approved : declined).add(p);
|
|
5847
|
+
s.sources.approvedHomes = [...approved];
|
|
5848
|
+
s.sources.declinedHomes = [...declined];
|
|
5849
|
+
try {
|
|
5850
|
+
saveSettings(s);
|
|
5851
|
+
} catch {
|
|
5852
|
+
return false;
|
|
5853
|
+
}
|
|
5854
|
+
return true;
|
|
5594
5855
|
}
|
|
5595
|
-
function
|
|
5596
|
-
|
|
5597
|
-
if (env !== null)
|
|
5598
|
-
return env;
|
|
5599
|
-
return settingsView().enabled;
|
|
5856
|
+
function approveHome(path) {
|
|
5857
|
+
return setDecision(path, "approved");
|
|
5600
5858
|
}
|
|
5601
|
-
function
|
|
5602
|
-
|
|
5859
|
+
function declineHome(path) {
|
|
5860
|
+
return setDecision(path, "declined");
|
|
5603
5861
|
}
|
|
5604
|
-
function
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
for (const r of rules) {
|
|
5612
|
-
out = out.replace(r.pattern, (...args) => {
|
|
5613
|
-
const whole = args[0];
|
|
5614
|
-
const m = args.slice(0, args.length);
|
|
5615
|
-
const secret = secretOf(r, m);
|
|
5616
|
-
if (secret == null)
|
|
5617
|
-
return whole;
|
|
5618
|
-
if (opts.count)
|
|
5619
|
-
opts.count.redactions++;
|
|
5620
|
-
return whole.slice(0, whole.length - secret.length) + `[REDACTED:${r.label}]`;
|
|
5621
|
-
});
|
|
5862
|
+
function resetHomeDecision(path) {
|
|
5863
|
+
const p = normalizeHomePath(path);
|
|
5864
|
+
let s;
|
|
5865
|
+
try {
|
|
5866
|
+
s = loadSettings();
|
|
5867
|
+
} catch {
|
|
5868
|
+
return false;
|
|
5622
5869
|
}
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5870
|
+
s.sources.approvedHomes = (s.sources.approvedHomes ?? []).filter((x) => normalizeHomePath(x) !== p);
|
|
5871
|
+
s.sources.declinedHomes = (s.sources.declinedHomes ?? []).filter((x) => normalizeHomePath(x) !== p);
|
|
5872
|
+
try {
|
|
5873
|
+
saveSettings(s);
|
|
5874
|
+
} catch {
|
|
5875
|
+
return false;
|
|
5629
5876
|
}
|
|
5630
|
-
return
|
|
5631
|
-
}
|
|
5632
|
-
function maskSecret(s) {
|
|
5633
|
-
if (!s)
|
|
5634
|
-
return "";
|
|
5635
|
-
if (s.length <= 4)
|
|
5636
|
-
return "*".repeat(s.length);
|
|
5637
|
-
return "*".repeat(Math.min(s.length - 4, 60)) + s.slice(-4);
|
|
5877
|
+
return true;
|
|
5638
5878
|
}
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
globalRegexCache.set(key2, re);
|
|
5879
|
+
var dirOf;
|
|
5880
|
+
var init_home_approval = __esm({
|
|
5881
|
+
"../engine/dist/core/home-approval.js"() {
|
|
5882
|
+
"use strict";
|
|
5883
|
+
init_settings();
|
|
5884
|
+
dirOf = (filePath) => dirname3(filePath);
|
|
5646
5885
|
}
|
|
5647
|
-
|
|
5886
|
+
});
|
|
5887
|
+
|
|
5888
|
+
// ../engine/dist/core/tool-paths.js
|
|
5889
|
+
import { existsSync as existsSync4, readdirSync as readdirSync3 } from "fs";
|
|
5890
|
+
import { homedir as homedir3 } from "os";
|
|
5891
|
+
import { join as join4, dirname as dirname4, basename } from "path";
|
|
5892
|
+
function claudeHomeDir() {
|
|
5893
|
+
return process.env.CHAT_RECALL_CLAUDE_HOME || sources().claudeHome || join4(homedir3(), ".claude");
|
|
5648
5894
|
}
|
|
5649
|
-
function
|
|
5650
|
-
const
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
const mid = lo + hi >> 1;
|
|
5657
|
-
if (starts[mid] < index)
|
|
5658
|
-
lo = mid + 1;
|
|
5659
|
-
else
|
|
5660
|
-
hi = mid;
|
|
5661
|
-
}
|
|
5662
|
-
return lo + 1;
|
|
5895
|
+
function claudeProjectDirs(opts = {}) {
|
|
5896
|
+
const home = homedir3();
|
|
5897
|
+
const dirs = [];
|
|
5898
|
+
const addHome = (d) => {
|
|
5899
|
+
const p = join4(d, "projects");
|
|
5900
|
+
if (existsSync4(p) && !dirs.includes(p))
|
|
5901
|
+
dirs.push(p);
|
|
5663
5902
|
};
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
if (!
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
let m;
|
|
5676
|
-
while ((m = re.exec(text)) !== null) {
|
|
5677
|
-
const secret = secretOf(r, m);
|
|
5678
|
-
if (secret == null) {
|
|
5679
|
-
if (re.lastIndex === m.index)
|
|
5680
|
-
re.lastIndex++;
|
|
5681
|
-
continue;
|
|
5682
|
-
}
|
|
5683
|
-
const at = m.index + (m[0].length - secret.length);
|
|
5684
|
-
const span = `${at}:${secret.length}`;
|
|
5685
|
-
if (!seenSpans.has(span)) {
|
|
5686
|
-
seenSpans.add(span);
|
|
5687
|
-
findings.push({ rule: r.label, line: lineAt(at), preview: maskSecret(secret) });
|
|
5688
|
-
}
|
|
5689
|
-
if (re.lastIndex === m.index)
|
|
5690
|
-
re.lastIndex++;
|
|
5691
|
-
}
|
|
5692
|
-
}
|
|
5693
|
-
for (const { start, end } of findContextualSecrets(text)) {
|
|
5694
|
-
const line = lineAt(start);
|
|
5695
|
-
findings.push({ rule: "secret-context", line, preview: maskSecret(text.slice(start, end)) });
|
|
5696
|
-
}
|
|
5697
|
-
return findings;
|
|
5698
|
-
}
|
|
5699
|
-
function privacyView() {
|
|
5700
|
-
let mtimeMs = -1;
|
|
5701
|
-
try {
|
|
5702
|
-
mtimeMs = statSync3(settingsFilePath()).mtimeMs;
|
|
5703
|
-
} catch {
|
|
5903
|
+
addHome(claudeHomeDir());
|
|
5904
|
+
const homeOverridden = !!(process.env.CHAT_RECALL_CLAUDE_HOME || sources().claudeHome);
|
|
5905
|
+
if (!homeOverridden) {
|
|
5906
|
+
try {
|
|
5907
|
+
for (const entry of readdirSync3(home, { withFileTypes: true })) {
|
|
5908
|
+
if (entry.isDirectory() && entry.name.startsWith(".claude-") && entry.name !== ".claude-code") {
|
|
5909
|
+
addHome(join4(home, entry.name));
|
|
5910
|
+
}
|
|
5911
|
+
}
|
|
5912
|
+
} catch {
|
|
5913
|
+
}
|
|
5704
5914
|
}
|
|
5705
|
-
if (
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
redactFilePaths2 = !!s.privacy?.redactFilePaths;
|
|
5712
|
-
} catch {
|
|
5915
|
+
if (process.env.CLAUDE_DIRS) {
|
|
5916
|
+
for (const d of process.env.CLAUDE_DIRS.split(",")) {
|
|
5917
|
+
const t = d.trim();
|
|
5918
|
+
if (t)
|
|
5919
|
+
addHome(t.startsWith("~/") ? join4(home, t.slice(2)) : t);
|
|
5920
|
+
}
|
|
5713
5921
|
}
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
function _resetPrivacyCache() {
|
|
5718
|
-
privacyCache = null;
|
|
5719
|
-
}
|
|
5720
|
-
function isToolOutputRedactionEnabled() {
|
|
5721
|
-
return privacyView().redactToolOutputs;
|
|
5922
|
+
if (dirs.length === 0)
|
|
5923
|
+
dirs.push(join4(claudeHomeDir(), "projects"));
|
|
5924
|
+
return applyDecisions(dirs, opts.includeExcluded);
|
|
5722
5925
|
}
|
|
5723
|
-
function
|
|
5724
|
-
|
|
5926
|
+
function _setSourceExclusionFilter(fn) {
|
|
5927
|
+
_sourceExclusionFilter = fn;
|
|
5725
5928
|
}
|
|
5726
|
-
function
|
|
5727
|
-
|
|
5728
|
-
return text;
|
|
5729
|
-
return text.replace(PATH_REGEX, (m) => {
|
|
5730
|
-
if (m.startsWith("[REDACTED:"))
|
|
5731
|
-
return m;
|
|
5732
|
-
const h = createHash("sha256").update(m).digest("hex").slice(0, 12);
|
|
5733
|
-
return `[path:${h}]`;
|
|
5734
|
-
});
|
|
5929
|
+
function geminiHomeDir() {
|
|
5930
|
+
return process.env.CHAT_RECALL_GEMINI_HOME || sources().geminiHome || join4(homedir3(), ".gemini");
|
|
5735
5931
|
}
|
|
5736
|
-
function
|
|
5737
|
-
|
|
5738
|
-
if (privacyView().redactFilePaths)
|
|
5739
|
-
out = redactFilePaths(out);
|
|
5740
|
-
return out;
|
|
5932
|
+
function opencodeDbPath() {
|
|
5933
|
+
return process.env.CHAT_RECALL_OPENCODE_DB || sources().opencodeDbPath || join4(homedir3(), ".local", "share", "opencode", "opencode.db");
|
|
5741
5934
|
}
|
|
5742
|
-
function
|
|
5743
|
-
|
|
5744
|
-
return body;
|
|
5745
|
-
return `[redacted: ${toolName} output, ${body.length} chars]`;
|
|
5935
|
+
function codexHomeDir() {
|
|
5936
|
+
return process.env.CHAT_RECALL_CODEX_HOME || sources().codexHome || join4(homedir3(), ".codex");
|
|
5746
5937
|
}
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
"../engine/dist/core/secret-redactor.js"() {
|
|
5750
|
-
"use strict";
|
|
5751
|
-
init_settings();
|
|
5752
|
-
DEFAULT_REDACTION_RULES = [
|
|
5753
|
-
// AWS access tokens. Prefix-anchored: AKIA, ASIA, ABIA (long-term,
|
|
5754
|
-
// STS, EC2). Always exactly 16 hex/alphanumeric after the prefix.
|
|
5755
|
-
// NOTE: this matches only the access-key-ID, NOT the secret key value —
|
|
5756
|
-
// the `env-secret` rule below covers AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN.
|
|
5757
|
-
{ label: "aws-access-token", pattern: /\b(?:AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}\b/g },
|
|
5758
|
-
// AWS STS session tokens. These are long base64 blobs with NO `NAME=` context
|
|
5759
|
-
// when pasted bare into prose/code, so `env-secret` misses them — but they
|
|
5760
|
-
// reliably start with one of a handful of base64 prefixes (the encoded
|
|
5761
|
-
// `{"...` JSON header). Prefix-anchored → low false-positive. This is the
|
|
5762
|
-
// bare-value form that leaked (`search('IQoJEXAMPLEfakeSESSIONtokenNOTreal01234567890abcDEF==...')`).
|
|
5763
|
-
{ label: "aws-session-token", pattern: /\b(?:IQoJ|FwoG|FwoH|FQoG|FQoH|FwoD)[A-Za-z0-9/+]{40,}={0,2}/g },
|
|
5764
|
-
// Env/shell-assignment secrets — a VAR whose name ends in KEY/SECRET/TOKEN/
|
|
5765
|
-
// PASSWORD/CREDENTIAL, then `=`/`:`, then the value. Zero-width lookbehind so
|
|
5766
|
-
// only the VALUE is redacted (the var name stays for context). This is the
|
|
5767
|
-
// dominant real-world leak vector (`export` dumps, .env files, `curl -H`,
|
|
5768
|
-
// tool output) and is exactly what leaked AWS_SECRET_ACCESS_KEY and
|
|
5769
|
-
// AWS_SESSION_TOKEN in cleartext while the access-key-ID rule above caught
|
|
5770
|
-
// only the (least-sensitive) ID.
|
|
5771
|
-
// Suffix list deliberately broad: DB_PASS (our own helm chart's name!),
|
|
5772
|
-
// NPM_AUTH, SENTRY_DSN, SESSION/COOKIE secrets and BEARER vars all leaked
|
|
5773
|
-
// past the original KEY/SECRET/TOKEN/PASSWORD-only list. Values under 12
|
|
5774
|
-
// chars don't redact, so flag-ish vars (BYPASS=true) stay readable.
|
|
5775
|
-
// Context CONSUMED, secret in group 1 — see RedactionRule.secret. As a
|
|
5776
|
-
// lookbehind this was the second most expensive rule in the collector.
|
|
5777
|
-
{ label: "env-secret", secret: 1, pattern: /\b[A-Z][A-Z0-9_]*(?:KEY|SECRET|TOKEN|PASSWORD|PASSWD|PASS|PWD|CREDENTIAL|AUTH|DSN|SESSION|COOKIE|BEARER|SIGNATURE)S?["']?[ \t]{0,3}[:=][ \t]{0,3}["']?([A-Za-z0-9/+_.~=:@-]{12,})/g },
|
|
5778
|
-
// Credentials embedded in a connection URL: scheme://user:PASSWORD@host.
|
|
5779
|
-
// Redacts only the password segment (keeps scheme/user/host for context).
|
|
5780
|
-
//
|
|
5781
|
-
// Scheme-agnostic on purpose. The old rule allowlisted eight DB schemes, so
|
|
5782
|
-
// `https://user:pass@host`, `ssh://`, `ftp://`, `smtp://` and every other
|
|
5783
|
-
// userinfo URL walked through in cleartext — and a `jdbc:postgresql://…`
|
|
5784
|
-
// prefix broke the \b anchor for the one family it did cover. Any scheme
|
|
5785
|
-
// followed by userinfo is a credential, so match the shape, not a list.
|
|
5786
|
-
{ label: "url-password", secret: 1, pattern: /\b[a-zA-Z][a-zA-Z0-9+.-]{1,31}:\/\/[^:@\s/]{1,64}:([^@\s/]{1,256})(?=@)/g },
|
|
5787
|
-
// Bare AWS secret-access-key VALUE: EXACTLY 40 base64 chars, word-bounded,
|
|
5788
|
-
// containing upper+lower+digit. This is the gitleaks heuristic — the mixed-case
|
|
5789
|
-
// requirement excludes 40-char lowercase-hex git SHAs, and the exact-40 bound
|
|
5790
|
-
// excludes longer blobs/tokens. It catches the value when it appears bare in
|
|
5791
|
-
// prose with NO `NAME=` and NO nearby context word (e.g. inside a markdown
|
|
5792
|
-
// table or quoted in analysis) — the residual leak path the env-secret and
|
|
5793
|
-
// contextual rules both miss. Placed AFTER env-secret/url-password so the
|
|
5794
|
-
// keyed/URL forms keep their more-specific labels.
|
|
5795
|
-
// The three stacked lookaheads each re-scanned the same 40 characters, which
|
|
5796
|
-
// made this the MOST expensive rule of all 106 the collector runs (13.1% of a
|
|
5797
|
-
// 29.5 MB scan). The mixed-case-plus-digit test is now one JS pass.
|
|
5798
|
-
{
|
|
5799
|
-
label: "aws-secret-key",
|
|
5800
|
-
secret: 1,
|
|
5801
|
-
pattern: /(?<![A-Za-z0-9/+])([A-Za-z0-9/+]{40})(?![A-Za-z0-9/+])/g,
|
|
5802
|
-
where: (v) => /[a-z]/.test(v) && /[A-Z]/.test(v) && /[0-9]/.test(v)
|
|
5803
|
-
},
|
|
5804
|
-
// GitHub personal access tokens.
|
|
5805
|
-
{ label: "github-pat", pattern: /\bghp_[A-Za-z0-9]{36}\b/g },
|
|
5806
|
-
// GitHub fine-grained PATs.
|
|
5807
|
-
{ label: "github-fine", pattern: /\bgithub_pat_[A-Za-z0-9_]{82}\b/g },
|
|
5808
|
-
// GitHub OAuth.
|
|
5809
|
-
{ label: "github-oauth", pattern: /\bgho_[A-Za-z0-9]{36}\b/g },
|
|
5810
|
-
// GitHub app + refresh.
|
|
5811
|
-
{ label: "github-app", pattern: /\b(?:ghs|ghu|ghr)_[A-Za-z0-9]{36}\b/g },
|
|
5812
|
-
// Slack bot/user/app tokens.
|
|
5813
|
-
{ label: "slack-token", pattern: /\bxox[abopr]-[A-Za-z0-9-]{10,}\b/g },
|
|
5814
|
-
// Slack incoming webhook URL.
|
|
5815
|
-
{ label: "slack-webhook", pattern: /\bhttps:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+\b/g },
|
|
5816
|
-
// Stripe live + test keys.
|
|
5817
|
-
{ label: "stripe-key", pattern: /\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{24,}\b/g },
|
|
5818
|
-
// Anthropic.
|
|
5819
|
-
{ label: "anthropic-key", pattern: /\bsk-ant-[a-zA-Z0-9_-]{40,}\b/g },
|
|
5820
|
-
// OpenAI. Two shapes, deliberately kept apart. The classic key is
|
|
5821
|
-
// `sk-` + base62 with NO hyphens; the modern scoped keys carry an explicit
|
|
5822
|
-
// proj/svcacct/admin segment first. Allowing hyphens after a bare `sk-`
|
|
5823
|
-
// (which is what admitted sk-proj- in the first place) also matches ordinary
|
|
5824
|
-
// kebab-case text — `sk-fix-the-login-redirect-bug`, `.sk-button-primary`,
|
|
5825
|
-
// `sk-2024-01-01-backup` — and this corpus is full of branch names and CSS
|
|
5826
|
-
// classes. Requiring the segment keeps sk-proj- coverage without that.
|
|
5827
|
-
{ label: "openai-key", pattern: /\bsk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,}\b|\bsk-[A-Za-z0-9]{20,}\b/g },
|
|
5828
|
-
// Google API key (AIza…).
|
|
5829
|
-
{ label: "google-api-key", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
|
|
5830
|
-
// Generic JWT — three base64url segments.
|
|
5831
|
-
{ label: "jwt", pattern: /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g },
|
|
5832
|
-
// Private key blocks (PEM). The label is a free word run rather than a fixed
|
|
5833
|
-
// list, so ENCRYPTED PRIVATE KEY and PGP PRIVATE KEY BLOCK are covered
|
|
5834
|
-
// without another enumeration, and \1 forces END to name the same kind as
|
|
5835
|
-
// BEGIN.
|
|
5836
|
-
//
|
|
5837
|
-
// The body is [^-], NOT [\s\S]*?. That is a performance boundary, not a
|
|
5838
|
-
// style choice. An unanchored prefix with an unbounded lazy gap re-scans to
|
|
5839
|
-
// end-of-input from every candidate start, so text carrying many BEGIN
|
|
5840
|
-
// markers and no matching END costs O(n^2): 500KB of that measured 810ms
|
|
5841
|
-
// here, and this runs synchronously on the server's event loop over
|
|
5842
|
-
// tenant-supplied text (services/secret-rescan.ts). A PEM body is base64
|
|
5843
|
-
// plus headers and contains no '-', so excluding it makes the scan stop at
|
|
5844
|
-
// the next delimiter instead of at EOF — same matches, linear cost.
|
|
5845
|
-
{ label: "private-key", pattern: /-----BEGIN ([A-Z0-9 ]{0,32})PRIVATE KEY(?: BLOCK)?-----[^-]{0,10000}?-----END \1PRIVATE KEY(?: BLOCK)?-----/g },
|
|
5846
|
-
// PuTTY keys are not PEM. Match the Private-Lines payload — the actual key
|
|
5847
|
-
// material — rather than spanning the whole file, which would need the same
|
|
5848
|
-
// unbounded gap the rule above just removed. Line-anchored and base64-only,
|
|
5849
|
-
// so it cannot run away.
|
|
5850
|
-
{ label: "putty-key", pattern: /^Private-Lines:[ \t]*\d{1,4}[\r\n]+(?:[A-Za-z0-9+/=]{8,120}[\r\n]+){1,400}/gm },
|
|
5851
|
-
// npm tokens (uuid-shaped — overlap with our UUID filter; intentional).
|
|
5852
|
-
{ label: "npm-token", pattern: /\bnpm_[A-Za-z0-9]{36}\b/g },
|
|
5853
|
-
// .npmrc auth lines: //registry.npmjs.org/:_authToken=VALUE (the key is not
|
|
5854
|
-
// ALL-CAPS, so the env-secret rule above never matched it). The leading `:`
|
|
5855
|
-
// is required — it is what makes this npmrc syntax rather than any shell var
|
|
5856
|
-
// ending in _AUTH, which env-secret already owns and labels better.
|
|
5857
|
-
// [^\s"'`]+ so the closing quote is not swallowed, and a ${VAR} placeholder
|
|
5858
|
-
// is left alone — redacting it destroys a config example and hides nothing.
|
|
5859
|
-
// The rule the perf profile caught the wedged daemon inside: 98.94% of samples.
|
|
5860
|
-
{ label: "npmrc-auth", secret: 1, pattern: /:_(?:authToken|password|auth)[ \t]*=[ \t]*(?!\$\{)([^\s"'`]{12,})/gi },
|
|
5861
|
-
// HTTP Authorization headers. env-secret requires an ALL-CAPS name, and
|
|
5862
|
-
// `Authorization` is mixed case, so both Bearer and Basic walked straight
|
|
5863
|
-
// through — including short opaque tokens the contextual pass also misses
|
|
5864
|
-
// (it needs 32+ chars AND mixed-case-plus-digit).
|
|
5865
|
-
// Same two guards as npmrc-auth: stop at a quote, and skip the obvious
|
|
5866
|
-
// placeholders that fill documentation (<YOUR_TOKEN>, ${TOKEN}, xxx…).
|
|
5867
|
-
{ label: "auth-header", secret: 1, pattern: /\bauthorization[ \t]*:[ \t]*(?:bearer|basic|token)[ \t]+(?![<$])([^\s"'`<]{8,})/gi },
|
|
5868
|
-
// Vendor tokens with unambiguous prefixes. Each is a fixed literal, so a
|
|
5869
|
-
// false positive is essentially impossible and the cost of listing is low.
|
|
5870
|
-
{ label: "gitlab-pat", pattern: /\bglpat-[A-Za-z0-9_-]{20,}\b/g },
|
|
5871
|
-
{ label: "slack-app-token", pattern: /\bxapp-\d-[A-Z0-9]{8,}-\d{10,}-[a-f0-9]{32,}\b/g },
|
|
5872
|
-
{ label: "sendgrid-key", pattern: /\bSG\.[A-Za-z0-9_-]{20,24}\.[A-Za-z0-9_-]{40,50}\b/g },
|
|
5873
|
-
// (?<![-\w]) so `cache-key-<md5>` is not read as a mailgun key.
|
|
5874
|
-
{ label: "mailgun-key", pattern: /(?<![-\w])key-[a-f0-9]{32}\b/g },
|
|
5875
|
-
{ label: "digitalocean-pat", pattern: /\bdop_v1_[a-f0-9]{64}\b/g },
|
|
5876
|
-
{ label: "huggingface-token", pattern: /\bhf_[A-Za-z0-9]{30,}\b/g },
|
|
5877
|
-
{ label: "shopify-token", pattern: /\bshp(?:at|ca|pa|ss)_[a-fA-F0-9]{32}\b/g },
|
|
5878
|
-
{ label: "pypi-token", pattern: /\bpypi-AgEIcHlwaS5vcmc[A-Za-z0-9_-]{50,}\b/g },
|
|
5879
|
-
// No twilio-sid rule: AC…/SK… are Twilio SIDs, which are public account and
|
|
5880
|
-
// key identifiers, not the auth token. Redacting them hid nothing and
|
|
5881
|
-
// collided with 32-hex etags and MD5 sums.
|
|
5882
|
-
// No discord-bot-token rule either: the shape (word.word.word) matched
|
|
5883
|
-
// ordinary dotted symbols such as a.b.ApplicationContextProviderImpl.
|
|
5884
|
-
{ label: "telegram-bot-token", pattern: /\b\d{8,10}:AA[A-Za-z0-9_-]{32,}\b/g },
|
|
5885
|
-
{ label: "doppler-token", pattern: /\bdp\.(?:pt|st|sa|ct)\.[A-Za-z0-9_-]{20,}\b/g },
|
|
5886
|
-
// Azure Storage SAS signature — the `sig=` parameter is the actual secret.
|
|
5887
|
-
{ label: "azure-sas", secret: 1, pattern: /[?&]sig=([A-Za-z0-9%/+=]{20,})/g }
|
|
5888
|
-
];
|
|
5889
|
-
SECRET_CONTEXT_RE = /\b(?:secret|password|passwd|credential|access[ _-]?key|private[ _-]?key|api[ _-]?key|auth(?:oriz\w*)?|bearer|token)\b/gi;
|
|
5890
|
-
HIGH_ENTROPY_TOKEN_RE = /[A-Za-z0-9/+_-]{32,}/g;
|
|
5891
|
-
cache = null;
|
|
5892
|
-
CANARY = "the quick brown fox jumps over the lazy dog 0123456789 commit 8ba1f109551bd432803012645ac136ddd475d0fb src/index.ts:42";
|
|
5893
|
-
MAX_PACK_RULES = 200;
|
|
5894
|
-
MAX_PATTERN_LEN = 512;
|
|
5895
|
-
serverPack = null;
|
|
5896
|
-
globalRegexCache = /* @__PURE__ */ new Map();
|
|
5897
|
-
privacyCache = null;
|
|
5898
|
-
PATH_REGEX = /(?<![A-Za-z0-9_])(?:\/[^\s'"`<>]+|[A-Za-z]:\\[^\s'"`<>]+)/g;
|
|
5899
|
-
}
|
|
5900
|
-
});
|
|
5901
|
-
|
|
5902
|
-
// ../engine/dist/parsers/chunker.js
|
|
5903
|
-
function stripInjectedBanners(text) {
|
|
5904
|
-
let result = text;
|
|
5905
|
-
for (const re of INJECTED_BANNERS)
|
|
5906
|
-
result = result.replace(re, " ");
|
|
5907
|
-
return result;
|
|
5938
|
+
function agyHomeDir() {
|
|
5939
|
+
return process.env.CHAT_RECALL_AGY_HOME || sources().agyHome || join4(homedir3(), ".gemini", "antigravity-cli");
|
|
5908
5940
|
}
|
|
5909
|
-
function
|
|
5910
|
-
|
|
5911
|
-
result = stripInjectedBanners(result);
|
|
5912
|
-
result = result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
5913
|
-
result = result.replace(/\s+/g, " ");
|
|
5914
|
-
return result.trim();
|
|
5941
|
+
function cursorHomeDir() {
|
|
5942
|
+
return process.env.CHAT_RECALL_CURSOR_HOME || sources().cursorHome || join4(homedir3(), ".cursor");
|
|
5915
5943
|
}
|
|
5916
|
-
function
|
|
5917
|
-
|
|
5918
|
-
if (clean.length <= maxChars)
|
|
5919
|
-
return clean ? [clean] : [];
|
|
5920
|
-
const out = [];
|
|
5921
|
-
const overlap = 80;
|
|
5922
|
-
const step = Math.max(1, maxChars - overlap);
|
|
5923
|
-
for (let i = 0; i < clean.length && out.length < maxWindows; i += step) {
|
|
5924
|
-
out.push(clean.slice(i, i + maxChars));
|
|
5925
|
-
}
|
|
5926
|
-
return out;
|
|
5944
|
+
function cursorIdeHomeDir() {
|
|
5945
|
+
return process.env.CHAT_RECALL_CURSOR_IDE_HOME || sources().cursorIdeHome || (process.platform === "darwin" ? join4(homedir3(), "Library", "Application Support", "Cursor") : join4(homedir3(), ".config", "Cursor"));
|
|
5927
5946
|
}
|
|
5928
|
-
function
|
|
5929
|
-
const
|
|
5930
|
-
const
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
created: entry.created,
|
|
5935
|
-
modified: entry.modified,
|
|
5936
|
-
mtime: entry.fileMtime,
|
|
5937
|
-
firstPrompt: entry.firstPrompt || content.firstPrompt,
|
|
5938
|
-
sourceFile: content.sessionPath
|
|
5947
|
+
function siblingHomes(base3, overridden, suffix) {
|
|
5948
|
+
const roots = [];
|
|
5949
|
+
const add = (home) => {
|
|
5950
|
+
const root = suffix ? join4(home, suffix) : home;
|
|
5951
|
+
if (existsSync4(root) && !roots.includes(root))
|
|
5952
|
+
roots.push(root);
|
|
5939
5953
|
};
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
} else {
|
|
5954
|
-
chunks.push({
|
|
5955
|
-
chunkId: `${entry.sessionId}_summary_${i}`,
|
|
5956
|
-
chunkType: "summary",
|
|
5957
|
-
text: chunkText,
|
|
5958
|
-
sourceLine: 0,
|
|
5959
|
-
...baseData
|
|
5960
|
-
});
|
|
5954
|
+
add(base3);
|
|
5955
|
+
if (!overridden) {
|
|
5956
|
+
const parent = dirname4(base3);
|
|
5957
|
+
const leaf = basename(base3);
|
|
5958
|
+
try {
|
|
5959
|
+
for (const entry of readdirSync3(parent, { withFileTypes: true })) {
|
|
5960
|
+
if (!entry.isDirectory())
|
|
5961
|
+
continue;
|
|
5962
|
+
if (entry.name === leaf || !entry.name.startsWith(leaf + "-"))
|
|
5963
|
+
continue;
|
|
5964
|
+
add(join4(parent, entry.name));
|
|
5965
|
+
}
|
|
5966
|
+
} catch {
|
|
5961
5967
|
}
|
|
5962
5968
|
}
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
}
|
|
5976
|
-
}
|
|
5977
|
-
if (chunks.length >= maxChunks) {
|
|
5978
|
-
return chunks.slice(0, maxChunks);
|
|
5979
|
-
}
|
|
5980
|
-
const userStart = firstPrompt ? 1 : 0;
|
|
5981
|
-
for (let i = userStart; i < content.userMessages.length; i++) {
|
|
5982
|
-
if (chunks.length >= maxChunks)
|
|
5983
|
-
break;
|
|
5984
|
-
const msg = content.userMessages[i];
|
|
5985
|
-
if (!msg.text.trim())
|
|
5986
|
-
continue;
|
|
5987
|
-
const windows = windowText(msg.text, maxChars);
|
|
5988
|
-
for (let w = 0; w < windows.length; w++) {
|
|
5989
|
-
if (chunks.length >= maxChunks)
|
|
5990
|
-
break;
|
|
5991
|
-
chunks.push({
|
|
5992
|
-
chunkId: `${entry.sessionId}_user_${i}_${w}`,
|
|
5993
|
-
chunkType: "user_context",
|
|
5994
|
-
text: windows[w],
|
|
5995
|
-
sourceLine: msg.lineNumber,
|
|
5996
|
-
...baseData
|
|
5997
|
-
});
|
|
5969
|
+
return roots;
|
|
5970
|
+
}
|
|
5971
|
+
function applyDecisions(roots, includeExcluded) {
|
|
5972
|
+
if (roots.length === 0)
|
|
5973
|
+
return roots;
|
|
5974
|
+
let kept = roots;
|
|
5975
|
+
if (!includeExcluded) {
|
|
5976
|
+
try {
|
|
5977
|
+
grandfatherLegacyHomes();
|
|
5978
|
+
const approved = kept.filter((r) => isHomeSynced(dirname4(r)));
|
|
5979
|
+
if (approved.length > 0)
|
|
5980
|
+
kept = approved;
|
|
5981
|
+
} catch {
|
|
5998
5982
|
}
|
|
5999
5983
|
}
|
|
6000
|
-
if (
|
|
6001
|
-
|
|
5984
|
+
if (!includeExcluded && _sourceExclusionFilter) {
|
|
5985
|
+
const notExcluded = kept.filter((r) => !_sourceExclusionFilter(r));
|
|
5986
|
+
if (notExcluded.length > 0)
|
|
5987
|
+
kept = notExcluded;
|
|
6002
5988
|
}
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
5989
|
+
return kept;
|
|
5990
|
+
}
|
|
5991
|
+
function applyExclusions(roots, includeExcluded) {
|
|
5992
|
+
return applyDecisions(roots, includeExcluded);
|
|
5993
|
+
}
|
|
5994
|
+
function codexSessionDirs(opts = {}) {
|
|
5995
|
+
const overridden = !!(process.env.CHAT_RECALL_CODEX_HOME || sources().codexHome);
|
|
5996
|
+
return applyExclusions(siblingHomes(codexHomeDir(), overridden, "sessions"), opts.includeExcluded);
|
|
5997
|
+
}
|
|
5998
|
+
function agyBrainDirs(opts = {}) {
|
|
5999
|
+
const overridden = !!(process.env.CHAT_RECALL_AGY_HOME || sources().agyHome);
|
|
6000
|
+
return applyExclusions(siblingHomes(agyHomeDir(), overridden, "brain"), opts.includeExcluded);
|
|
6001
|
+
}
|
|
6002
|
+
function cursorChatDirs(opts = {}) {
|
|
6003
|
+
const overridden = !!(process.env.CHAT_RECALL_CURSOR_HOME || sources().cursorHome);
|
|
6004
|
+
return applyExclusions(siblingHomes(cursorHomeDir(), overridden, "chats"), opts.includeExcluded);
|
|
6005
|
+
}
|
|
6006
|
+
function cursorIdeWorkspaceDirs(opts = {}) {
|
|
6007
|
+
const overridden = !!(process.env.CHAT_RECALL_CURSOR_IDE_HOME || sources().cursorIdeHome);
|
|
6008
|
+
return applyExclusions(siblingHomes(cursorIdeHomeDir(), overridden, join4("User", "workspaceStorage")), opts.includeExcluded);
|
|
6009
|
+
}
|
|
6010
|
+
function cursorIdeGlobalDbs(opts = {}) {
|
|
6011
|
+
const overridden = !!(process.env.CHAT_RECALL_CURSOR_IDE_HOME || sources().cursorIdeHome);
|
|
6012
|
+
const roots = applyExclusions(siblingHomes(cursorIdeHomeDir(), overridden, join4("User", "globalStorage")), opts.includeExcluded);
|
|
6013
|
+
const dbs = [];
|
|
6014
|
+
for (const r of roots) {
|
|
6015
|
+
const p = join4(r, "state.vscdb");
|
|
6016
|
+
if (existsSync4(p) && !dbs.includes(p))
|
|
6017
|
+
dbs.push(p);
|
|
6021
6018
|
}
|
|
6022
|
-
|
|
6023
|
-
|
|
6019
|
+
return dbs;
|
|
6020
|
+
}
|
|
6021
|
+
function opencodeDbPaths(opts = {}) {
|
|
6022
|
+
const primary = opencodeDbPath();
|
|
6023
|
+
const overridden = !!(process.env.CHAT_RECALL_OPENCODE_DB || sources().opencodeDbPath);
|
|
6024
|
+
const dbFile = basename(primary);
|
|
6025
|
+
const dirs = siblingHomes(dirname4(primary), overridden, "");
|
|
6026
|
+
const dbs = [];
|
|
6027
|
+
for (const d of dirs) {
|
|
6028
|
+
const p = join4(d, dbFile);
|
|
6029
|
+
if (existsSync4(p) && !dbs.includes(p))
|
|
6030
|
+
dbs.push(p);
|
|
6024
6031
|
}
|
|
6025
|
-
if (
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
sourceLine: result.lineNumber,
|
|
6041
|
-
...baseData
|
|
6042
|
-
});
|
|
6043
|
-
}
|
|
6044
|
-
}
|
|
6032
|
+
if (dbs.length === 0)
|
|
6033
|
+
dbs.push(primary);
|
|
6034
|
+
return applyExclusions(dbs, opts.includeExcluded);
|
|
6035
|
+
}
|
|
6036
|
+
function extraClaudeHomeDirs() {
|
|
6037
|
+
const extras = sources().extraClaudeHomes ?? [];
|
|
6038
|
+
const primary = claudeHomeDir();
|
|
6039
|
+
return Array.from(new Set(extras)).filter((p) => p && p !== primary);
|
|
6040
|
+
}
|
|
6041
|
+
function claudeHomeDirs() {
|
|
6042
|
+
const overridden = !!(process.env.CHAT_RECALL_CLAUDE_HOME || sources().claudeHome);
|
|
6043
|
+
const homes = siblingHomes(claudeHomeDir(), overridden, "").filter((h) => basename(h) !== ".claude-code");
|
|
6044
|
+
for (const extra of extraClaudeHomeDirs()) {
|
|
6045
|
+
if (existsSync4(extra) && !homes.includes(extra))
|
|
6046
|
+
homes.push(extra);
|
|
6045
6047
|
}
|
|
6046
|
-
return
|
|
6048
|
+
return homes;
|
|
6047
6049
|
}
|
|
6048
|
-
var
|
|
6049
|
-
var
|
|
6050
|
-
"../engine/dist/
|
|
6050
|
+
var sources, _sourceExclusionFilter;
|
|
6051
|
+
var init_tool_paths = __esm({
|
|
6052
|
+
"../engine/dist/core/tool-paths.js"() {
|
|
6051
6053
|
"use strict";
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
/API Error:[^\n]{0,120}/g
|
|
6057
|
-
];
|
|
6054
|
+
init_home_approval();
|
|
6055
|
+
init_settings();
|
|
6056
|
+
sources = loadSourceSettings;
|
|
6057
|
+
_sourceExclusionFilter = null;
|
|
6058
6058
|
}
|
|
6059
6059
|
});
|
|
6060
6060
|
|
|
@@ -7821,6 +7821,28 @@ var init_session_git = __esm({
|
|
|
7821
7821
|
}
|
|
7822
7822
|
});
|
|
7823
7823
|
|
|
7824
|
+
// ../engine/dist/core/session-multi-tool.js
|
|
7825
|
+
function extractTurnsAny(sessionId, opts = {}) {
|
|
7826
|
+
const backend = getBackendForId(sessionId);
|
|
7827
|
+
if (!backend)
|
|
7828
|
+
return { sessionId, found: false, turns: [], startMs: 0, endMs: 0 };
|
|
7829
|
+
return memoInSessionScope(`turns:${sessionId}:${opts.maxTurns ?? ""}:${opts.assistantMax ?? ""}`, () => backend.extractTurns(sessionId, opts));
|
|
7830
|
+
}
|
|
7831
|
+
function replaySessionAny(sessionId) {
|
|
7832
|
+
const backend = getBackendForId(sessionId);
|
|
7833
|
+
if (!backend)
|
|
7834
|
+
return { sessionId, found: false, projectPath: "", files: [], totalLinesAdded: 0, totalLinesRemoved: 0 };
|
|
7835
|
+
return memoInSessionScope(`replay:${sessionId}`, () => backend.replay(sessionId));
|
|
7836
|
+
}
|
|
7837
|
+
var init_session_multi_tool = __esm({
|
|
7838
|
+
"../engine/dist/core/session-multi-tool.js"() {
|
|
7839
|
+
"use strict";
|
|
7840
|
+
init_tool_backend();
|
|
7841
|
+
init_live_session_scan();
|
|
7842
|
+
init_backends();
|
|
7843
|
+
}
|
|
7844
|
+
});
|
|
7845
|
+
|
|
7824
7846
|
// ../engine/dist/core/session-outcome.js
|
|
7825
7847
|
function extractDecisionsFromTurn(turn) {
|
|
7826
7848
|
if (turn.kind !== "assistant_text" || !turn.text)
|
|
@@ -7917,7 +7939,7 @@ function computeOutcome(sessionId, opts = {}) {
|
|
|
7917
7939
|
};
|
|
7918
7940
|
if (!backend)
|
|
7919
7941
|
return notFound;
|
|
7920
|
-
const turnsRes =
|
|
7942
|
+
const turnsRes = extractTurnsAny(sessionId, { assistantMax: 2e3 });
|
|
7921
7943
|
if (!turnsRes.found)
|
|
7922
7944
|
return notFound;
|
|
7923
7945
|
const decisions = [];
|
|
@@ -7956,7 +7978,7 @@ function computeOutcome(sessionId, opts = {}) {
|
|
|
7956
7978
|
totalLinesAdded = fast.totalLinesAdded;
|
|
7957
7979
|
totalLinesRemoved = fast.totalLinesRemoved;
|
|
7958
7980
|
} else {
|
|
7959
|
-
const replay =
|
|
7981
|
+
const replay = replaySessionAny(sessionId);
|
|
7960
7982
|
filesChanged = replay.files.map((f) => f.file);
|
|
7961
7983
|
totalLinesAdded = replay.totalLinesAdded;
|
|
7962
7984
|
totalLinesRemoved = replay.totalLinesRemoved;
|
|
@@ -7995,6 +8017,7 @@ var init_session_outcome = __esm({
|
|
|
7995
8017
|
init_session_sentiment();
|
|
7996
8018
|
init_session_git();
|
|
7997
8019
|
init_tool_backend();
|
|
8020
|
+
init_session_multi_tool();
|
|
7998
8021
|
init_backends();
|
|
7999
8022
|
DECISION_PATTERNS = [
|
|
8000
8023
|
/\b(?:I'?ll|I will|I'?m going to|Going to|Let me|We'?ll|We will|We should|Let'?s|I'?m about to|I'?m going to)\b[^\n.!?]*[.!?]/i,
|
|
@@ -11675,13 +11698,26 @@ function resolveSessionContentGroups(sessionId) {
|
|
|
11675
11698
|
}
|
|
11676
11699
|
function withSessionReadCache(fn) {
|
|
11677
11700
|
readScopeDepth++;
|
|
11701
|
+
if (readScopeDepth === 1)
|
|
11702
|
+
derivedScope = /* @__PURE__ */ new Map();
|
|
11678
11703
|
try {
|
|
11679
11704
|
return fn();
|
|
11680
11705
|
} finally {
|
|
11681
|
-
if (--readScopeDepth === 0)
|
|
11706
|
+
if (--readScopeDepth === 0) {
|
|
11682
11707
|
readScope = null;
|
|
11708
|
+
derivedScope = null;
|
|
11709
|
+
}
|
|
11683
11710
|
}
|
|
11684
11711
|
}
|
|
11712
|
+
function memoInSessionScope(key2, compute) {
|
|
11713
|
+
if (!derivedScope)
|
|
11714
|
+
return compute();
|
|
11715
|
+
if (derivedScope.has(key2))
|
|
11716
|
+
return derivedScope.get(key2);
|
|
11717
|
+
const value = compute();
|
|
11718
|
+
derivedScope.set(key2, value);
|
|
11719
|
+
return value;
|
|
11720
|
+
}
|
|
11685
11721
|
function readSessionGroupText(group) {
|
|
11686
11722
|
let key2 = "";
|
|
11687
11723
|
if (readScopeDepth > 0) {
|
|
@@ -11793,7 +11829,7 @@ function findCodexSessionFileIn(root, sessionId) {
|
|
|
11793
11829
|
}
|
|
11794
11830
|
return scanDir(root);
|
|
11795
11831
|
}
|
|
11796
|
-
var scanScopeDepth, indexCache, readScope, readScopeDepth, geminiCache, MAX_EVENT_LINE_BYTES;
|
|
11832
|
+
var scanScopeDepth, indexCache, readScope, readScopeDepth, derivedScope, geminiCache, MAX_EVENT_LINE_BYTES;
|
|
11797
11833
|
var init_live_session_scan = __esm({
|
|
11798
11834
|
"../engine/dist/core/live-session-scan.js"() {
|
|
11799
11835
|
"use strict";
|
|
@@ -11807,6 +11843,7 @@ var init_live_session_scan = __esm({
|
|
|
11807
11843
|
indexCache = null;
|
|
11808
11844
|
readScope = null;
|
|
11809
11845
|
readScopeDepth = 0;
|
|
11846
|
+
derivedScope = null;
|
|
11810
11847
|
geminiCache = null;
|
|
11811
11848
|
MAX_EVENT_LINE_BYTES = 8 * 1024 * 1024;
|
|
11812
11849
|
}
|
|
@@ -11937,6 +11974,7 @@ var UUID_RE, ClaudeBackend, claudeBackend;
|
|
|
11937
11974
|
var init_claude2 = __esm({
|
|
11938
11975
|
"../engine/dist/core/backends/claude.js"() {
|
|
11939
11976
|
"use strict";
|
|
11977
|
+
init_live_session_scan();
|
|
11940
11978
|
init_tool_paths();
|
|
11941
11979
|
init_live_session_scan();
|
|
11942
11980
|
init_session_outcome();
|
|
@@ -12199,6 +12237,9 @@ var init_claude2 = __esm({
|
|
|
12199
12237
|
* splits show up alongside the parent.
|
|
12200
12238
|
*/
|
|
12201
12239
|
readEvents(rawId) {
|
|
12240
|
+
return memoInSessionScope(`events:claude:${rawId}`, () => this.#readEventsUncached(rawId));
|
|
12241
|
+
}
|
|
12242
|
+
#readEventsUncached(rawId) {
|
|
12202
12243
|
const groups = resolveSessionContentGroups(rawId);
|
|
12203
12244
|
if (groups.length === 0)
|
|
12204
12245
|
return [];
|
|
@@ -18817,7 +18858,7 @@ var init_http = __esm({
|
|
|
18817
18858
|
CLI_VERSION = JSON.parse(readFileSync31(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
|
|
18818
18859
|
} catch {
|
|
18819
18860
|
try {
|
|
18820
|
-
if (true) CLI_VERSION = "0.6.
|
|
18861
|
+
if (true) CLI_VERSION = "0.6.9";
|
|
18821
18862
|
} catch {
|
|
18822
18863
|
}
|
|
18823
18864
|
}
|
|
@@ -18842,27 +18883,6 @@ var init_redact_deep = __esm({
|
|
|
18842
18883
|
}
|
|
18843
18884
|
});
|
|
18844
18885
|
|
|
18845
|
-
// ../engine/dist/core/session-multi-tool.js
|
|
18846
|
-
function extractTurnsAny(sessionId, opts = {}) {
|
|
18847
|
-
const backend = getBackendForId(sessionId);
|
|
18848
|
-
if (!backend)
|
|
18849
|
-
return { sessionId, found: false, turns: [], startMs: 0, endMs: 0 };
|
|
18850
|
-
return backend.extractTurns(sessionId, opts);
|
|
18851
|
-
}
|
|
18852
|
-
function replaySessionAny(sessionId) {
|
|
18853
|
-
const backend = getBackendForId(sessionId);
|
|
18854
|
-
if (!backend)
|
|
18855
|
-
return { sessionId, found: false, projectPath: "", files: [], totalLinesAdded: 0, totalLinesRemoved: 0 };
|
|
18856
|
-
return backend.replay(sessionId);
|
|
18857
|
-
}
|
|
18858
|
-
var init_session_multi_tool = __esm({
|
|
18859
|
-
"../engine/dist/core/session-multi-tool.js"() {
|
|
18860
|
-
"use strict";
|
|
18861
|
-
init_tool_backend();
|
|
18862
|
-
init_backends();
|
|
18863
|
-
}
|
|
18864
|
-
});
|
|
18865
|
-
|
|
18866
18886
|
// src/derived.ts
|
|
18867
18887
|
function collectDerivedRows(ref, mtime, count, maxRowBytes) {
|
|
18868
18888
|
const id = ref.prefixedId;
|
|
@@ -19337,7 +19357,7 @@ var init_telemetry = __esm({
|
|
|
19337
19357
|
init_sync_client();
|
|
19338
19358
|
cliVersion2 = () => {
|
|
19339
19359
|
try {
|
|
19340
|
-
return true ? "0.6.
|
|
19360
|
+
return true ? "0.6.9" : "dev";
|
|
19341
19361
|
} catch {
|
|
19342
19362
|
return "dev";
|
|
19343
19363
|
}
|
|
@@ -19352,6 +19372,7 @@ var collector_health_exports = {};
|
|
|
19352
19372
|
__export(collector_health_exports, {
|
|
19353
19373
|
CRASHLOOP_RESTARTS: () => CRASHLOOP_RESTARTS,
|
|
19354
19374
|
STALE_AFTER_MS: () => STALE_AFTER_MS,
|
|
19375
|
+
applySyncOutcome: () => applySyncOutcome,
|
|
19355
19376
|
collectorHealthPath: () => collectorHealthPath,
|
|
19356
19377
|
judgeHealth: () => judgeHealth,
|
|
19357
19378
|
progressLine: () => progressLine,
|
|
@@ -19362,6 +19383,22 @@ __export(collector_health_exports, {
|
|
|
19362
19383
|
});
|
|
19363
19384
|
import { readFileSync as readFileSync32, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8 } from "node:fs";
|
|
19364
19385
|
import { join as join40, dirname as dirname17 } from "node:path";
|
|
19386
|
+
function applySyncOutcome(prior, outcome, now = Date.now()) {
|
|
19387
|
+
const t = { lastOkAt: prior?.lastOkAt ?? null, failures: prior?.failures ?? 0 };
|
|
19388
|
+
if (prior?.lastError !== void 0)
|
|
19389
|
+
t.lastError = prior.lastError;
|
|
19390
|
+
if (outcome.ok) {
|
|
19391
|
+
t.lastOkAt = now;
|
|
19392
|
+
t.failures = 0;
|
|
19393
|
+
delete t.lastError;
|
|
19394
|
+
return t;
|
|
19395
|
+
}
|
|
19396
|
+
if ((outcome.accepted ?? 0) > 0)
|
|
19397
|
+
t.lastOkAt = now;
|
|
19398
|
+
t.failures += 1;
|
|
19399
|
+
t.lastError = (outcome.error ?? "unknown error").slice(0, 160);
|
|
19400
|
+
return t;
|
|
19401
|
+
}
|
|
19365
19402
|
function progressLine(h) {
|
|
19366
19403
|
const p = h?.progress;
|
|
19367
19404
|
if (!p || p.complete || p.total <= 0)
|
|
@@ -19920,6 +19957,7 @@ var sync_client_exports = {};
|
|
|
19920
19957
|
__export(sync_client_exports, {
|
|
19921
19958
|
BATCH_SCAN_MAX_BYTES: () => BATCH_SCAN_MAX_BYTES,
|
|
19922
19959
|
FULL_BUILD_MAX_BYTES: () => FULL_BUILD_MAX_BYTES,
|
|
19960
|
+
_acceptedUploads: () => _acceptedUploads,
|
|
19923
19961
|
_fetchTenantSyncConfigForTests: () => _fetchTenantSyncConfigForTests,
|
|
19924
19962
|
_resetCapsCacheForTests: () => _resetCapsCacheForTests,
|
|
19925
19963
|
_resetTenantSecurityConfigCache: () => _resetTenantSecurityConfigCache,
|
|
@@ -20183,6 +20221,9 @@ function removeCredentials(serverUrl) {
|
|
|
20183
20221
|
writeFileSync9(credPath(), JSON.stringify({ targets: kept }, null, 2));
|
|
20184
20222
|
return true;
|
|
20185
20223
|
}
|
|
20224
|
+
function _acceptedUploads(url) {
|
|
20225
|
+
return acceptedUploads.get(url) ?? 0;
|
|
20226
|
+
}
|
|
20186
20227
|
async function syncSessions(opts = {}) {
|
|
20187
20228
|
const targets = loadAllCredentials();
|
|
20188
20229
|
if (targets.length === 0) throw new Error("Not logged in \u2014 run `chat-recall login <server-url> --token <token>`");
|
|
@@ -20206,10 +20247,11 @@ async function syncSessions(opts = {}) {
|
|
|
20206
20247
|
};
|
|
20207
20248
|
continue;
|
|
20208
20249
|
}
|
|
20250
|
+
acceptedUploads.set(cred.serverUrl, 0);
|
|
20209
20251
|
try {
|
|
20210
20252
|
const r = await syncToTarget(cred, opts);
|
|
20211
20253
|
noteTargetSuccess(cred.serverUrl);
|
|
20212
|
-
perTarget[cred.serverUrl] = { ok: true };
|
|
20254
|
+
perTarget[cred.serverUrl] = { ok: true, accepted: acceptedUploads.get(cred.serverUrl) ?? 0 };
|
|
20213
20255
|
agg = agg ? {
|
|
20214
20256
|
uploaded: agg.uploaded + r.uploaded,
|
|
20215
20257
|
skipped: agg.skipped + r.skipped,
|
|
@@ -20226,7 +20268,7 @@ async function syncSessions(opts = {}) {
|
|
|
20226
20268
|
} catch (e) {
|
|
20227
20269
|
const msg = e instanceof Error ? e.message : String(e);
|
|
20228
20270
|
errors.push(`${cred.serverUrl}: ${msg}`);
|
|
20229
|
-
perTarget[cred.serverUrl] = { ok: false, error: msg };
|
|
20271
|
+
perTarget[cred.serverUrl] = { ok: false, error: msg, accepted: acceptedUploads.get(cred.serverUrl) ?? 0 };
|
|
20230
20272
|
const next = noteTargetFailure(cred.serverUrl, msg);
|
|
20231
20273
|
const notice = breakerNotice(cred.serverUrl, next);
|
|
20232
20274
|
if (notice) console.error(`[sync] ${notice}`);
|
|
@@ -20492,7 +20534,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
20492
20534
|
return [];
|
|
20493
20535
|
}
|
|
20494
20536
|
});
|
|
20495
|
-
const MAX_BATCH_BYTES = 4 * 1024 * 1024;
|
|
20537
|
+
const MAX_BATCH_BYTES = Number(process.env.CHAT_RECALL_MAX_BATCH_BYTES) || (isLocalHost(cred.serverUrl) ? 4 * 1024 * 1024 : 1024 * 1024);
|
|
20496
20538
|
const sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
20497
20539
|
const explicitThrottle = typeof opts.throttleMs === "number" && opts.throttleMs > 0;
|
|
20498
20540
|
const throttleMs = explicitThrottle ? opts.throttleMs : 0;
|
|
@@ -20527,6 +20569,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
20527
20569
|
signal: ac.signal
|
|
20528
20570
|
});
|
|
20529
20571
|
if (res.ok) {
|
|
20572
|
+
acceptedUploads.set(cred.serverUrl, (acceptedUploads.get(cred.serverUrl) ?? 0) + 1);
|
|
20530
20573
|
const body2 = await res.json().catch(() => ({}));
|
|
20531
20574
|
if (typeof body2.telemetry === "boolean") {
|
|
20532
20575
|
setTelemetryEligible(base3, body2.telemetry);
|
|
@@ -20577,7 +20620,8 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
20577
20620
|
if (attempt >= RETRY_DELAYS_MS.length) throw new Error(`sync failed after ${attempt + 1} attempts: HTTP ${res.status} ${text}`);
|
|
20578
20621
|
const retryAfterS = Number(res.headers.get("retry-after"));
|
|
20579
20622
|
const waitMs = res.status === 429 && retryAfterS > 0 ? retryAfterS * 1e3 : RETRY_DELAYS_MS[attempt];
|
|
20580
|
-
|
|
20623
|
+
const why = res.status === 524 ? ` \u2014 the proxy cut the request at its time limit; the batch is too big for one request` : "";
|
|
20624
|
+
console.error(`[sync] HTTP ${res.status} from ${base3}${why} \u2014 backing off ${Math.round(waitMs / 1e3)}s (attempt ${attempt + 1})`);
|
|
20581
20625
|
await sleep2(waitMs);
|
|
20582
20626
|
continue;
|
|
20583
20627
|
} catch (err) {
|
|
@@ -21436,7 +21480,7 @@ async function syncIncremental(opts = {}) {
|
|
|
21436
21480
|
lock.release();
|
|
21437
21481
|
}
|
|
21438
21482
|
}
|
|
21439
|
-
var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, SYNC_RULES_REFUSAL, hashPath, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
|
|
21483
|
+
var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, SYNC_RULES_REFUSAL, hashPath, acceptedUploads, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
|
|
21440
21484
|
var init_sync_client = __esm({
|
|
21441
21485
|
"src/sync-client.ts"() {
|
|
21442
21486
|
"use strict";
|
|
@@ -21481,7 +21525,7 @@ var init_sync_client = __esm({
|
|
|
21481
21525
|
ownVersion = JSON.parse(readFileSync35(join42(dir, "../package.json"), "utf-8")).version || ownVersion;
|
|
21482
21526
|
} catch {
|
|
21483
21527
|
try {
|
|
21484
|
-
if (true) ownVersion = "0.6.
|
|
21528
|
+
if (true) ownVersion = "0.6.9";
|
|
21485
21529
|
} catch {
|
|
21486
21530
|
}
|
|
21487
21531
|
}
|
|
@@ -21501,6 +21545,7 @@ var init_sync_client = __esm({
|
|
|
21501
21545
|
_fetchTenantSyncConfigForTests = fetchTenantSyncConfig;
|
|
21502
21546
|
SYNC_RULES_REFUSAL = /* @__PURE__ */ Symbol.for("chat-recall.syncRulesRefusal");
|
|
21503
21547
|
hashPath = (p) => p ? "p_" + createHash9("sha256").update(p).digest("hex").slice(0, 12) : "";
|
|
21548
|
+
acceptedUploads = /* @__PURE__ */ new Map();
|
|
21504
21549
|
MAX_DERIVED_ROW_BYTES = 2 * 1024 * 1024;
|
|
21505
21550
|
isLocalHost = isPrivateHost;
|
|
21506
21551
|
CAPS_TTL_MS = 60 * 6e4;
|
|
@@ -25596,7 +25641,7 @@ var init_intent_drain = __esm({
|
|
|
25596
25641
|
ownVersion2 = JSON.parse(readFileSync45(join56(dir, "../package.json"), "utf-8")).version || ownVersion2;
|
|
25597
25642
|
} catch {
|
|
25598
25643
|
try {
|
|
25599
|
-
if (true) ownVersion2 = "0.6.
|
|
25644
|
+
if (true) ownVersion2 = "0.6.9";
|
|
25600
25645
|
} catch {
|
|
25601
25646
|
}
|
|
25602
25647
|
}
|
|
@@ -29945,7 +29990,19 @@ program.command("guard").description("Check a pending tool call against the deci
|
|
|
29945
29990
|
if (target.token) headers.authorization = `Bearer ${target.token}`;
|
|
29946
29991
|
const res = await fetchWithTimeout2(
|
|
29947
29992
|
`${target.base}/api/decisions/check`,
|
|
29948
|
-
{
|
|
29993
|
+
{
|
|
29994
|
+
method: "POST",
|
|
29995
|
+
headers,
|
|
29996
|
+
// The folder group is resolved HERE, from the directory the hook runs
|
|
29997
|
+
// in. The server has no checkout to read it from, and a lookup per
|
|
29998
|
+
// tool call is exactly the cost this guard cannot afford.
|
|
29999
|
+
body: JSON.stringify({
|
|
30000
|
+
names,
|
|
30001
|
+
via,
|
|
30002
|
+
project: opts.project,
|
|
30003
|
+
workspace: resolveWorkspaceId(process.cwd())?.slice(3)
|
|
30004
|
+
})
|
|
30005
|
+
},
|
|
29949
30006
|
4e3
|
|
29950
30007
|
);
|
|
29951
30008
|
if (!res.ok) return;
|