chat-recall 0.6.9 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/auto-update.d.ts +34 -0
- package/dist/auto-update.d.ts.map +1 -1
- package/dist/auto-update.js +51 -8
- package/dist/auto-update.js.map +1 -1
- package/dist/cli.js +1448 -1661
- package/dist/cli.js.map +1 -1
- package/dist/escalate.js +2 -2
- package/dist/escalate.js.map +1 -1
- package/dist/mcp-relay.js +76 -13
- package/dist/mcp-relay.js.map +1 -1
- package/dist/mcp.js +2573 -2223
- package/dist/mcp.js.map +1 -1
- package/dist/relay-lifecycle.d.ts +69 -0
- package/dist/relay-lifecycle.d.ts.map +1 -0
- package/dist/relay-lifecycle.js +117 -0
- package/dist/relay-lifecycle.js.map +1 -0
- package/dist/repair.d.ts +2 -0
- package/dist/repair.d.ts.map +1 -1
- package/dist/repair.js +23 -3
- package/dist/repair.js.map +1 -1
- package/dist/scan-worker.js +48 -18
- package/dist/service-installer.d.ts.map +1 -1
- package/dist/service-installer.js +7 -2
- package/dist/service-installer.js.map +1 -1
- package/dist/sync-client.d.ts +38 -5
- package/dist/sync-client.d.ts.map +1 -1
- package/dist/sync-client.js +191 -58
- package/dist/sync-client.js.map +1 -1
- package/dist/sync-supervisor.d.ts +43 -0
- package/dist/sync-supervisor.d.ts.map +1 -0
- package/dist/sync-supervisor.js +84 -0
- package/dist/sync-supervisor.js.map +1 -0
- package/dist/watch.js +753 -998
- package/package.json +1 -1
- package/skills/chat-recall/SKILL.md +2 -1
- package/skills/chat-recall-memory/SKILL.md +15 -0
package/dist/watch.js
CHANGED
|
@@ -115,6 +115,25 @@ var init_tool_backend = __esm({
|
|
|
115
115
|
}
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
+
// ../engine/dist/parsers/banners.js
|
|
119
|
+
function stripInjectedBanners(text) {
|
|
120
|
+
let result = text;
|
|
121
|
+
for (const re of INJECTED_BANNERS)
|
|
122
|
+
result = result.replace(re, " ");
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
var INJECTED_BANNERS;
|
|
126
|
+
var init_banners = __esm({
|
|
127
|
+
"../engine/dist/parsers/banners.js"() {
|
|
128
|
+
"use strict";
|
|
129
|
+
INJECTED_BANNERS = [
|
|
130
|
+
/MCP issues detected\. ?Run \/mcp list for status\.?/g,
|
|
131
|
+
/Context low[^\n]*Run \/compact[^\n]*/g,
|
|
132
|
+
/API Error:[^\n]{0,120}/g
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
118
137
|
// ../engine/dist/core/project-path-match.js
|
|
119
138
|
function canonicalProjectPath(p) {
|
|
120
139
|
return (p || "").trim().replace(/\\/g, "/").replace(/[-_]/g, "/").replace(/\/{2,}/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
@@ -5031,572 +5050,6 @@ var init_settings = __esm({
|
|
|
5031
5050
|
}
|
|
5032
5051
|
});
|
|
5033
5052
|
|
|
5034
|
-
// ../engine/dist/core/secret-redactor.js
|
|
5035
|
-
import { statSync as statSync3 } from "fs";
|
|
5036
|
-
function secretOf(rule, m) {
|
|
5037
|
-
const raw = rule.secret != null ? m[rule.secret] : m[0];
|
|
5038
|
-
if (raw == null)
|
|
5039
|
-
return null;
|
|
5040
|
-
if (rule.where && !rule.where(raw))
|
|
5041
|
-
return null;
|
|
5042
|
-
return raw;
|
|
5043
|
-
}
|
|
5044
|
-
function looksLikeSecretToken(t) {
|
|
5045
|
-
if (t.length < 32)
|
|
5046
|
-
return false;
|
|
5047
|
-
if (/^[a-f0-9]+$/i.test(t))
|
|
5048
|
-
return true;
|
|
5049
|
-
return /[A-Z]/.test(t) && /[a-z]/.test(t) && /[0-9]/.test(t);
|
|
5050
|
-
}
|
|
5051
|
-
function findContextualSecrets(text) {
|
|
5052
|
-
if (!text)
|
|
5053
|
-
return [];
|
|
5054
|
-
const ranges = [];
|
|
5055
|
-
let m;
|
|
5056
|
-
SECRET_CONTEXT_RE.lastIndex = 0;
|
|
5057
|
-
while ((m = SECRET_CONTEXT_RE.exec(text)) !== null) {
|
|
5058
|
-
const winStart = m.index + m[0].length;
|
|
5059
|
-
const window = text.slice(winStart, winStart + 80);
|
|
5060
|
-
HIGH_ENTROPY_TOKEN_RE.lastIndex = 0;
|
|
5061
|
-
let tm;
|
|
5062
|
-
while ((tm = HIGH_ENTROPY_TOKEN_RE.exec(window)) !== null) {
|
|
5063
|
-
if (!looksLikeSecretToken(tm[0]))
|
|
5064
|
-
continue;
|
|
5065
|
-
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))) {
|
|
5066
|
-
continue;
|
|
5067
|
-
}
|
|
5068
|
-
ranges.push({ start: winStart + tm.index, end: winStart + tm.index + tm[0].length });
|
|
5069
|
-
break;
|
|
5070
|
-
}
|
|
5071
|
-
}
|
|
5072
|
-
ranges.sort((a, b) => a.start - b.start);
|
|
5073
|
-
const out = [];
|
|
5074
|
-
for (const r of ranges) {
|
|
5075
|
-
const last = out[out.length - 1];
|
|
5076
|
-
if (last && r.start < last.end) {
|
|
5077
|
-
last.end = Math.max(last.end, r.end);
|
|
5078
|
-
continue;
|
|
5079
|
-
}
|
|
5080
|
-
out.push({ ...r });
|
|
5081
|
-
}
|
|
5082
|
-
return out;
|
|
5083
|
-
}
|
|
5084
|
-
function isEnvRedactionRequested() {
|
|
5085
|
-
const v = process.env.CHAT_RECALL_REDACT_INDEX;
|
|
5086
|
-
if (v === void 0)
|
|
5087
|
-
return null;
|
|
5088
|
-
return v === "1" || v === "true" || v === "yes";
|
|
5089
|
-
}
|
|
5090
|
-
function settingsView() {
|
|
5091
|
-
let mtimeMs = -1;
|
|
5092
|
-
try {
|
|
5093
|
-
mtimeMs = statSync3(settingsFilePath()).mtimeMs;
|
|
5094
|
-
} catch {
|
|
5095
|
-
}
|
|
5096
|
-
if (cache && cache.mtimeMs === mtimeMs)
|
|
5097
|
-
return cache;
|
|
5098
|
-
let enabled = false;
|
|
5099
|
-
const extra = [];
|
|
5100
|
-
try {
|
|
5101
|
-
const s = loadSettings();
|
|
5102
|
-
enabled = !!s.privacy?.redactIndex;
|
|
5103
|
-
for (const r of s.privacy?.redactionRules ?? []) {
|
|
5104
|
-
try {
|
|
5105
|
-
extra.push({ label: r.label, pattern: new RegExp(r.pattern, "g") });
|
|
5106
|
-
} catch {
|
|
5107
|
-
}
|
|
5108
|
-
}
|
|
5109
|
-
} catch {
|
|
5110
|
-
}
|
|
5111
|
-
cache = { mtimeMs, enabled, rules: [...DEFAULT_REDACTION_RULES, ...extra] };
|
|
5112
|
-
return cache;
|
|
5113
|
-
}
|
|
5114
|
-
function compileServerRule(spec) {
|
|
5115
|
-
const name = spec.name || "(unnamed)";
|
|
5116
|
-
if (!spec.regex)
|
|
5117
|
-
return { name, reason: "empty pattern" };
|
|
5118
|
-
if (spec.regex.length > MAX_PATTERN_LEN)
|
|
5119
|
-
return { name, reason: `pattern longer than ${MAX_PATTERN_LEN} chars` };
|
|
5120
|
-
let re;
|
|
5121
|
-
try {
|
|
5122
|
-
const flags = "g" + (spec.flags || "").replace(/[^ims]/g, "");
|
|
5123
|
-
re = new RegExp(spec.regex, flags);
|
|
5124
|
-
} catch (e) {
|
|
5125
|
-
return { name, reason: `invalid regex: ${e instanceof Error ? e.message : e}` };
|
|
5126
|
-
}
|
|
5127
|
-
if (re.test(""))
|
|
5128
|
-
return { name, reason: "matches the empty string (would redact everything)" };
|
|
5129
|
-
re.lastIndex = 0;
|
|
5130
|
-
if (re.test(CANARY))
|
|
5131
|
-
return { name, reason: "matches benign text \u2014 too broad" };
|
|
5132
|
-
re.lastIndex = 0;
|
|
5133
|
-
return { label: `${spec.source === "pack" ? "pack" : "tenant"}:${name}`, pattern: re };
|
|
5134
|
-
}
|
|
5135
|
-
function installServerRulePack(pack) {
|
|
5136
|
-
const rejected = [];
|
|
5137
|
-
const rules = [];
|
|
5138
|
-
for (const spec of pack.rules || []) {
|
|
5139
|
-
if (rules.length >= MAX_PACK_RULES) {
|
|
5140
|
-
rejected.push({ name: spec.name || "(unnamed)", reason: `pack exceeds ${MAX_PACK_RULES} rules` });
|
|
5141
|
-
continue;
|
|
5142
|
-
}
|
|
5143
|
-
const out = compileServerRule(spec);
|
|
5144
|
-
if ("reason" in out)
|
|
5145
|
-
rejected.push(out);
|
|
5146
|
-
else
|
|
5147
|
-
rules.push(out);
|
|
5148
|
-
}
|
|
5149
|
-
const version = pack.version || "";
|
|
5150
|
-
serverPack = { version, rules };
|
|
5151
|
-
return { version, accepted: rules.length, rejected };
|
|
5152
|
-
}
|
|
5153
|
-
function serverRulePackVersion() {
|
|
5154
|
-
return serverPack ? serverPack.version || "unversioned" : null;
|
|
5155
|
-
}
|
|
5156
|
-
function activeRules() {
|
|
5157
|
-
const base2 = settingsView().rules;
|
|
5158
|
-
return serverPack && serverPack.rules.length > 0 ? [...base2, ...serverPack.rules] : base2;
|
|
5159
|
-
}
|
|
5160
|
-
function isRedactionEnabled() {
|
|
5161
|
-
const env = isEnvRedactionRequested();
|
|
5162
|
-
if (env !== null)
|
|
5163
|
-
return env;
|
|
5164
|
-
return settingsView().enabled;
|
|
5165
|
-
}
|
|
5166
|
-
function redactSecrets(text, opts = {}) {
|
|
5167
|
-
if (!opts.force && !isRedactionEnabled())
|
|
5168
|
-
return text;
|
|
5169
|
-
if (!text)
|
|
5170
|
-
return text;
|
|
5171
|
-
const rules = opts.rules || activeRules();
|
|
5172
|
-
let out = text;
|
|
5173
|
-
for (const r of rules) {
|
|
5174
|
-
out = out.replace(r.pattern, (...args) => {
|
|
5175
|
-
const whole = args[0];
|
|
5176
|
-
const m = args.slice(0, args.length);
|
|
5177
|
-
const secret = secretOf(r, m);
|
|
5178
|
-
if (secret == null)
|
|
5179
|
-
return whole;
|
|
5180
|
-
if (opts.count)
|
|
5181
|
-
opts.count.redactions++;
|
|
5182
|
-
return whole.slice(0, whole.length - secret.length) + `[REDACTED:${r.label}]`;
|
|
5183
|
-
});
|
|
5184
|
-
}
|
|
5185
|
-
const ctx = findContextualSecrets(out);
|
|
5186
|
-
for (let i = ctx.length - 1; i >= 0; i--) {
|
|
5187
|
-
const { start, end } = ctx[i];
|
|
5188
|
-
if (opts.count)
|
|
5189
|
-
opts.count.redactions++;
|
|
5190
|
-
out = out.slice(0, start) + "[REDACTED:secret-context]" + out.slice(end);
|
|
5191
|
-
}
|
|
5192
|
-
return out;
|
|
5193
|
-
}
|
|
5194
|
-
function maskSecret(s) {
|
|
5195
|
-
if (!s)
|
|
5196
|
-
return "";
|
|
5197
|
-
if (s.length <= 4)
|
|
5198
|
-
return "*".repeat(s.length);
|
|
5199
|
-
return "*".repeat(Math.min(s.length - 4, 60)) + s.slice(-4);
|
|
5200
|
-
}
|
|
5201
|
-
function globalRegexFor(pattern) {
|
|
5202
|
-
const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
|
|
5203
|
-
const key2 = pattern.source + "\0" + flags;
|
|
5204
|
-
let re = globalRegexCache.get(key2);
|
|
5205
|
-
if (!re) {
|
|
5206
|
-
re = new RegExp(pattern.source, flags);
|
|
5207
|
-
globalRegexCache.set(key2, re);
|
|
5208
|
-
}
|
|
5209
|
-
return re;
|
|
5210
|
-
}
|
|
5211
|
-
function makeLineResolver(text) {
|
|
5212
|
-
const starts = [];
|
|
5213
|
-
for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1))
|
|
5214
|
-
starts.push(i);
|
|
5215
|
-
return (index) => {
|
|
5216
|
-
let lo = 0, hi = starts.length;
|
|
5217
|
-
while (lo < hi) {
|
|
5218
|
-
const mid = lo + hi >> 1;
|
|
5219
|
-
if (starts[mid] < index)
|
|
5220
|
-
lo = mid + 1;
|
|
5221
|
-
else
|
|
5222
|
-
hi = mid;
|
|
5223
|
-
}
|
|
5224
|
-
return lo + 1;
|
|
5225
|
-
};
|
|
5226
|
-
}
|
|
5227
|
-
function scanTextForFindings(text, opts = {}) {
|
|
5228
|
-
if (!text)
|
|
5229
|
-
return [];
|
|
5230
|
-
const rules = opts.rules || activeRules();
|
|
5231
|
-
const findings = [];
|
|
5232
|
-
const seenSpans = /* @__PURE__ */ new Set();
|
|
5233
|
-
const lineAt = makeLineResolver(text);
|
|
5234
|
-
for (const r of rules) {
|
|
5235
|
-
const re = globalRegexFor(r.pattern);
|
|
5236
|
-
re.lastIndex = 0;
|
|
5237
|
-
let m;
|
|
5238
|
-
while ((m = re.exec(text)) !== null) {
|
|
5239
|
-
const secret = secretOf(r, m);
|
|
5240
|
-
if (secret == null) {
|
|
5241
|
-
if (re.lastIndex === m.index)
|
|
5242
|
-
re.lastIndex++;
|
|
5243
|
-
continue;
|
|
5244
|
-
}
|
|
5245
|
-
const at = m.index + (m[0].length - secret.length);
|
|
5246
|
-
const span = `${at}:${secret.length}`;
|
|
5247
|
-
if (!seenSpans.has(span)) {
|
|
5248
|
-
seenSpans.add(span);
|
|
5249
|
-
findings.push({ rule: r.label, line: lineAt(at), preview: maskSecret(secret) });
|
|
5250
|
-
}
|
|
5251
|
-
if (re.lastIndex === m.index)
|
|
5252
|
-
re.lastIndex++;
|
|
5253
|
-
}
|
|
5254
|
-
}
|
|
5255
|
-
for (const { start, end } of findContextualSecrets(text)) {
|
|
5256
|
-
const line = lineAt(start);
|
|
5257
|
-
findings.push({ rule: "secret-context", line, preview: maskSecret(text.slice(start, end)) });
|
|
5258
|
-
}
|
|
5259
|
-
return findings;
|
|
5260
|
-
}
|
|
5261
|
-
function privacyView() {
|
|
5262
|
-
let mtimeMs = -1;
|
|
5263
|
-
try {
|
|
5264
|
-
mtimeMs = statSync3(settingsFilePath()).mtimeMs;
|
|
5265
|
-
} catch {
|
|
5266
|
-
}
|
|
5267
|
-
if (privacyCache && privacyCache.mtimeMs === mtimeMs)
|
|
5268
|
-
return privacyCache;
|
|
5269
|
-
let redactToolOutputs = false, redactFilePaths = false;
|
|
5270
|
-
try {
|
|
5271
|
-
const s = loadSettings();
|
|
5272
|
-
redactToolOutputs = !!s.privacy?.redactToolOutputs;
|
|
5273
|
-
redactFilePaths = !!s.privacy?.redactFilePaths;
|
|
5274
|
-
} catch {
|
|
5275
|
-
}
|
|
5276
|
-
privacyCache = { mtimeMs, redactToolOutputs, redactFilePaths };
|
|
5277
|
-
return privacyCache;
|
|
5278
|
-
}
|
|
5279
|
-
function isToolOutputRedactionEnabled() {
|
|
5280
|
-
return privacyView().redactToolOutputs;
|
|
5281
|
-
}
|
|
5282
|
-
function redactToolResultBody(toolName, body) {
|
|
5283
|
-
if (!body)
|
|
5284
|
-
return body;
|
|
5285
|
-
return `[redacted: ${toolName} output, ${body.length} chars]`;
|
|
5286
|
-
}
|
|
5287
|
-
var DEFAULT_REDACTION_RULES, SECRET_CONTEXT_RE, HIGH_ENTROPY_TOKEN_RE, cache, CANARY, MAX_PACK_RULES, MAX_PATTERN_LEN, serverPack, globalRegexCache, privacyCache;
|
|
5288
|
-
var init_secret_redactor = __esm({
|
|
5289
|
-
"../engine/dist/core/secret-redactor.js"() {
|
|
5290
|
-
"use strict";
|
|
5291
|
-
init_settings();
|
|
5292
|
-
DEFAULT_REDACTION_RULES = [
|
|
5293
|
-
// AWS access tokens. Prefix-anchored: AKIA, ASIA, ABIA (long-term,
|
|
5294
|
-
// STS, EC2). Always exactly 16 hex/alphanumeric after the prefix.
|
|
5295
|
-
// NOTE: this matches only the access-key-ID, NOT the secret key value —
|
|
5296
|
-
// the `env-secret` rule below covers AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN.
|
|
5297
|
-
{ label: "aws-access-token", pattern: /\b(?:AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}\b/g },
|
|
5298
|
-
// AWS STS session tokens. These are long base64 blobs with NO `NAME=` context
|
|
5299
|
-
// when pasted bare into prose/code, so `env-secret` misses them — but they
|
|
5300
|
-
// reliably start with one of a handful of base64 prefixes (the encoded
|
|
5301
|
-
// `{"...` JSON header). Prefix-anchored → low false-positive. This is the
|
|
5302
|
-
// bare-value form that leaked (`search('IQoJEXAMPLEfakeSESSIONtokenNOTreal01234567890abcDEF==...')`).
|
|
5303
|
-
{ label: "aws-session-token", pattern: /\b(?:IQoJ|FwoG|FwoH|FQoG|FQoH|FwoD)[A-Za-z0-9/+]{40,}={0,2}/g },
|
|
5304
|
-
// Env/shell-assignment secrets — a VAR whose name ends in KEY/SECRET/TOKEN/
|
|
5305
|
-
// PASSWORD/CREDENTIAL, then `=`/`:`, then the value. Zero-width lookbehind so
|
|
5306
|
-
// only the VALUE is redacted (the var name stays for context). This is the
|
|
5307
|
-
// dominant real-world leak vector (`export` dumps, .env files, `curl -H`,
|
|
5308
|
-
// tool output) and is exactly what leaked AWS_SECRET_ACCESS_KEY and
|
|
5309
|
-
// AWS_SESSION_TOKEN in cleartext while the access-key-ID rule above caught
|
|
5310
|
-
// only the (least-sensitive) ID.
|
|
5311
|
-
// Suffix list deliberately broad: DB_PASS (our own helm chart's name!),
|
|
5312
|
-
// NPM_AUTH, SENTRY_DSN, SESSION/COOKIE secrets and BEARER vars all leaked
|
|
5313
|
-
// past the original KEY/SECRET/TOKEN/PASSWORD-only list. Values under 12
|
|
5314
|
-
// chars don't redact, so flag-ish vars (BYPASS=true) stay readable.
|
|
5315
|
-
// Context CONSUMED, secret in group 1 — see RedactionRule.secret. As a
|
|
5316
|
-
// lookbehind this was the second most expensive rule in the collector.
|
|
5317
|
-
{ 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 },
|
|
5318
|
-
// Credentials embedded in a connection URL: scheme://user:PASSWORD@host.
|
|
5319
|
-
// Redacts only the password segment (keeps scheme/user/host for context).
|
|
5320
|
-
//
|
|
5321
|
-
// Scheme-agnostic on purpose. The old rule allowlisted eight DB schemes, so
|
|
5322
|
-
// `https://user:pass@host`, `ssh://`, `ftp://`, `smtp://` and every other
|
|
5323
|
-
// userinfo URL walked through in cleartext — and a `jdbc:postgresql://…`
|
|
5324
|
-
// prefix broke the \b anchor for the one family it did cover. Any scheme
|
|
5325
|
-
// followed by userinfo is a credential, so match the shape, not a list.
|
|
5326
|
-
{ label: "url-password", secret: 1, pattern: /\b[a-zA-Z][a-zA-Z0-9+.-]{1,31}:\/\/[^:@\s/]{1,64}:([^@\s/]{1,256})(?=@)/g },
|
|
5327
|
-
// Bare AWS secret-access-key VALUE: EXACTLY 40 base64 chars, word-bounded,
|
|
5328
|
-
// containing upper+lower+digit. This is the gitleaks heuristic — the mixed-case
|
|
5329
|
-
// requirement excludes 40-char lowercase-hex git SHAs, and the exact-40 bound
|
|
5330
|
-
// excludes longer blobs/tokens. It catches the value when it appears bare in
|
|
5331
|
-
// prose with NO `NAME=` and NO nearby context word (e.g. inside a markdown
|
|
5332
|
-
// table or quoted in analysis) — the residual leak path the env-secret and
|
|
5333
|
-
// contextual rules both miss. Placed AFTER env-secret/url-password so the
|
|
5334
|
-
// keyed/URL forms keep their more-specific labels.
|
|
5335
|
-
// The three stacked lookaheads each re-scanned the same 40 characters, which
|
|
5336
|
-
// made this the MOST expensive rule of all 106 the collector runs (13.1% of a
|
|
5337
|
-
// 29.5 MB scan). The mixed-case-plus-digit test is now one JS pass.
|
|
5338
|
-
{
|
|
5339
|
-
label: "aws-secret-key",
|
|
5340
|
-
secret: 1,
|
|
5341
|
-
pattern: /(?<![A-Za-z0-9/+])([A-Za-z0-9/+]{40})(?![A-Za-z0-9/+])/g,
|
|
5342
|
-
where: (v) => /[a-z]/.test(v) && /[A-Z]/.test(v) && /[0-9]/.test(v)
|
|
5343
|
-
},
|
|
5344
|
-
// GitHub personal access tokens.
|
|
5345
|
-
{ label: "github-pat", pattern: /\bghp_[A-Za-z0-9]{36}\b/g },
|
|
5346
|
-
// GitHub fine-grained PATs.
|
|
5347
|
-
{ label: "github-fine", pattern: /\bgithub_pat_[A-Za-z0-9_]{82}\b/g },
|
|
5348
|
-
// GitHub OAuth.
|
|
5349
|
-
{ label: "github-oauth", pattern: /\bgho_[A-Za-z0-9]{36}\b/g },
|
|
5350
|
-
// GitHub app + refresh.
|
|
5351
|
-
{ label: "github-app", pattern: /\b(?:ghs|ghu|ghr)_[A-Za-z0-9]{36}\b/g },
|
|
5352
|
-
// Slack bot/user/app tokens.
|
|
5353
|
-
{ label: "slack-token", pattern: /\bxox[abopr]-[A-Za-z0-9-]{10,}\b/g },
|
|
5354
|
-
// Slack incoming webhook URL.
|
|
5355
|
-
{ label: "slack-webhook", pattern: /\bhttps:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+\b/g },
|
|
5356
|
-
// Stripe live + test keys.
|
|
5357
|
-
{ label: "stripe-key", pattern: /\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{24,}\b/g },
|
|
5358
|
-
// Anthropic.
|
|
5359
|
-
{ label: "anthropic-key", pattern: /\bsk-ant-[a-zA-Z0-9_-]{40,}\b/g },
|
|
5360
|
-
// OpenAI. Two shapes, deliberately kept apart. The classic key is
|
|
5361
|
-
// `sk-` + base62 with NO hyphens; the modern scoped keys carry an explicit
|
|
5362
|
-
// proj/svcacct/admin segment first. Allowing hyphens after a bare `sk-`
|
|
5363
|
-
// (which is what admitted sk-proj- in the first place) also matches ordinary
|
|
5364
|
-
// kebab-case text — `sk-fix-the-login-redirect-bug`, `.sk-button-primary`,
|
|
5365
|
-
// `sk-2024-01-01-backup` — and this corpus is full of branch names and CSS
|
|
5366
|
-
// classes. Requiring the segment keeps sk-proj- coverage without that.
|
|
5367
|
-
{ label: "openai-key", pattern: /\bsk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,}\b|\bsk-[A-Za-z0-9]{20,}\b/g },
|
|
5368
|
-
// Google API key (AIza…).
|
|
5369
|
-
{ label: "google-api-key", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
|
|
5370
|
-
// Generic JWT — three base64url segments.
|
|
5371
|
-
{ label: "jwt", pattern: /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g },
|
|
5372
|
-
// Private key blocks (PEM). The label is a free word run rather than a fixed
|
|
5373
|
-
// list, so ENCRYPTED PRIVATE KEY and PGP PRIVATE KEY BLOCK are covered
|
|
5374
|
-
// without another enumeration, and \1 forces END to name the same kind as
|
|
5375
|
-
// BEGIN.
|
|
5376
|
-
//
|
|
5377
|
-
// The body is [^-], NOT [\s\S]*?. That is a performance boundary, not a
|
|
5378
|
-
// style choice. An unanchored prefix with an unbounded lazy gap re-scans to
|
|
5379
|
-
// end-of-input from every candidate start, so text carrying many BEGIN
|
|
5380
|
-
// markers and no matching END costs O(n^2): 500KB of that measured 810ms
|
|
5381
|
-
// here, and this runs synchronously on the server's event loop over
|
|
5382
|
-
// tenant-supplied text (services/secret-rescan.ts). A PEM body is base64
|
|
5383
|
-
// plus headers and contains no '-', so excluding it makes the scan stop at
|
|
5384
|
-
// the next delimiter instead of at EOF — same matches, linear cost.
|
|
5385
|
-
{ label: "private-key", pattern: /-----BEGIN ([A-Z0-9 ]{0,32})PRIVATE KEY(?: BLOCK)?-----[^-]{0,10000}?-----END \1PRIVATE KEY(?: BLOCK)?-----/g },
|
|
5386
|
-
// PuTTY keys are not PEM. Match the Private-Lines payload — the actual key
|
|
5387
|
-
// material — rather than spanning the whole file, which would need the same
|
|
5388
|
-
// unbounded gap the rule above just removed. Line-anchored and base64-only,
|
|
5389
|
-
// so it cannot run away.
|
|
5390
|
-
{ label: "putty-key", pattern: /^Private-Lines:[ \t]*\d{1,4}[\r\n]+(?:[A-Za-z0-9+/=]{8,120}[\r\n]+){1,400}/gm },
|
|
5391
|
-
// npm tokens (uuid-shaped — overlap with our UUID filter; intentional).
|
|
5392
|
-
{ label: "npm-token", pattern: /\bnpm_[A-Za-z0-9]{36}\b/g },
|
|
5393
|
-
// .npmrc auth lines: //registry.npmjs.org/:_authToken=VALUE (the key is not
|
|
5394
|
-
// ALL-CAPS, so the env-secret rule above never matched it). The leading `:`
|
|
5395
|
-
// is required — it is what makes this npmrc syntax rather than any shell var
|
|
5396
|
-
// ending in _AUTH, which env-secret already owns and labels better.
|
|
5397
|
-
// [^\s"'`]+ so the closing quote is not swallowed, and a ${VAR} placeholder
|
|
5398
|
-
// is left alone — redacting it destroys a config example and hides nothing.
|
|
5399
|
-
// The rule the perf profile caught the wedged daemon inside: 98.94% of samples.
|
|
5400
|
-
{ label: "npmrc-auth", secret: 1, pattern: /:_(?:authToken|password|auth)[ \t]*=[ \t]*(?!\$\{)([^\s"'`]{12,})/gi },
|
|
5401
|
-
// HTTP Authorization headers. env-secret requires an ALL-CAPS name, and
|
|
5402
|
-
// `Authorization` is mixed case, so both Bearer and Basic walked straight
|
|
5403
|
-
// through — including short opaque tokens the contextual pass also misses
|
|
5404
|
-
// (it needs 32+ chars AND mixed-case-plus-digit).
|
|
5405
|
-
// Same two guards as npmrc-auth: stop at a quote, and skip the obvious
|
|
5406
|
-
// placeholders that fill documentation (<YOUR_TOKEN>, ${TOKEN}, xxx…).
|
|
5407
|
-
{ label: "auth-header", secret: 1, pattern: /\bauthorization[ \t]*:[ \t]*(?:bearer|basic|token)[ \t]+(?![<$])([^\s"'`<]{8,})/gi },
|
|
5408
|
-
// Vendor tokens with unambiguous prefixes. Each is a fixed literal, so a
|
|
5409
|
-
// false positive is essentially impossible and the cost of listing is low.
|
|
5410
|
-
{ label: "gitlab-pat", pattern: /\bglpat-[A-Za-z0-9_-]{20,}\b/g },
|
|
5411
|
-
{ label: "slack-app-token", pattern: /\bxapp-\d-[A-Z0-9]{8,}-\d{10,}-[a-f0-9]{32,}\b/g },
|
|
5412
|
-
{ label: "sendgrid-key", pattern: /\bSG\.[A-Za-z0-9_-]{20,24}\.[A-Za-z0-9_-]{40,50}\b/g },
|
|
5413
|
-
// (?<![-\w]) so `cache-key-<md5>` is not read as a mailgun key.
|
|
5414
|
-
{ label: "mailgun-key", pattern: /(?<![-\w])key-[a-f0-9]{32}\b/g },
|
|
5415
|
-
{ label: "digitalocean-pat", pattern: /\bdop_v1_[a-f0-9]{64}\b/g },
|
|
5416
|
-
{ label: "huggingface-token", pattern: /\bhf_[A-Za-z0-9]{30,}\b/g },
|
|
5417
|
-
{ label: "shopify-token", pattern: /\bshp(?:at|ca|pa|ss)_[a-fA-F0-9]{32}\b/g },
|
|
5418
|
-
{ label: "pypi-token", pattern: /\bpypi-AgEIcHlwaS5vcmc[A-Za-z0-9_-]{50,}\b/g },
|
|
5419
|
-
// No twilio-sid rule: AC…/SK… are Twilio SIDs, which are public account and
|
|
5420
|
-
// key identifiers, not the auth token. Redacting them hid nothing and
|
|
5421
|
-
// collided with 32-hex etags and MD5 sums.
|
|
5422
|
-
// No discord-bot-token rule either: the shape (word.word.word) matched
|
|
5423
|
-
// ordinary dotted symbols such as a.b.ApplicationContextProviderImpl.
|
|
5424
|
-
{ label: "telegram-bot-token", pattern: /\b\d{8,10}:AA[A-Za-z0-9_-]{32,}\b/g },
|
|
5425
|
-
{ label: "doppler-token", pattern: /\bdp\.(?:pt|st|sa|ct)\.[A-Za-z0-9_-]{20,}\b/g },
|
|
5426
|
-
// Azure Storage SAS signature — the `sig=` parameter is the actual secret.
|
|
5427
|
-
{ label: "azure-sas", secret: 1, pattern: /[?&]sig=([A-Za-z0-9%/+=]{20,})/g }
|
|
5428
|
-
];
|
|
5429
|
-
SECRET_CONTEXT_RE = /\b(?:secret|password|passwd|credential|access[ _-]?key|private[ _-]?key|api[ _-]?key|auth(?:oriz\w*)?|bearer|token)\b/gi;
|
|
5430
|
-
HIGH_ENTROPY_TOKEN_RE = /[A-Za-z0-9/+_-]{32,}/g;
|
|
5431
|
-
cache = null;
|
|
5432
|
-
CANARY = "the quick brown fox jumps over the lazy dog 0123456789 commit 8ba1f109551bd432803012645ac136ddd475d0fb src/index.ts:42";
|
|
5433
|
-
MAX_PACK_RULES = 200;
|
|
5434
|
-
MAX_PATTERN_LEN = 512;
|
|
5435
|
-
serverPack = null;
|
|
5436
|
-
globalRegexCache = /* @__PURE__ */ new Map();
|
|
5437
|
-
privacyCache = null;
|
|
5438
|
-
}
|
|
5439
|
-
});
|
|
5440
|
-
|
|
5441
|
-
// ../engine/dist/parsers/chunker.js
|
|
5442
|
-
function stripInjectedBanners(text) {
|
|
5443
|
-
let result = text;
|
|
5444
|
-
for (const re of INJECTED_BANNERS)
|
|
5445
|
-
result = result.replace(re, " ");
|
|
5446
|
-
return result;
|
|
5447
|
-
}
|
|
5448
|
-
function cleanText(text) {
|
|
5449
|
-
let result = text.replace(/<system-reminder>.*?<\/system-reminder>/gs, "");
|
|
5450
|
-
result = stripInjectedBanners(result);
|
|
5451
|
-
result = result.replace(/\x1b\[[0-9;]*m/g, "");
|
|
5452
|
-
result = result.replace(/\s+/g, " ");
|
|
5453
|
-
return result.trim();
|
|
5454
|
-
}
|
|
5455
|
-
function windowText(text, maxChars, maxWindows = 12) {
|
|
5456
|
-
const clean = cleanText(text);
|
|
5457
|
-
if (clean.length <= maxChars)
|
|
5458
|
-
return clean ? [clean] : [];
|
|
5459
|
-
const out = [];
|
|
5460
|
-
const overlap = 80;
|
|
5461
|
-
const step = Math.max(1, maxChars - overlap);
|
|
5462
|
-
for (let i = 0; i < clean.length && out.length < maxWindows; i += step) {
|
|
5463
|
-
out.push(clean.slice(i, i + maxChars));
|
|
5464
|
-
}
|
|
5465
|
-
return out;
|
|
5466
|
-
}
|
|
5467
|
-
function chunkSession(entry, content, maxChunks = 80, maxTokensPerChunk = 500) {
|
|
5468
|
-
const chunks = [];
|
|
5469
|
-
const maxChars = maxTokensPerChunk * 4;
|
|
5470
|
-
const baseData = {
|
|
5471
|
-
sessionId: entry.sessionId,
|
|
5472
|
-
projectPath: entry.projectPath,
|
|
5473
|
-
created: entry.created,
|
|
5474
|
-
modified: entry.modified,
|
|
5475
|
-
mtime: entry.fileMtime,
|
|
5476
|
-
firstPrompt: entry.firstPrompt || content.firstPrompt,
|
|
5477
|
-
sourceFile: content.sessionPath
|
|
5478
|
-
};
|
|
5479
|
-
for (let i = 0; i < Math.min(2, content.summaries.length); i++) {
|
|
5480
|
-
const summary = content.summaries[i];
|
|
5481
|
-
if (!summary?.trim())
|
|
5482
|
-
continue;
|
|
5483
|
-
const chunkText = cleanText(summary.slice(0, maxChars));
|
|
5484
|
-
if (chunkText.length > 8e3) {
|
|
5485
|
-
chunks.push({
|
|
5486
|
-
chunkId: `${entry.sessionId}_summary_${i}`,
|
|
5487
|
-
chunkType: "summary",
|
|
5488
|
-
text: chunkText.slice(0, 8e3),
|
|
5489
|
-
sourceLine: 0,
|
|
5490
|
-
...baseData
|
|
5491
|
-
});
|
|
5492
|
-
} else {
|
|
5493
|
-
chunks.push({
|
|
5494
|
-
chunkId: `${entry.sessionId}_summary_${i}`,
|
|
5495
|
-
chunkType: "summary",
|
|
5496
|
-
text: chunkText,
|
|
5497
|
-
sourceLine: 0,
|
|
5498
|
-
...baseData
|
|
5499
|
-
});
|
|
5500
|
-
}
|
|
5501
|
-
}
|
|
5502
|
-
if (chunks.length >= maxChunks) {
|
|
5503
|
-
return chunks.slice(0, maxChunks);
|
|
5504
|
-
}
|
|
5505
|
-
const firstPrompt = entry.firstPrompt || content.firstPrompt;
|
|
5506
|
-
if (firstPrompt?.trim()) {
|
|
5507
|
-
const firstLine = content.userMessages[0]?.lineNumber || 0;
|
|
5508
|
-
chunks.push({
|
|
5509
|
-
chunkId: `${entry.sessionId}_first_prompt`,
|
|
5510
|
-
chunkType: "first_prompt",
|
|
5511
|
-
text: cleanText(firstPrompt.slice(0, maxChars)),
|
|
5512
|
-
sourceLine: firstLine,
|
|
5513
|
-
...baseData
|
|
5514
|
-
});
|
|
5515
|
-
}
|
|
5516
|
-
if (chunks.length >= maxChunks) {
|
|
5517
|
-
return chunks.slice(0, maxChunks);
|
|
5518
|
-
}
|
|
5519
|
-
const userStart = firstPrompt ? 1 : 0;
|
|
5520
|
-
for (let i = userStart; i < content.userMessages.length; i++) {
|
|
5521
|
-
if (chunks.length >= maxChunks)
|
|
5522
|
-
break;
|
|
5523
|
-
const msg = content.userMessages[i];
|
|
5524
|
-
if (!msg.text.trim())
|
|
5525
|
-
continue;
|
|
5526
|
-
const windows = windowText(msg.text, maxChars);
|
|
5527
|
-
for (let w = 0; w < windows.length; w++) {
|
|
5528
|
-
if (chunks.length >= maxChunks)
|
|
5529
|
-
break;
|
|
5530
|
-
chunks.push({
|
|
5531
|
-
chunkId: `${entry.sessionId}_user_${i}_${w}`,
|
|
5532
|
-
chunkType: "user_context",
|
|
5533
|
-
text: windows[w],
|
|
5534
|
-
sourceLine: msg.lineNumber,
|
|
5535
|
-
...baseData
|
|
5536
|
-
});
|
|
5537
|
-
}
|
|
5538
|
-
}
|
|
5539
|
-
if (chunks.length >= maxChunks) {
|
|
5540
|
-
return chunks.slice(0, maxChunks);
|
|
5541
|
-
}
|
|
5542
|
-
for (let i = 0; i < content.assistantMessages.length; i++) {
|
|
5543
|
-
if (chunks.length >= maxChunks)
|
|
5544
|
-
break;
|
|
5545
|
-
const msg = content.assistantMessages[i];
|
|
5546
|
-
if (!msg.text.trim() || msg.text.length <= 50)
|
|
5547
|
-
continue;
|
|
5548
|
-
const windows = windowText(msg.text, maxChars);
|
|
5549
|
-
for (let w = 0; w < windows.length; w++) {
|
|
5550
|
-
if (chunks.length >= maxChunks)
|
|
5551
|
-
break;
|
|
5552
|
-
chunks.push({
|
|
5553
|
-
chunkId: `${entry.sessionId}_assistant_${i}_${w}`,
|
|
5554
|
-
chunkType: "assistant",
|
|
5555
|
-
text: windows[w],
|
|
5556
|
-
sourceLine: msg.lineNumber,
|
|
5557
|
-
...baseData
|
|
5558
|
-
});
|
|
5559
|
-
}
|
|
5560
|
-
}
|
|
5561
|
-
if (chunks.length >= maxChunks) {
|
|
5562
|
-
return chunks.slice(0, maxChunks);
|
|
5563
|
-
}
|
|
5564
|
-
if (content.toolResults.length > 0) {
|
|
5565
|
-
const webSearches = content.toolResults.filter((r) => r.contentType === "web_search");
|
|
5566
|
-
const otherResults = content.toolResults.filter((r) => r.contentType !== "web_search");
|
|
5567
|
-
const resultsToProcess = [...webSearches.slice(0, 5), ...otherResults.slice(0, 10)];
|
|
5568
|
-
const stripBodies = isToolOutputRedactionEnabled();
|
|
5569
|
-
for (let i = 0; i < resultsToProcess.length; i++) {
|
|
5570
|
-
if (chunks.length >= maxChunks)
|
|
5571
|
-
break;
|
|
5572
|
-
const result = resultsToProcess[i];
|
|
5573
|
-
if (result.text.trim()) {
|
|
5574
|
-
const body = stripBodies && result.contentType !== "web_search" ? redactToolResultBody(result.contentType, result.text) : result.text.slice(0, maxChars);
|
|
5575
|
-
chunks.push({
|
|
5576
|
-
chunkId: `${entry.sessionId}_${result.contentType}_${i}`,
|
|
5577
|
-
chunkType: result.contentType,
|
|
5578
|
-
text: cleanText(body),
|
|
5579
|
-
sourceLine: result.lineNumber,
|
|
5580
|
-
...baseData
|
|
5581
|
-
});
|
|
5582
|
-
}
|
|
5583
|
-
}
|
|
5584
|
-
}
|
|
5585
|
-
return chunks.slice(0, maxChunks);
|
|
5586
|
-
}
|
|
5587
|
-
var INJECTED_BANNERS;
|
|
5588
|
-
var init_chunker = __esm({
|
|
5589
|
-
"../engine/dist/parsers/chunker.js"() {
|
|
5590
|
-
"use strict";
|
|
5591
|
-
init_secret_redactor();
|
|
5592
|
-
INJECTED_BANNERS = [
|
|
5593
|
-
/MCP issues detected\. ?Run \/mcp list for status\.?/g,
|
|
5594
|
-
/Context low[^\n]*Run \/compact[^\n]*/g,
|
|
5595
|
-
/API Error:[^\n]{0,120}/g
|
|
5596
|
-
];
|
|
5597
|
-
}
|
|
5598
|
-
});
|
|
5599
|
-
|
|
5600
5053
|
// ../engine/dist/core/home-approval.js
|
|
5601
5054
|
import { existsSync as existsSync3, readdirSync as readdirSync2, realpathSync } from "fs";
|
|
5602
5055
|
import { homedir as homedir2 } from "os";
|
|
@@ -6141,7 +5594,7 @@ var init_model_pricing = __esm({
|
|
|
6141
5594
|
});
|
|
6142
5595
|
|
|
6143
5596
|
// ../engine/dist/parsers/session.js
|
|
6144
|
-
import { createReadStream, existsSync as existsSync5, readdirSync as readdirSync5, readFileSync as readFileSync2, statSync as
|
|
5597
|
+
import { createReadStream, existsSync as existsSync5, readdirSync as readdirSync5, readFileSync as readFileSync2, statSync as statSync3, openSync, readSync, closeSync } from "fs";
|
|
6145
5598
|
import { createInterface } from "readline";
|
|
6146
5599
|
import { join as join5, basename as basename2 } from "path";
|
|
6147
5600
|
function removeCodeBlocks(text) {
|
|
@@ -6188,44 +5641,6 @@ function isValuableToolResult(content, toolName = "") {
|
|
|
6188
5641
|
}
|
|
6189
5642
|
return true;
|
|
6190
5643
|
}
|
|
6191
|
-
function parseSessionsIndex(indexPath) {
|
|
6192
|
-
if (!existsSync5(indexPath)) {
|
|
6193
|
-
return [];
|
|
6194
|
-
}
|
|
6195
|
-
const data = JSON.parse(readFileSync2(indexPath, "utf-8"));
|
|
6196
|
-
const entries = data.entries || [];
|
|
6197
|
-
return entries.map((e) => ({
|
|
6198
|
-
sessionId: e.sessionId || "",
|
|
6199
|
-
fullPath: e.fullPath || "",
|
|
6200
|
-
fileMtime: e.fileMtime || 0,
|
|
6201
|
-
firstPrompt: e.firstPrompt || "",
|
|
6202
|
-
messageCount: e.messageCount || 0,
|
|
6203
|
-
created: e.created || "",
|
|
6204
|
-
modified: e.modified || "",
|
|
6205
|
-
gitBranch: e.gitBranch || "",
|
|
6206
|
-
projectPath: e.projectPath || "",
|
|
6207
|
-
isSidechain: e.isSidechain || false
|
|
6208
|
-
}));
|
|
6209
|
-
}
|
|
6210
|
-
function* iterAllSessionIndices(claudeDir) {
|
|
6211
|
-
const dir = claudeDir || claudeBackend.projectsDir();
|
|
6212
|
-
if (!existsSync5(dir)) {
|
|
6213
|
-
return;
|
|
6214
|
-
}
|
|
6215
|
-
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
6216
|
-
for (const entry of entries) {
|
|
6217
|
-
if (!entry.isDirectory())
|
|
6218
|
-
continue;
|
|
6219
|
-
const projectDir = join5(dir, entry.name);
|
|
6220
|
-
const indexPath = join5(projectDir, "sessions-index.json");
|
|
6221
|
-
if (existsSync5(indexPath)) {
|
|
6222
|
-
const sessions = parseSessionsIndex(indexPath);
|
|
6223
|
-
if (sessions.length > 0) {
|
|
6224
|
-
yield [projectDir, sessions];
|
|
6225
|
-
}
|
|
6226
|
-
}
|
|
6227
|
-
}
|
|
6228
|
-
}
|
|
6229
5644
|
function hasSubagentsDir(sessionPath) {
|
|
6230
5645
|
const dir = sessionPath.slice(0, -6);
|
|
6231
5646
|
return existsSync5(join5(dir, "subagents"));
|
|
@@ -6236,40 +5651,6 @@ function getSubagentPaths(sessionPath) {
|
|
|
6236
5651
|
return [];
|
|
6237
5652
|
return readdirSync5(subagentsDir).filter((f) => f.endsWith(".jsonl")).sort().map((f) => join5(subagentsDir, f));
|
|
6238
5653
|
}
|
|
6239
|
-
function getSubagentProjectPath(sessionPath, folderDerivedPath) {
|
|
6240
|
-
const subagentPaths = getSubagentPaths(sessionPath);
|
|
6241
|
-
if (subagentPaths.length === 0)
|
|
6242
|
-
return folderDerivedPath;
|
|
6243
|
-
const cwdCounts = /* @__PURE__ */ new Map();
|
|
6244
|
-
for (const filePath of subagentPaths) {
|
|
6245
|
-
try {
|
|
6246
|
-
const lines = readFileSync2(filePath, "utf-8").split("\n");
|
|
6247
|
-
for (const line of lines) {
|
|
6248
|
-
if (!line.trim())
|
|
6249
|
-
continue;
|
|
6250
|
-
try {
|
|
6251
|
-
const entry = JSON.parse(line);
|
|
6252
|
-
if (entry.type === "user" && typeof entry.cwd === "string" && entry.cwd) {
|
|
6253
|
-
cwdCounts.set(entry.cwd, (cwdCounts.get(entry.cwd) ?? 0) + 1);
|
|
6254
|
-
}
|
|
6255
|
-
} catch {
|
|
6256
|
-
}
|
|
6257
|
-
}
|
|
6258
|
-
} catch {
|
|
6259
|
-
}
|
|
6260
|
-
}
|
|
6261
|
-
if (cwdCounts.size === 0)
|
|
6262
|
-
return folderDerivedPath;
|
|
6263
|
-
let best = folderDerivedPath;
|
|
6264
|
-
let bestCount = 0;
|
|
6265
|
-
for (const [cwd, count] of cwdCounts) {
|
|
6266
|
-
if (count > bestCount) {
|
|
6267
|
-
best = cwd;
|
|
6268
|
-
bestCount = count;
|
|
6269
|
-
}
|
|
6270
|
-
}
|
|
6271
|
-
return best;
|
|
6272
|
-
}
|
|
6273
5654
|
function readCwdFromJsonl(sessionPath) {
|
|
6274
5655
|
try {
|
|
6275
5656
|
if (!existsSync5(sessionPath))
|
|
@@ -6298,22 +5679,6 @@ function readCwdFromJsonl(sessionPath) {
|
|
|
6298
5679
|
}
|
|
6299
5680
|
return "";
|
|
6300
5681
|
}
|
|
6301
|
-
function getSessionMtime(sessionPath) {
|
|
6302
|
-
const subagentPaths = getSubagentPaths(sessionPath);
|
|
6303
|
-
if (subagentPaths.length === 0) {
|
|
6304
|
-
return statSync4(sessionPath).mtimeMs;
|
|
6305
|
-
}
|
|
6306
|
-
let maxMtime = statSync4(sessionPath).mtimeMs;
|
|
6307
|
-
for (const p of subagentPaths) {
|
|
6308
|
-
try {
|
|
6309
|
-
const m = statSync4(p).mtimeMs;
|
|
6310
|
-
if (m > maxMtime)
|
|
6311
|
-
maxMtime = m;
|
|
6312
|
-
} catch {
|
|
6313
|
-
}
|
|
6314
|
-
}
|
|
6315
|
-
return maxMtime;
|
|
6316
|
-
}
|
|
6317
5682
|
async function parseSessionFile(sessionPath, maxMessages = 50) {
|
|
6318
5683
|
const sessionId = basename2(sessionPath, ".jsonl");
|
|
6319
5684
|
const content = {
|
|
@@ -6549,78 +5914,12 @@ ${resultText}` : resultText;
|
|
|
6549
5914
|
cacheReadTokens: content.metadata.cacheReadTokens,
|
|
6550
5915
|
cacheCreationTokens: content.metadata.cacheCreationTokens
|
|
6551
5916
|
});
|
|
6552
|
-
return content;
|
|
6553
|
-
}
|
|
6554
|
-
function* getAllSessions(claudeDir) {
|
|
6555
|
-
const seenSessionIds = /* @__PURE__ */ new Set();
|
|
6556
|
-
const projectDirs = claudeDir ? [claudeDir] : claudeProjectDirs();
|
|
6557
|
-
for (const dir of projectDirs) {
|
|
6558
|
-
for (const [, entries] of iterAllSessionIndices(dir)) {
|
|
6559
|
-
for (const entry of entries) {
|
|
6560
|
-
if (!seenSessionIds.has(entry.sessionId) && existsSync5(entry.fullPath)) {
|
|
6561
|
-
seenSessionIds.add(entry.sessionId);
|
|
6562
|
-
yield [entry, entry.fullPath];
|
|
6563
|
-
}
|
|
6564
|
-
}
|
|
6565
|
-
}
|
|
6566
|
-
if (!existsSync5(dir))
|
|
6567
|
-
continue;
|
|
6568
|
-
const dirEntries = readdirSync5(dir, { withFileTypes: true });
|
|
6569
|
-
for (const projDirEntry of dirEntries) {
|
|
6570
|
-
if (!projDirEntry.isDirectory())
|
|
6571
|
-
continue;
|
|
6572
|
-
const projectPath = join5(dir, projDirEntry.name);
|
|
6573
|
-
const files = readdirSync5(projectPath);
|
|
6574
|
-
for (const file of files) {
|
|
6575
|
-
if (!file.endsWith(".jsonl"))
|
|
6576
|
-
continue;
|
|
6577
|
-
if (file.startsWith("agent-"))
|
|
6578
|
-
continue;
|
|
6579
|
-
if (file === "sessions-index.json")
|
|
6580
|
-
continue;
|
|
6581
|
-
const sessionId = basename2(file, ".jsonl");
|
|
6582
|
-
if (seenSessionIds.has(sessionId))
|
|
6583
|
-
continue;
|
|
6584
|
-
const fullPath = join5(projectPath, file);
|
|
6585
|
-
try {
|
|
6586
|
-
const stat3 = statSync4(fullPath);
|
|
6587
|
-
const derivedProjectPath = resolveProjectDirName(projDirEntry.name);
|
|
6588
|
-
const effectiveMtime = getSessionMtime(fullPath);
|
|
6589
|
-
let projectPath2 = "";
|
|
6590
|
-
if (hasSubagentsDir(fullPath)) {
|
|
6591
|
-
projectPath2 = getSubagentProjectPath(fullPath, derivedProjectPath);
|
|
6592
|
-
} else {
|
|
6593
|
-
const fromJsonl = readCwdFromJsonl(fullPath);
|
|
6594
|
-
if (fromJsonl)
|
|
6595
|
-
projectPath2 = fromJsonl;
|
|
6596
|
-
}
|
|
6597
|
-
if (!projectPath2)
|
|
6598
|
-
projectPath2 = derivedProjectPath;
|
|
6599
|
-
const entry = {
|
|
6600
|
-
sessionId,
|
|
6601
|
-
fullPath,
|
|
6602
|
-
fileMtime: effectiveMtime,
|
|
6603
|
-
firstPrompt: "",
|
|
6604
|
-
messageCount: 0,
|
|
6605
|
-
created: stat3.birthtime.toISOString(),
|
|
6606
|
-
modified: new Date(effectiveMtime).toISOString(),
|
|
6607
|
-
gitBranch: "",
|
|
6608
|
-
projectPath: projectPath2,
|
|
6609
|
-
isSidechain: false
|
|
6610
|
-
};
|
|
6611
|
-
seenSessionIds.add(sessionId);
|
|
6612
|
-
yield [entry, fullPath];
|
|
6613
|
-
} catch {
|
|
6614
|
-
continue;
|
|
6615
|
-
}
|
|
6616
|
-
}
|
|
6617
|
-
}
|
|
6618
|
-
}
|
|
5917
|
+
return content;
|
|
6619
5918
|
}
|
|
6620
5919
|
var init_session = __esm({
|
|
6621
5920
|
"../engine/dist/parsers/session.js"() {
|
|
6622
5921
|
"use strict";
|
|
6623
|
-
|
|
5922
|
+
init_banners();
|
|
6624
5923
|
init_claude2();
|
|
6625
5924
|
init_tool_paths();
|
|
6626
5925
|
init_project_dir_name();
|
|
@@ -7286,7 +6585,7 @@ var init_raw = __esm({
|
|
|
7286
6585
|
// ../engine/dist/transcript/shadow.js
|
|
7287
6586
|
import { gzipSync as gzipSync2, gunzipSync as gunzipSync2 } from "zlib";
|
|
7288
6587
|
import { createHash as createHash2 } from "crypto";
|
|
7289
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync as renameSync2, statSync as
|
|
6588
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync as renameSync2, statSync as statSync4, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
|
|
7290
6589
|
import { dirname as dirname7, join as join8 } from "path";
|
|
7291
6590
|
function shadowRoot() {
|
|
7292
6591
|
return join8(getDataDir(), "shadow");
|
|
@@ -7494,7 +6793,7 @@ function liveFingerprint(backend, rawId) {
|
|
|
7494
6793
|
const loc = backend.findSession(rawId);
|
|
7495
6794
|
if (!loc?.path)
|
|
7496
6795
|
return "";
|
|
7497
|
-
const st =
|
|
6796
|
+
const st = statSync4(loc.path);
|
|
7498
6797
|
return `${st.mtimeMs}:${st.size}`;
|
|
7499
6798
|
} catch {
|
|
7500
6799
|
return "";
|
|
@@ -7510,9 +6809,32 @@ function seedShadow(sessionId, container) {
|
|
|
7510
6809
|
}
|
|
7511
6810
|
return fullest;
|
|
7512
6811
|
}
|
|
6812
|
+
function shadowUncompressedBytes(tool, rawId) {
|
|
6813
|
+
const path = shadowFileFor(tool, rawId);
|
|
6814
|
+
let fd = null;
|
|
6815
|
+
try {
|
|
6816
|
+
const size = statSync4(path).size;
|
|
6817
|
+
if (size < 18)
|
|
6818
|
+
return 0;
|
|
6819
|
+
fd = openSync2(path, "r");
|
|
6820
|
+
const tail = Buffer.alloc(4);
|
|
6821
|
+
if (readSync2(fd, tail, 0, 4, size - 4) !== 4)
|
|
6822
|
+
return 0;
|
|
6823
|
+
return tail.readUInt32LE(0);
|
|
6824
|
+
} catch {
|
|
6825
|
+
return 0;
|
|
6826
|
+
} finally {
|
|
6827
|
+
if (fd !== null) {
|
|
6828
|
+
try {
|
|
6829
|
+
closeSync2(fd);
|
|
6830
|
+
} catch {
|
|
6831
|
+
}
|
|
6832
|
+
}
|
|
6833
|
+
}
|
|
6834
|
+
}
|
|
7513
6835
|
function shadowSizeOnDisk(tool, rawId) {
|
|
7514
6836
|
try {
|
|
7515
|
-
return
|
|
6837
|
+
return statSync4(shadowFileFor(tool, rawId)).size;
|
|
7516
6838
|
} catch {
|
|
7517
6839
|
return 0;
|
|
7518
6840
|
}
|
|
@@ -7530,7 +6852,7 @@ var init_shadow = __esm({
|
|
|
7530
6852
|
});
|
|
7531
6853
|
|
|
7532
6854
|
// ../engine/dist/core/live-session-scan.js
|
|
7533
|
-
import { existsSync as existsSync7, readdirSync as readdirSync6, readFileSync as readFileSync4, statSync as
|
|
6855
|
+
import { existsSync as existsSync7, readdirSync as readdirSync6, readFileSync as readFileSync4, statSync as statSync5, openSync as openSync3, readSync as readSync3, closeSync as closeSync3 } from "fs";
|
|
7534
6856
|
import { join as join9, basename as basename5 } from "path";
|
|
7535
6857
|
function detectTool(sessionId) {
|
|
7536
6858
|
return getBackendForId(sessionId)?.id ?? "claude";
|
|
@@ -7557,6 +6879,15 @@ function sessionFileIndex() {
|
|
|
7557
6879
|
return indexCache;
|
|
7558
6880
|
const byId = /* @__PURE__ */ new Map();
|
|
7559
6881
|
const seenRoots = /* @__PURE__ */ new Set();
|
|
6882
|
+
const projectPaths = /* @__PURE__ */ new Map();
|
|
6883
|
+
const projectPathOf = (dirName) => {
|
|
6884
|
+
let p = projectPaths.get(dirName);
|
|
6885
|
+
if (p === void 0) {
|
|
6886
|
+
p = resolveProjectDirName(dirName);
|
|
6887
|
+
projectPaths.set(dirName, p);
|
|
6888
|
+
}
|
|
6889
|
+
return p;
|
|
6890
|
+
};
|
|
7560
6891
|
for (const root of claudeProjectDirs()) {
|
|
7561
6892
|
if (seenRoots.has(root) || !existsSync7(root))
|
|
7562
6893
|
continue;
|
|
@@ -7584,7 +6915,7 @@ function sessionFileIndex() {
|
|
|
7584
6915
|
const located = {
|
|
7585
6916
|
path: join9(dir, name),
|
|
7586
6917
|
projectDir: entry.name,
|
|
7587
|
-
projectPath:
|
|
6918
|
+
projectPath: projectPathOf(entry.name)
|
|
7588
6919
|
};
|
|
7589
6920
|
const prior = byId.get(id);
|
|
7590
6921
|
if (prior)
|
|
@@ -7648,7 +6979,7 @@ function readSessionGroupText(group) {
|
|
|
7648
6979
|
const parts = [];
|
|
7649
6980
|
for (const p of group.paths) {
|
|
7650
6981
|
try {
|
|
7651
|
-
const st =
|
|
6982
|
+
const st = statSync5(p);
|
|
7652
6983
|
parts.push(`${p}:${st.mtimeMs}:${st.size}`);
|
|
7653
6984
|
} catch {
|
|
7654
6985
|
parts.push(`${p}:missing`);
|
|
@@ -7668,7 +6999,7 @@ function readSessionGroupText(group) {
|
|
|
7668
6999
|
continue;
|
|
7669
7000
|
}
|
|
7670
7001
|
try {
|
|
7671
|
-
mtime = Math.max(mtime,
|
|
7002
|
+
mtime = Math.max(mtime, statSync5(p).mtimeMs);
|
|
7672
7003
|
} catch {
|
|
7673
7004
|
}
|
|
7674
7005
|
text = text ? mergeLineText(text, raw).text : raw;
|
|
@@ -8266,7 +7597,7 @@ var init_flat_string = __esm({
|
|
|
8266
7597
|
});
|
|
8267
7598
|
|
|
8268
7599
|
// ../engine/dist/core/first-prompt.js
|
|
8269
|
-
import { createReadStream as createReadStream2, existsSync as existsSync9, openSync as
|
|
7600
|
+
import { createReadStream as createReadStream2, existsSync as existsSync9, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
|
|
8270
7601
|
import { StringDecoder } from "string_decoder";
|
|
8271
7602
|
function stripBanners2(s) {
|
|
8272
7603
|
let out = s;
|
|
@@ -8320,7 +7651,7 @@ function extractFirstUserPromptSync(filePath, options = {}) {
|
|
|
8320
7651
|
const maxBytes = options.maxBytes ?? 4 * 1024 * 1024;
|
|
8321
7652
|
let fd;
|
|
8322
7653
|
try {
|
|
8323
|
-
fd =
|
|
7654
|
+
fd = openSync4(filePath, "r");
|
|
8324
7655
|
} catch {
|
|
8325
7656
|
return "";
|
|
8326
7657
|
}
|
|
@@ -8332,7 +7663,7 @@ function extractFirstUserPromptSync(filePath, options = {}) {
|
|
|
8332
7663
|
let pos = 0;
|
|
8333
7664
|
let lineNo = 0;
|
|
8334
7665
|
for (; ; ) {
|
|
8335
|
-
const n =
|
|
7666
|
+
const n = readSync4(fd, buf, 0, CHUNK, pos);
|
|
8336
7667
|
if (n <= 0)
|
|
8337
7668
|
break;
|
|
8338
7669
|
pos += n;
|
|
@@ -8358,7 +7689,7 @@ function extractFirstUserPromptSync(filePath, options = {}) {
|
|
|
8358
7689
|
return "";
|
|
8359
7690
|
} finally {
|
|
8360
7691
|
try {
|
|
8361
|
-
|
|
7692
|
+
closeSync4(fd);
|
|
8362
7693
|
} catch {
|
|
8363
7694
|
}
|
|
8364
7695
|
}
|
|
@@ -8377,24 +7708,24 @@ var init_first_prompt = __esm({
|
|
|
8377
7708
|
});
|
|
8378
7709
|
|
|
8379
7710
|
// ../engine/dist/core/backends/tail-read.js
|
|
8380
|
-
import { openSync as
|
|
8381
|
-
function readTailFromOffset(path, offset) {
|
|
7711
|
+
import { openSync as openSync5, readSync as readSync5, closeSync as closeSync5, statSync as statSync6 } from "node:fs";
|
|
7712
|
+
function readTailFromOffset(path, offset, maxBytes = TAIL_READ_MAX_BYTES) {
|
|
8382
7713
|
let size = 0;
|
|
8383
7714
|
try {
|
|
8384
|
-
size =
|
|
7715
|
+
size = statSync6(path).size;
|
|
8385
7716
|
} catch {
|
|
8386
7717
|
return { text: "", newOffset: offset };
|
|
8387
7718
|
}
|
|
8388
7719
|
if (size <= offset)
|
|
8389
7720
|
return { text: "", newOffset: offset };
|
|
8390
|
-
const len = Math.min(size - offset,
|
|
7721
|
+
const len = Math.min(size - offset, Math.max(1, maxBytes));
|
|
8391
7722
|
const buf = Buffer.allocUnsafe(len);
|
|
8392
7723
|
let fd = null;
|
|
8393
7724
|
try {
|
|
8394
|
-
fd =
|
|
7725
|
+
fd = openSync5(path, "r");
|
|
8395
7726
|
let got = 0;
|
|
8396
7727
|
while (got < len) {
|
|
8397
|
-
const n =
|
|
7728
|
+
const n = readSync5(fd, buf, got, len - got, offset + got);
|
|
8398
7729
|
if (n <= 0)
|
|
8399
7730
|
break;
|
|
8400
7731
|
got += n;
|
|
@@ -8403,25 +7734,47 @@ function readTailFromOffset(path, offset) {
|
|
|
8403
7734
|
return { text: "", newOffset: offset };
|
|
8404
7735
|
const slice = got < len ? buf.subarray(0, got) : buf;
|
|
8405
7736
|
const lastNl = slice.lastIndexOf(10);
|
|
8406
|
-
if (lastNl
|
|
8407
|
-
|
|
7737
|
+
if (lastNl >= 0) {
|
|
7738
|
+
const text = slice.subarray(0, lastNl + 1).toString("utf-8");
|
|
7739
|
+
return { text, newOffset: offset + lastNl + 1 };
|
|
8408
7740
|
}
|
|
8409
|
-
|
|
8410
|
-
|
|
7741
|
+
if (offset + got >= size)
|
|
7742
|
+
return { text: "", newOffset: offset };
|
|
7743
|
+
const end = findNewline(fd, offset + got, size);
|
|
7744
|
+
if (end < 0)
|
|
7745
|
+
return { text: "", newOffset: offset };
|
|
7746
|
+
return { text: "", newOffset: end + 1, skippedBytes: end + 1 - offset };
|
|
8411
7747
|
} catch {
|
|
8412
7748
|
return { text: "", newOffset: offset };
|
|
8413
7749
|
} finally {
|
|
8414
7750
|
if (fd !== null) {
|
|
8415
7751
|
try {
|
|
8416
|
-
|
|
7752
|
+
closeSync5(fd);
|
|
8417
7753
|
} catch {
|
|
8418
7754
|
}
|
|
8419
7755
|
}
|
|
8420
7756
|
}
|
|
8421
7757
|
}
|
|
7758
|
+
function findNewline(fd, from, size) {
|
|
7759
|
+
const block = Buffer.allocUnsafe(SKIP_SCAN_BYTES);
|
|
7760
|
+
let pos = from;
|
|
7761
|
+
while (pos < size) {
|
|
7762
|
+
const n = readSync5(fd, block, 0, Math.min(SKIP_SCAN_BYTES, size - pos), pos);
|
|
7763
|
+
if (n <= 0)
|
|
7764
|
+
return -1;
|
|
7765
|
+
const i = block.subarray(0, n).indexOf(10);
|
|
7766
|
+
if (i >= 0)
|
|
7767
|
+
return pos + i;
|
|
7768
|
+
pos += n;
|
|
7769
|
+
}
|
|
7770
|
+
return -1;
|
|
7771
|
+
}
|
|
7772
|
+
var TAIL_READ_MAX_BYTES, SKIP_SCAN_BYTES;
|
|
8422
7773
|
var init_tail_read = __esm({
|
|
8423
7774
|
"../engine/dist/core/backends/tail-read.js"() {
|
|
8424
7775
|
"use strict";
|
|
7776
|
+
TAIL_READ_MAX_BYTES = 16 * 1024 * 1024;
|
|
7777
|
+
SKIP_SCAN_BYTES = 1024 * 1024;
|
|
8425
7778
|
}
|
|
8426
7779
|
});
|
|
8427
7780
|
|
|
@@ -8698,7 +8051,7 @@ var init_generic_engine = __esm({
|
|
|
8698
8051
|
});
|
|
8699
8052
|
|
|
8700
8053
|
// ../engine/dist/core/backends/claude.js
|
|
8701
|
-
import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync5, statSync as
|
|
8054
|
+
import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync5, statSync as statSync7, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
|
|
8702
8055
|
import { join as join10, basename as basename8 } from "path";
|
|
8703
8056
|
function stripReminders(text) {
|
|
8704
8057
|
return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
|
|
@@ -8795,13 +8148,13 @@ var init_claude2 = __esm({
|
|
|
8795
8148
|
return null;
|
|
8796
8149
|
let head;
|
|
8797
8150
|
try {
|
|
8798
|
-
const fd =
|
|
8151
|
+
const fd = openSync6(located.path, "r");
|
|
8799
8152
|
try {
|
|
8800
8153
|
const buf = Buffer.alloc(65536);
|
|
8801
|
-
const n =
|
|
8154
|
+
const n = readSync6(fd, buf, 0, buf.length, 0);
|
|
8802
8155
|
head = buf.toString("utf-8", 0, n);
|
|
8803
8156
|
} finally {
|
|
8804
|
-
|
|
8157
|
+
closeSync6(fd);
|
|
8805
8158
|
}
|
|
8806
8159
|
} catch {
|
|
8807
8160
|
return null;
|
|
@@ -8828,7 +8181,7 @@ var init_claude2 = __esm({
|
|
|
8828
8181
|
let mtime = 0;
|
|
8829
8182
|
for (const c of copies) {
|
|
8830
8183
|
try {
|
|
8831
|
-
mtime = Math.max(mtime,
|
|
8184
|
+
mtime = Math.max(mtime, statSync7(c.path).mtimeMs);
|
|
8832
8185
|
} catch {
|
|
8833
8186
|
}
|
|
8834
8187
|
}
|
|
@@ -8904,7 +8257,7 @@ var init_claude2 = __esm({
|
|
|
8904
8257
|
const fullPath = join10(projPath, f);
|
|
8905
8258
|
let stat3;
|
|
8906
8259
|
try {
|
|
8907
|
-
stat3 =
|
|
8260
|
+
stat3 = statSync7(fullPath);
|
|
8908
8261
|
} catch {
|
|
8909
8262
|
continue;
|
|
8910
8263
|
}
|
|
@@ -9125,7 +8478,7 @@ var init_claude2 = __esm({
|
|
|
9125
8478
|
const events = this.readEvents(rawId);
|
|
9126
8479
|
let fileMtime = 0;
|
|
9127
8480
|
try {
|
|
9128
|
-
fileMtime =
|
|
8481
|
+
fileMtime = statSync7(located.path).mtimeMs;
|
|
9129
8482
|
} catch {
|
|
9130
8483
|
}
|
|
9131
8484
|
return liveScanEditsFromEvents(events, this.fileToolMap, {
|
|
@@ -9180,13 +8533,13 @@ var init_claude2 = __esm({
|
|
|
9180
8533
|
continue;
|
|
9181
8534
|
const fp = join10(projPath, f);
|
|
9182
8535
|
try {
|
|
9183
|
-
const st =
|
|
8536
|
+
const st = statSync7(fp);
|
|
9184
8537
|
let mtime = st.mtimeMs;
|
|
9185
8538
|
const subDir = join10(projPath, f.slice(0, -6), "subagents");
|
|
9186
8539
|
if (existsSync10(subDir)) {
|
|
9187
8540
|
for (const sub of readdirSync7(subDir)) {
|
|
9188
8541
|
try {
|
|
9189
|
-
const subSt =
|
|
8542
|
+
const subSt = statSync7(join10(subDir, sub));
|
|
9190
8543
|
if (subSt.mtimeMs > mtime)
|
|
9191
8544
|
mtime = subSt.mtimeMs;
|
|
9192
8545
|
} catch {
|
|
@@ -9251,7 +8604,7 @@ var init_claude2 = __esm({
|
|
|
9251
8604
|
let total = 0;
|
|
9252
8605
|
for (const located of findSessionFiles(this.toRawId(prefixedId))) {
|
|
9253
8606
|
try {
|
|
9254
|
-
total +=
|
|
8607
|
+
total += statSync7(located.path).size;
|
|
9255
8608
|
} catch {
|
|
9256
8609
|
}
|
|
9257
8610
|
}
|
|
@@ -9273,14 +8626,14 @@ var init_claude2 = __esm({
|
|
|
9273
8626
|
spansMultipleSources(prefixedId) {
|
|
9274
8627
|
return findSessionFiles(this.toRawId(prefixedId)).length > 1;
|
|
9275
8628
|
}
|
|
9276
|
-
async readFromOffset(prefixedId, offset) {
|
|
8629
|
+
async readFromOffset(prefixedId, offset, maxBytes) {
|
|
9277
8630
|
const rawId = this.toRawId(prefixedId);
|
|
9278
8631
|
const copies = findSessionFiles(rawId);
|
|
9279
8632
|
if (copies.length === 0)
|
|
9280
8633
|
return { text: "", newOffset: offset };
|
|
9281
8634
|
if (copies.length > 1)
|
|
9282
8635
|
return { text: "", newOffset: offset };
|
|
9283
|
-
return readTailFromOffset(copies[0].path, offset);
|
|
8636
|
+
return readTailFromOffset(copies[0].path, offset, maxBytes);
|
|
9284
8637
|
}
|
|
9285
8638
|
};
|
|
9286
8639
|
claudeBackend = new ClaudeBackend();
|
|
@@ -9864,7 +9217,7 @@ var init_opencode = __esm({
|
|
|
9864
9217
|
});
|
|
9865
9218
|
|
|
9866
9219
|
// ../engine/dist/core/backends/codex.js
|
|
9867
|
-
import { readdirSync as readdirSync8, readFileSync as readFileSync6, statSync as
|
|
9220
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync6, statSync as statSync8, openSync as openSync7, readSync as readSync7, closeSync as closeSync7 } from "fs";
|
|
9868
9221
|
import { basename as basename9, dirname as dirname10, join as join12 } from "path";
|
|
9869
9222
|
function parseShellWrites(cmd) {
|
|
9870
9223
|
const out = [];
|
|
@@ -10072,7 +9425,7 @@ var init_codex2 = __esm({
|
|
|
10072
9425
|
return null;
|
|
10073
9426
|
let mtime = 0;
|
|
10074
9427
|
try {
|
|
10075
|
-
mtime =
|
|
9428
|
+
mtime = statSync8(located.path).mtimeMs;
|
|
10076
9429
|
} catch {
|
|
10077
9430
|
}
|
|
10078
9431
|
return {
|
|
@@ -10092,7 +9445,7 @@ var init_codex2 = __esm({
|
|
|
10092
9445
|
for (const filePath of walkRollouts(root)) {
|
|
10093
9446
|
let stat3;
|
|
10094
9447
|
try {
|
|
10095
|
-
stat3 =
|
|
9448
|
+
stat3 = statSync8(filePath);
|
|
10096
9449
|
} catch {
|
|
10097
9450
|
continue;
|
|
10098
9451
|
}
|
|
@@ -10288,7 +9641,7 @@ var init_codex2 = __esm({
|
|
|
10288
9641
|
let mtime = 0;
|
|
10289
9642
|
try {
|
|
10290
9643
|
raw = readFileSync6(path, "utf-8");
|
|
10291
|
-
mtime =
|
|
9644
|
+
mtime = statSync8(path).mtimeMs;
|
|
10292
9645
|
} catch {
|
|
10293
9646
|
return [];
|
|
10294
9647
|
}
|
|
@@ -10388,7 +9741,7 @@ var init_codex2 = __esm({
|
|
|
10388
9741
|
const events = this.readEvents(this.toRawId(id));
|
|
10389
9742
|
let fileMtime = 0;
|
|
10390
9743
|
try {
|
|
10391
|
-
fileMtime =
|
|
9744
|
+
fileMtime = statSync8(located.path).mtimeMs;
|
|
10392
9745
|
} catch {
|
|
10393
9746
|
}
|
|
10394
9747
|
return liveScanEditsFromEvents(events, this.fileToolMap, {
|
|
@@ -10452,7 +9805,7 @@ var init_codex2 = __esm({
|
|
|
10452
9805
|
const p = join12(dir, entry.name);
|
|
10453
9806
|
let st;
|
|
10454
9807
|
try {
|
|
10455
|
-
st =
|
|
9808
|
+
st = statSync8(p);
|
|
10456
9809
|
} catch {
|
|
10457
9810
|
continue;
|
|
10458
9811
|
}
|
|
@@ -10519,7 +9872,7 @@ var init_codex2 = __esm({
|
|
|
10519
9872
|
let mtime = 0;
|
|
10520
9873
|
const push = (path, name) => {
|
|
10521
9874
|
try {
|
|
10522
|
-
const st =
|
|
9875
|
+
const st = statSync8(path);
|
|
10523
9876
|
files.push({ name, bytes: readFileSync6(path) });
|
|
10524
9877
|
if (st.mtimeMs > mtime)
|
|
10525
9878
|
mtime = st.mtimeMs;
|
|
@@ -10538,10 +9891,10 @@ var init_codex2 = __esm({
|
|
|
10538
9891
|
if (p === loc.path)
|
|
10539
9892
|
continue;
|
|
10540
9893
|
try {
|
|
10541
|
-
const fd =
|
|
9894
|
+
const fd = openSync7(p, "r");
|
|
10542
9895
|
const buf = Buffer.alloc(4096);
|
|
10543
|
-
const n =
|
|
10544
|
-
|
|
9896
|
+
const n = readSync7(fd, buf, 0, 4096, 0);
|
|
9897
|
+
closeSync7(fd);
|
|
10545
9898
|
const head = buf.toString("utf-8", 0, n);
|
|
10546
9899
|
if (head.includes(parentId) && head.includes("parent_thread_id"))
|
|
10547
9900
|
push(p, `subagents/${f}`);
|
|
@@ -10561,16 +9914,16 @@ var init_codex2 = __esm({
|
|
|
10561
9914
|
if (!loc)
|
|
10562
9915
|
return 0;
|
|
10563
9916
|
try {
|
|
10564
|
-
return
|
|
9917
|
+
return statSync8(loc.path).size;
|
|
10565
9918
|
} catch {
|
|
10566
9919
|
return 0;
|
|
10567
9920
|
}
|
|
10568
9921
|
}
|
|
10569
|
-
async readFromOffset(prefixedId, offset) {
|
|
9922
|
+
async readFromOffset(prefixedId, offset, maxBytes) {
|
|
10570
9923
|
const loc = this.findSession(prefixedId);
|
|
10571
9924
|
if (!loc)
|
|
10572
9925
|
return { text: "", newOffset: offset };
|
|
10573
|
-
return readTailFromOffset(loc.path, offset);
|
|
9926
|
+
return readTailFromOffset(loc.path, offset, maxBytes);
|
|
10574
9927
|
}
|
|
10575
9928
|
};
|
|
10576
9929
|
codexBackend = new CodexBackend();
|
|
@@ -10578,7 +9931,7 @@ var init_codex2 = __esm({
|
|
|
10578
9931
|
});
|
|
10579
9932
|
|
|
10580
9933
|
// ../engine/dist/core/backends/agy.js
|
|
10581
|
-
import { existsSync as existsSync12, readdirSync as readdirSync9, readFileSync as readFileSync7, statSync as
|
|
9934
|
+
import { existsSync as existsSync12, readdirSync as readdirSync9, readFileSync as readFileSync7, statSync as statSync9 } from "fs";
|
|
10582
9935
|
import { basename as basename10, dirname as dirname11, join as join13 } from "path";
|
|
10583
9936
|
function agyTranscriptPath(logsDir) {
|
|
10584
9937
|
const full = join13(logsDir, "transcript_full.jsonl");
|
|
@@ -10771,7 +10124,7 @@ var init_agy = __esm({
|
|
|
10771
10124
|
return null;
|
|
10772
10125
|
let mtime = 0;
|
|
10773
10126
|
try {
|
|
10774
|
-
mtime =
|
|
10127
|
+
mtime = statSync9(path).mtimeMs;
|
|
10775
10128
|
} catch {
|
|
10776
10129
|
}
|
|
10777
10130
|
const projectPath = resolveAgyProject(dirname11(path), this.homeDir());
|
|
@@ -10799,7 +10152,7 @@ var init_agy = __esm({
|
|
|
10799
10152
|
for (const rawId of sessionDirs) {
|
|
10800
10153
|
const sessionPath = join13(brainDir, rawId);
|
|
10801
10154
|
try {
|
|
10802
|
-
if (!
|
|
10155
|
+
if (!statSync9(sessionPath).isDirectory())
|
|
10803
10156
|
continue;
|
|
10804
10157
|
} catch {
|
|
10805
10158
|
continue;
|
|
@@ -10809,7 +10162,7 @@ var init_agy = __esm({
|
|
|
10809
10162
|
continue;
|
|
10810
10163
|
let stat3;
|
|
10811
10164
|
try {
|
|
10812
|
-
stat3 =
|
|
10165
|
+
stat3 = statSync9(filePath);
|
|
10813
10166
|
} catch {
|
|
10814
10167
|
continue;
|
|
10815
10168
|
}
|
|
@@ -11074,7 +10427,7 @@ var init_agy = __esm({
|
|
|
11074
10427
|
for (const rawId of sessionDirs) {
|
|
11075
10428
|
const sessionPath = join13(brainDir, rawId);
|
|
11076
10429
|
try {
|
|
11077
|
-
if (!
|
|
10430
|
+
if (!statSync9(sessionPath).isDirectory())
|
|
11078
10431
|
continue;
|
|
11079
10432
|
} catch {
|
|
11080
10433
|
continue;
|
|
@@ -11083,7 +10436,7 @@ var init_agy = __esm({
|
|
|
11083
10436
|
if (!filePath)
|
|
11084
10437
|
continue;
|
|
11085
10438
|
try {
|
|
11086
|
-
const st =
|
|
10439
|
+
const st = statSync9(filePath);
|
|
11087
10440
|
if (st.mtimeMs < opts.sinceMs)
|
|
11088
10441
|
continue;
|
|
11089
10442
|
candidates2.push({ rawId, mtime: st.mtimeMs });
|
|
@@ -11111,7 +10464,7 @@ var init_agy = __esm({
|
|
|
11111
10464
|
try {
|
|
11112
10465
|
return {
|
|
11113
10466
|
tool: "agy",
|
|
11114
|
-
mtime:
|
|
10467
|
+
mtime: statSync9(loc.path).mtimeMs,
|
|
11115
10468
|
files: [{ name: basename10(loc.path), bytes: readFileSync7(loc.path) }]
|
|
11116
10469
|
};
|
|
11117
10470
|
} catch {
|
|
@@ -11126,16 +10479,16 @@ var init_agy = __esm({
|
|
|
11126
10479
|
if (!loc)
|
|
11127
10480
|
return 0;
|
|
11128
10481
|
try {
|
|
11129
|
-
return
|
|
10482
|
+
return statSync9(loc.path).size;
|
|
11130
10483
|
} catch {
|
|
11131
10484
|
return 0;
|
|
11132
10485
|
}
|
|
11133
10486
|
}
|
|
11134
|
-
async readFromOffset(prefixedId, offset) {
|
|
10487
|
+
async readFromOffset(prefixedId, offset, maxBytes) {
|
|
11135
10488
|
const loc = this.findSession(prefixedId);
|
|
11136
10489
|
if (!loc)
|
|
11137
10490
|
return { text: "", newOffset: offset };
|
|
11138
|
-
return readTailFromOffset(loc.path, offset);
|
|
10491
|
+
return readTailFromOffset(loc.path, offset, maxBytes);
|
|
11139
10492
|
}
|
|
11140
10493
|
};
|
|
11141
10494
|
agyBackend = new AgyBackend();
|
|
@@ -11143,7 +10496,7 @@ var init_agy = __esm({
|
|
|
11143
10496
|
});
|
|
11144
10497
|
|
|
11145
10498
|
// ../engine/dist/core/backends/cursor-store.js
|
|
11146
|
-
import { existsSync as existsSync13, readdirSync as readdirSync10, readFileSync as readFileSync8, statSync as
|
|
10499
|
+
import { existsSync as existsSync13, readdirSync as readdirSync10, readFileSync as readFileSync8, statSync as statSync10 } from "fs";
|
|
11147
10500
|
import { join as join14 } from "path";
|
|
11148
10501
|
function cursorProjectSlug(absPath) {
|
|
11149
10502
|
return absPath.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -11160,7 +10513,7 @@ function listChatsIn(chatsRoot) {
|
|
|
11160
10513
|
const hashPath3 = join14(chatsRoot, workspaceHash);
|
|
11161
10514
|
let chatIds;
|
|
11162
10515
|
try {
|
|
11163
|
-
if (!
|
|
10516
|
+
if (!statSync10(hashPath3).isDirectory())
|
|
11164
10517
|
continue;
|
|
11165
10518
|
chatIds = readdirSync10(hashPath3);
|
|
11166
10519
|
} catch {
|
|
@@ -11182,7 +10535,7 @@ function listChatsIn(chatsRoot) {
|
|
|
11182
10535
|
let created = meta.createdAtMs ?? 0;
|
|
11183
10536
|
if (!mtime || !created) {
|
|
11184
10537
|
try {
|
|
11185
|
-
const st =
|
|
10538
|
+
const st = statSync10(dbPath);
|
|
11186
10539
|
mtime = mtime || st.mtimeMs;
|
|
11187
10540
|
created = created || st.birthtimeMs || st.mtimeMs;
|
|
11188
10541
|
} catch {
|
|
@@ -11225,7 +10578,7 @@ function findChat(chatsRoots, chatId) {
|
|
|
11225
10578
|
let created = meta.createdAtMs ?? 0;
|
|
11226
10579
|
if (!mtime || !created) {
|
|
11227
10580
|
try {
|
|
11228
|
-
const st =
|
|
10581
|
+
const st = statSync10(dbPath);
|
|
11229
10582
|
mtime = mtime || st.mtimeMs;
|
|
11230
10583
|
created = created || st.birthtimeMs || st.mtimeMs;
|
|
11231
10584
|
} catch {
|
|
@@ -11528,7 +10881,7 @@ var init_cursor_store = __esm({
|
|
|
11528
10881
|
});
|
|
11529
10882
|
|
|
11530
10883
|
// ../engine/dist/core/backends/cursor-ide.js
|
|
11531
|
-
import { existsSync as existsSync14, readFileSync as readFileSync9, readdirSync as readdirSync11, statSync as
|
|
10884
|
+
import { existsSync as existsSync14, readFileSync as readFileSync9, readdirSync as readdirSync11, statSync as statSync11 } from "fs";
|
|
11532
10885
|
import { dirname as dirname12, join as join15 } from "path";
|
|
11533
10886
|
function jsonOf(value) {
|
|
11534
10887
|
if (value == null)
|
|
@@ -11598,7 +10951,7 @@ function workspaceIndex(workspaceRoots) {
|
|
|
11598
10951
|
for (const id of entries) {
|
|
11599
10952
|
const dir = join15(root, id);
|
|
11600
10953
|
try {
|
|
11601
|
-
if (!
|
|
10954
|
+
if (!statSync11(dir).isDirectory())
|
|
11602
10955
|
continue;
|
|
11603
10956
|
} catch {
|
|
11604
10957
|
continue;
|
|
@@ -11944,7 +11297,7 @@ var init_cursor_ide = __esm({
|
|
|
11944
11297
|
});
|
|
11945
11298
|
|
|
11946
11299
|
// ../engine/dist/core/backends/cursor.js
|
|
11947
|
-
import { existsSync as existsSync15, readFileSync as readFileSync10, statSync as
|
|
11300
|
+
import { existsSync as existsSync15, readFileSync as readFileSync10, statSync as statSync12 } from "fs";
|
|
11948
11301
|
import { join as join16 } from "path";
|
|
11949
11302
|
var PREFIX4, CursorBackend, cursorBackend;
|
|
11950
11303
|
var init_cursor = __esm({
|
|
@@ -11997,7 +11350,7 @@ var init_cursor = __esm({
|
|
|
11997
11350
|
if (r.surface === "cli") {
|
|
11998
11351
|
let mtime2 = r.chat.updatedAtMs;
|
|
11999
11352
|
try {
|
|
12000
|
-
mtime2 =
|
|
11353
|
+
mtime2 = statSync12(r.chat.dbPath).mtimeMs || mtime2;
|
|
12001
11354
|
} catch {
|
|
12002
11355
|
}
|
|
12003
11356
|
return {
|
|
@@ -12010,7 +11363,7 @@ var init_cursor = __esm({
|
|
|
12010
11363
|
}
|
|
12011
11364
|
let mtime = r.composer.updatedAtMs;
|
|
12012
11365
|
try {
|
|
12013
|
-
mtime =
|
|
11366
|
+
mtime = statSync12(r.composer.globalDbPath).mtimeMs || mtime;
|
|
12014
11367
|
} catch {
|
|
12015
11368
|
}
|
|
12016
11369
|
return {
|
|
@@ -12256,7 +11609,7 @@ var init_cursor = __esm({
|
|
|
12256
11609
|
if (existsSync15(metaPath)) {
|
|
12257
11610
|
files.push({ name: "meta.json", bytes: readFileSync10(metaPath) });
|
|
12258
11611
|
}
|
|
12259
|
-
return { tool: "cursor", mtime:
|
|
11612
|
+
return { tool: "cursor", mtime: statSync12(r.chat.dbPath).mtimeMs, files };
|
|
12260
11613
|
}
|
|
12261
11614
|
const events = readComposerEvents(r.composer);
|
|
12262
11615
|
if (events.length === 0)
|
|
@@ -12264,7 +11617,7 @@ var init_cursor = __esm({
|
|
|
12264
11617
|
const body = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
12265
11618
|
return {
|
|
12266
11619
|
tool: "cursor",
|
|
12267
|
-
mtime:
|
|
11620
|
+
mtime: statSync12(r.composer.globalDbPath).mtimeMs,
|
|
12268
11621
|
files: [{ name: `${r.composer.composerId}.jsonl`, bytes: Buffer.from(body, "utf8") }]
|
|
12269
11622
|
};
|
|
12270
11623
|
} catch {
|
|
@@ -12408,11 +11761,391 @@ function envCredentials(env = process.env) {
|
|
|
12408
11761
|
return null;
|
|
12409
11762
|
return { serverUrl: normalise(server), token };
|
|
12410
11763
|
}
|
|
12411
|
-
var normalise;
|
|
12412
|
-
var init_credentials_env = __esm({
|
|
12413
|
-
"../engine/dist/core/credentials-env.js"() {
|
|
11764
|
+
var normalise;
|
|
11765
|
+
var init_credentials_env = __esm({
|
|
11766
|
+
"../engine/dist/core/credentials-env.js"() {
|
|
11767
|
+
"use strict";
|
|
11768
|
+
normalise = (u) => u.trim().replace(/\/+$/, "");
|
|
11769
|
+
}
|
|
11770
|
+
});
|
|
11771
|
+
|
|
11772
|
+
// ../engine/dist/core/secret-redactor.js
|
|
11773
|
+
import { statSync as statSync13 } from "fs";
|
|
11774
|
+
function secretOf(rule, m) {
|
|
11775
|
+
const raw = rule.secret != null ? m[rule.secret] : m[0];
|
|
11776
|
+
if (raw == null)
|
|
11777
|
+
return null;
|
|
11778
|
+
if (rule.where && !rule.where(raw))
|
|
11779
|
+
return null;
|
|
11780
|
+
return raw;
|
|
11781
|
+
}
|
|
11782
|
+
function looksLikeSecretToken(t) {
|
|
11783
|
+
if (t.length < 32)
|
|
11784
|
+
return false;
|
|
11785
|
+
if (/^[a-f0-9]+$/i.test(t))
|
|
11786
|
+
return true;
|
|
11787
|
+
return /[A-Z]/.test(t) && /[a-z]/.test(t) && /[0-9]/.test(t);
|
|
11788
|
+
}
|
|
11789
|
+
function findContextualSecrets(text) {
|
|
11790
|
+
if (!text)
|
|
11791
|
+
return [];
|
|
11792
|
+
const ranges = [];
|
|
11793
|
+
let m;
|
|
11794
|
+
SECRET_CONTEXT_RE.lastIndex = 0;
|
|
11795
|
+
while ((m = SECRET_CONTEXT_RE.exec(text)) !== null) {
|
|
11796
|
+
const winStart = m.index + m[0].length;
|
|
11797
|
+
const window = text.slice(winStart, winStart + 80);
|
|
11798
|
+
HIGH_ENTROPY_TOKEN_RE.lastIndex = 0;
|
|
11799
|
+
let tm;
|
|
11800
|
+
while ((tm = HIGH_ENTROPY_TOKEN_RE.exec(window)) !== null) {
|
|
11801
|
+
if (!looksLikeSecretToken(tm[0]))
|
|
11802
|
+
continue;
|
|
11803
|
+
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))) {
|
|
11804
|
+
continue;
|
|
11805
|
+
}
|
|
11806
|
+
ranges.push({ start: winStart + tm.index, end: winStart + tm.index + tm[0].length });
|
|
11807
|
+
break;
|
|
11808
|
+
}
|
|
11809
|
+
}
|
|
11810
|
+
ranges.sort((a, b) => a.start - b.start);
|
|
11811
|
+
const out = [];
|
|
11812
|
+
for (const r of ranges) {
|
|
11813
|
+
const last = out[out.length - 1];
|
|
11814
|
+
if (last && r.start < last.end) {
|
|
11815
|
+
last.end = Math.max(last.end, r.end);
|
|
11816
|
+
continue;
|
|
11817
|
+
}
|
|
11818
|
+
out.push({ ...r });
|
|
11819
|
+
}
|
|
11820
|
+
return out;
|
|
11821
|
+
}
|
|
11822
|
+
function isEnvRedactionRequested() {
|
|
11823
|
+
const v = process.env.CHAT_RECALL_REDACT_INDEX;
|
|
11824
|
+
if (v === void 0)
|
|
11825
|
+
return null;
|
|
11826
|
+
return v === "1" || v === "true" || v === "yes";
|
|
11827
|
+
}
|
|
11828
|
+
function settingsView() {
|
|
11829
|
+
let mtimeMs = -1;
|
|
11830
|
+
try {
|
|
11831
|
+
mtimeMs = statSync13(settingsFilePath()).mtimeMs;
|
|
11832
|
+
} catch {
|
|
11833
|
+
}
|
|
11834
|
+
if (cache && cache.mtimeMs === mtimeMs)
|
|
11835
|
+
return cache;
|
|
11836
|
+
let enabled = false;
|
|
11837
|
+
const extra = [];
|
|
11838
|
+
try {
|
|
11839
|
+
const s = loadSettings();
|
|
11840
|
+
enabled = !!s.privacy?.redactIndex;
|
|
11841
|
+
for (const r of s.privacy?.redactionRules ?? []) {
|
|
11842
|
+
try {
|
|
11843
|
+
extra.push({ label: r.label, pattern: new RegExp(r.pattern, "g") });
|
|
11844
|
+
} catch {
|
|
11845
|
+
}
|
|
11846
|
+
}
|
|
11847
|
+
} catch {
|
|
11848
|
+
}
|
|
11849
|
+
cache = { mtimeMs, enabled, rules: [...DEFAULT_REDACTION_RULES, ...extra] };
|
|
11850
|
+
return cache;
|
|
11851
|
+
}
|
|
11852
|
+
function compileServerRule(spec) {
|
|
11853
|
+
const name = spec.name || "(unnamed)";
|
|
11854
|
+
if (!spec.regex)
|
|
11855
|
+
return { name, reason: "empty pattern" };
|
|
11856
|
+
if (spec.regex.length > MAX_PATTERN_LEN)
|
|
11857
|
+
return { name, reason: `pattern longer than ${MAX_PATTERN_LEN} chars` };
|
|
11858
|
+
let re;
|
|
11859
|
+
try {
|
|
11860
|
+
const flags = "g" + (spec.flags || "").replace(/[^ims]/g, "");
|
|
11861
|
+
re = new RegExp(spec.regex, flags);
|
|
11862
|
+
} catch (e) {
|
|
11863
|
+
return { name, reason: `invalid regex: ${e instanceof Error ? e.message : e}` };
|
|
11864
|
+
}
|
|
11865
|
+
if (re.test(""))
|
|
11866
|
+
return { name, reason: "matches the empty string (would redact everything)" };
|
|
11867
|
+
re.lastIndex = 0;
|
|
11868
|
+
if (re.test(CANARY))
|
|
11869
|
+
return { name, reason: "matches benign text \u2014 too broad" };
|
|
11870
|
+
re.lastIndex = 0;
|
|
11871
|
+
return { label: `${spec.source === "pack" ? "pack" : "tenant"}:${name}`, pattern: re };
|
|
11872
|
+
}
|
|
11873
|
+
function installServerRulePack(pack) {
|
|
11874
|
+
const rejected = [];
|
|
11875
|
+
const rules = [];
|
|
11876
|
+
for (const spec of pack.rules || []) {
|
|
11877
|
+
if (rules.length >= MAX_PACK_RULES) {
|
|
11878
|
+
rejected.push({ name: spec.name || "(unnamed)", reason: `pack exceeds ${MAX_PACK_RULES} rules` });
|
|
11879
|
+
continue;
|
|
11880
|
+
}
|
|
11881
|
+
const out = compileServerRule(spec);
|
|
11882
|
+
if ("reason" in out)
|
|
11883
|
+
rejected.push(out);
|
|
11884
|
+
else
|
|
11885
|
+
rules.push(out);
|
|
11886
|
+
}
|
|
11887
|
+
const version = pack.version || "";
|
|
11888
|
+
serverPack = { version, rules };
|
|
11889
|
+
return { version, accepted: rules.length, rejected };
|
|
11890
|
+
}
|
|
11891
|
+
function serverRulePackVersion() {
|
|
11892
|
+
return serverPack ? serverPack.version || "unversioned" : null;
|
|
11893
|
+
}
|
|
11894
|
+
function activeRules() {
|
|
11895
|
+
const base2 = settingsView().rules;
|
|
11896
|
+
return serverPack && serverPack.rules.length > 0 ? [...base2, ...serverPack.rules] : base2;
|
|
11897
|
+
}
|
|
11898
|
+
function isRedactionEnabled() {
|
|
11899
|
+
const env = isEnvRedactionRequested();
|
|
11900
|
+
if (env !== null)
|
|
11901
|
+
return env;
|
|
11902
|
+
return settingsView().enabled;
|
|
11903
|
+
}
|
|
11904
|
+
function redactSecrets(text, opts = {}) {
|
|
11905
|
+
if (!opts.force && !isRedactionEnabled())
|
|
11906
|
+
return text;
|
|
11907
|
+
if (!text)
|
|
11908
|
+
return text;
|
|
11909
|
+
const rules = opts.rules || activeRules();
|
|
11910
|
+
let out = text;
|
|
11911
|
+
for (const r of rules) {
|
|
11912
|
+
out = out.replace(r.pattern, (...args) => {
|
|
11913
|
+
const whole = args[0];
|
|
11914
|
+
const m = args.slice(0, args.length);
|
|
11915
|
+
const secret = secretOf(r, m);
|
|
11916
|
+
if (secret == null)
|
|
11917
|
+
return whole;
|
|
11918
|
+
if (opts.count)
|
|
11919
|
+
opts.count.redactions++;
|
|
11920
|
+
return whole.slice(0, whole.length - secret.length) + `[REDACTED:${r.label}]`;
|
|
11921
|
+
});
|
|
11922
|
+
}
|
|
11923
|
+
const ctx = findContextualSecrets(out);
|
|
11924
|
+
for (let i = ctx.length - 1; i >= 0; i--) {
|
|
11925
|
+
const { start, end } = ctx[i];
|
|
11926
|
+
if (opts.count)
|
|
11927
|
+
opts.count.redactions++;
|
|
11928
|
+
out = out.slice(0, start) + "[REDACTED:secret-context]" + out.slice(end);
|
|
11929
|
+
}
|
|
11930
|
+
return out;
|
|
11931
|
+
}
|
|
11932
|
+
function maskSecret(s) {
|
|
11933
|
+
if (!s)
|
|
11934
|
+
return "";
|
|
11935
|
+
if (s.length <= 4)
|
|
11936
|
+
return "*".repeat(s.length);
|
|
11937
|
+
return "*".repeat(Math.min(s.length - 4, 60)) + s.slice(-4);
|
|
11938
|
+
}
|
|
11939
|
+
function globalRegexFor(pattern) {
|
|
11940
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
|
|
11941
|
+
const key2 = pattern.source + "\0" + flags;
|
|
11942
|
+
let re = globalRegexCache.get(key2);
|
|
11943
|
+
if (!re) {
|
|
11944
|
+
re = new RegExp(pattern.source, flags);
|
|
11945
|
+
globalRegexCache.set(key2, re);
|
|
11946
|
+
}
|
|
11947
|
+
return re;
|
|
11948
|
+
}
|
|
11949
|
+
function makeLineResolver(text) {
|
|
11950
|
+
const starts = [];
|
|
11951
|
+
for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1))
|
|
11952
|
+
starts.push(i);
|
|
11953
|
+
return (index) => {
|
|
11954
|
+
let lo = 0, hi = starts.length;
|
|
11955
|
+
while (lo < hi) {
|
|
11956
|
+
const mid = lo + hi >> 1;
|
|
11957
|
+
if (starts[mid] < index)
|
|
11958
|
+
lo = mid + 1;
|
|
11959
|
+
else
|
|
11960
|
+
hi = mid;
|
|
11961
|
+
}
|
|
11962
|
+
return lo + 1;
|
|
11963
|
+
};
|
|
11964
|
+
}
|
|
11965
|
+
function scanTextForFindings(text, opts = {}) {
|
|
11966
|
+
if (!text)
|
|
11967
|
+
return [];
|
|
11968
|
+
const rules = opts.rules || activeRules();
|
|
11969
|
+
const findings = [];
|
|
11970
|
+
const seenSpans = /* @__PURE__ */ new Set();
|
|
11971
|
+
const lineAt = makeLineResolver(text);
|
|
11972
|
+
for (const r of rules) {
|
|
11973
|
+
const re = globalRegexFor(r.pattern);
|
|
11974
|
+
re.lastIndex = 0;
|
|
11975
|
+
let m;
|
|
11976
|
+
while ((m = re.exec(text)) !== null) {
|
|
11977
|
+
const secret = secretOf(r, m);
|
|
11978
|
+
if (secret == null) {
|
|
11979
|
+
if (re.lastIndex === m.index)
|
|
11980
|
+
re.lastIndex++;
|
|
11981
|
+
continue;
|
|
11982
|
+
}
|
|
11983
|
+
const at = m.index + (m[0].length - secret.length);
|
|
11984
|
+
const span = `${at}:${secret.length}`;
|
|
11985
|
+
if (!seenSpans.has(span)) {
|
|
11986
|
+
seenSpans.add(span);
|
|
11987
|
+
findings.push({ rule: r.label, line: lineAt(at), preview: maskSecret(secret) });
|
|
11988
|
+
}
|
|
11989
|
+
if (re.lastIndex === m.index)
|
|
11990
|
+
re.lastIndex++;
|
|
11991
|
+
}
|
|
11992
|
+
}
|
|
11993
|
+
for (const { start, end } of findContextualSecrets(text)) {
|
|
11994
|
+
const line = lineAt(start);
|
|
11995
|
+
findings.push({ rule: "secret-context", line, preview: maskSecret(text.slice(start, end)) });
|
|
11996
|
+
}
|
|
11997
|
+
return findings;
|
|
11998
|
+
}
|
|
11999
|
+
var DEFAULT_REDACTION_RULES, SECRET_CONTEXT_RE, HIGH_ENTROPY_TOKEN_RE, cache, CANARY, MAX_PACK_RULES, MAX_PATTERN_LEN, serverPack, globalRegexCache;
|
|
12000
|
+
var init_secret_redactor = __esm({
|
|
12001
|
+
"../engine/dist/core/secret-redactor.js"() {
|
|
12414
12002
|
"use strict";
|
|
12415
|
-
|
|
12003
|
+
init_settings();
|
|
12004
|
+
DEFAULT_REDACTION_RULES = [
|
|
12005
|
+
// AWS access tokens. Prefix-anchored: AKIA, ASIA, ABIA (long-term,
|
|
12006
|
+
// STS, EC2). Always exactly 16 hex/alphanumeric after the prefix.
|
|
12007
|
+
// NOTE: this matches only the access-key-ID, NOT the secret key value —
|
|
12008
|
+
// the `env-secret` rule below covers AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN.
|
|
12009
|
+
{ label: "aws-access-token", pattern: /\b(?:AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}\b/g },
|
|
12010
|
+
// AWS STS session tokens. These are long base64 blobs with NO `NAME=` context
|
|
12011
|
+
// when pasted bare into prose/code, so `env-secret` misses them — but they
|
|
12012
|
+
// reliably start with one of a handful of base64 prefixes (the encoded
|
|
12013
|
+
// `{"...` JSON header). Prefix-anchored → low false-positive. This is the
|
|
12014
|
+
// bare-value form that leaked (`search('IQoJEXAMPLEfakeSESSIONtokenNOTreal01234567890abcDEF==...')`).
|
|
12015
|
+
{ label: "aws-session-token", pattern: /\b(?:IQoJ|FwoG|FwoH|FQoG|FQoH|FwoD)[A-Za-z0-9/+]{40,}={0,2}/g },
|
|
12016
|
+
// Env/shell-assignment secrets — a VAR whose name ends in KEY/SECRET/TOKEN/
|
|
12017
|
+
// PASSWORD/CREDENTIAL, then `=`/`:`, then the value. Zero-width lookbehind so
|
|
12018
|
+
// only the VALUE is redacted (the var name stays for context). This is the
|
|
12019
|
+
// dominant real-world leak vector (`export` dumps, .env files, `curl -H`,
|
|
12020
|
+
// tool output) and is exactly what leaked AWS_SECRET_ACCESS_KEY and
|
|
12021
|
+
// AWS_SESSION_TOKEN in cleartext while the access-key-ID rule above caught
|
|
12022
|
+
// only the (least-sensitive) ID.
|
|
12023
|
+
// Suffix list deliberately broad: DB_PASS (our own helm chart's name!),
|
|
12024
|
+
// NPM_AUTH, SENTRY_DSN, SESSION/COOKIE secrets and BEARER vars all leaked
|
|
12025
|
+
// past the original KEY/SECRET/TOKEN/PASSWORD-only list. Values under 12
|
|
12026
|
+
// chars don't redact, so flag-ish vars (BYPASS=true) stay readable.
|
|
12027
|
+
// Context CONSUMED, secret in group 1 — see RedactionRule.secret. As a
|
|
12028
|
+
// lookbehind this was the second most expensive rule in the collector.
|
|
12029
|
+
{ 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 },
|
|
12030
|
+
// Credentials embedded in a connection URL: scheme://user:PASSWORD@host.
|
|
12031
|
+
// Redacts only the password segment (keeps scheme/user/host for context).
|
|
12032
|
+
//
|
|
12033
|
+
// Scheme-agnostic on purpose. The old rule allowlisted eight DB schemes, so
|
|
12034
|
+
// `https://user:pass@host`, `ssh://`, `ftp://`, `smtp://` and every other
|
|
12035
|
+
// userinfo URL walked through in cleartext — and a `jdbc:postgresql://…`
|
|
12036
|
+
// prefix broke the \b anchor for the one family it did cover. Any scheme
|
|
12037
|
+
// followed by userinfo is a credential, so match the shape, not a list.
|
|
12038
|
+
{ label: "url-password", secret: 1, pattern: /\b[a-zA-Z][a-zA-Z0-9+.-]{1,31}:\/\/[^:@\s/]{1,64}:([^@\s/]{1,256})(?=@)/g },
|
|
12039
|
+
// Bare AWS secret-access-key VALUE: EXACTLY 40 base64 chars, word-bounded,
|
|
12040
|
+
// containing upper+lower+digit. This is the gitleaks heuristic — the mixed-case
|
|
12041
|
+
// requirement excludes 40-char lowercase-hex git SHAs, and the exact-40 bound
|
|
12042
|
+
// excludes longer blobs/tokens. It catches the value when it appears bare in
|
|
12043
|
+
// prose with NO `NAME=` and NO nearby context word (e.g. inside a markdown
|
|
12044
|
+
// table or quoted in analysis) — the residual leak path the env-secret and
|
|
12045
|
+
// contextual rules both miss. Placed AFTER env-secret/url-password so the
|
|
12046
|
+
// keyed/URL forms keep their more-specific labels.
|
|
12047
|
+
// The three stacked lookaheads each re-scanned the same 40 characters, which
|
|
12048
|
+
// made this the MOST expensive rule of all 106 the collector runs (13.1% of a
|
|
12049
|
+
// 29.5 MB scan). The mixed-case-plus-digit test is now one JS pass.
|
|
12050
|
+
{
|
|
12051
|
+
label: "aws-secret-key",
|
|
12052
|
+
secret: 1,
|
|
12053
|
+
pattern: /(?<![A-Za-z0-9/+])([A-Za-z0-9/+]{40})(?![A-Za-z0-9/+])/g,
|
|
12054
|
+
where: (v) => /[a-z]/.test(v) && /[A-Z]/.test(v) && /[0-9]/.test(v)
|
|
12055
|
+
},
|
|
12056
|
+
// GitHub personal access tokens.
|
|
12057
|
+
{ label: "github-pat", pattern: /\bghp_[A-Za-z0-9]{36}\b/g },
|
|
12058
|
+
// GitHub fine-grained PATs.
|
|
12059
|
+
{ label: "github-fine", pattern: /\bgithub_pat_[A-Za-z0-9_]{82}\b/g },
|
|
12060
|
+
// GitHub OAuth.
|
|
12061
|
+
{ label: "github-oauth", pattern: /\bgho_[A-Za-z0-9]{36}\b/g },
|
|
12062
|
+
// GitHub app + refresh.
|
|
12063
|
+
{ label: "github-app", pattern: /\b(?:ghs|ghu|ghr)_[A-Za-z0-9]{36}\b/g },
|
|
12064
|
+
// Slack bot/user/app tokens.
|
|
12065
|
+
{ label: "slack-token", pattern: /\bxox[abopr]-[A-Za-z0-9-]{10,}\b/g },
|
|
12066
|
+
// Slack incoming webhook URL.
|
|
12067
|
+
{ label: "slack-webhook", pattern: /\bhttps:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+\b/g },
|
|
12068
|
+
// Stripe live + test keys.
|
|
12069
|
+
{ label: "stripe-key", pattern: /\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{24,}\b/g },
|
|
12070
|
+
// Anthropic.
|
|
12071
|
+
{ label: "anthropic-key", pattern: /\bsk-ant-[a-zA-Z0-9_-]{40,}\b/g },
|
|
12072
|
+
// OpenAI. Two shapes, deliberately kept apart. The classic key is
|
|
12073
|
+
// `sk-` + base62 with NO hyphens; the modern scoped keys carry an explicit
|
|
12074
|
+
// proj/svcacct/admin segment first. Allowing hyphens after a bare `sk-`
|
|
12075
|
+
// (which is what admitted sk-proj- in the first place) also matches ordinary
|
|
12076
|
+
// kebab-case text — `sk-fix-the-login-redirect-bug`, `.sk-button-primary`,
|
|
12077
|
+
// `sk-2024-01-01-backup` — and this corpus is full of branch names and CSS
|
|
12078
|
+
// classes. Requiring the segment keeps sk-proj- coverage without that.
|
|
12079
|
+
{ label: "openai-key", pattern: /\bsk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,}\b|\bsk-[A-Za-z0-9]{20,}\b/g },
|
|
12080
|
+
// Google API key (AIza…).
|
|
12081
|
+
{ label: "google-api-key", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
|
|
12082
|
+
// Generic JWT — three base64url segments.
|
|
12083
|
+
{ label: "jwt", pattern: /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g },
|
|
12084
|
+
// Private key blocks (PEM). The label is a free word run rather than a fixed
|
|
12085
|
+
// list, so ENCRYPTED PRIVATE KEY and PGP PRIVATE KEY BLOCK are covered
|
|
12086
|
+
// without another enumeration, and \1 forces END to name the same kind as
|
|
12087
|
+
// BEGIN.
|
|
12088
|
+
//
|
|
12089
|
+
// The body is [^-], NOT [\s\S]*?. That is a performance boundary, not a
|
|
12090
|
+
// style choice. An unanchored prefix with an unbounded lazy gap re-scans to
|
|
12091
|
+
// end-of-input from every candidate start, so text carrying many BEGIN
|
|
12092
|
+
// markers and no matching END costs O(n^2): 500KB of that measured 810ms
|
|
12093
|
+
// here, and this runs synchronously on the server's event loop over
|
|
12094
|
+
// tenant-supplied text (services/secret-rescan.ts). A PEM body is base64
|
|
12095
|
+
// plus headers and contains no '-', so excluding it makes the scan stop at
|
|
12096
|
+
// the next delimiter instead of at EOF — same matches, linear cost.
|
|
12097
|
+
{ label: "private-key", pattern: /-----BEGIN ([A-Z0-9 ]{0,32})PRIVATE KEY(?: BLOCK)?-----[^-]{0,10000}?-----END \1PRIVATE KEY(?: BLOCK)?-----/g },
|
|
12098
|
+
// PuTTY keys are not PEM. Match the Private-Lines payload — the actual key
|
|
12099
|
+
// material — rather than spanning the whole file, which would need the same
|
|
12100
|
+
// unbounded gap the rule above just removed. Line-anchored and base64-only,
|
|
12101
|
+
// so it cannot run away.
|
|
12102
|
+
{ label: "putty-key", pattern: /^Private-Lines:[ \t]*\d{1,4}[\r\n]+(?:[A-Za-z0-9+/=]{8,120}[\r\n]+){1,400}/gm },
|
|
12103
|
+
// npm tokens (uuid-shaped — overlap with our UUID filter; intentional).
|
|
12104
|
+
{ label: "npm-token", pattern: /\bnpm_[A-Za-z0-9]{36}\b/g },
|
|
12105
|
+
// .npmrc auth lines: //registry.npmjs.org/:_authToken=VALUE (the key is not
|
|
12106
|
+
// ALL-CAPS, so the env-secret rule above never matched it). The leading `:`
|
|
12107
|
+
// is required — it is what makes this npmrc syntax rather than any shell var
|
|
12108
|
+
// ending in _AUTH, which env-secret already owns and labels better.
|
|
12109
|
+
// [^\s"'`]+ so the closing quote is not swallowed, and a ${VAR} placeholder
|
|
12110
|
+
// is left alone — redacting it destroys a config example and hides nothing.
|
|
12111
|
+
// The rule the perf profile caught the wedged daemon inside: 98.94% of samples.
|
|
12112
|
+
{ label: "npmrc-auth", secret: 1, pattern: /:_(?:authToken|password|auth)[ \t]*=[ \t]*(?!\$\{)([^\s"'`]{12,})/gi },
|
|
12113
|
+
// HTTP Authorization headers. env-secret requires an ALL-CAPS name, and
|
|
12114
|
+
// `Authorization` is mixed case, so both Bearer and Basic walked straight
|
|
12115
|
+
// through — including short opaque tokens the contextual pass also misses
|
|
12116
|
+
// (it needs 32+ chars AND mixed-case-plus-digit).
|
|
12117
|
+
// Same two guards as npmrc-auth: stop at a quote, and skip the obvious
|
|
12118
|
+
// placeholders that fill documentation (<YOUR_TOKEN>, ${TOKEN}, xxx…).
|
|
12119
|
+
{ label: "auth-header", secret: 1, pattern: /\bauthorization[ \t]*:[ \t]*(?:bearer|basic|token)[ \t]+(?![<$])([^\s"'`<]{8,})/gi },
|
|
12120
|
+
// Vendor tokens with unambiguous prefixes. Each is a fixed literal, so a
|
|
12121
|
+
// false positive is essentially impossible and the cost of listing is low.
|
|
12122
|
+
{ label: "gitlab-pat", pattern: /\bglpat-[A-Za-z0-9_-]{20,}\b/g },
|
|
12123
|
+
{ label: "slack-app-token", pattern: /\bxapp-\d-[A-Z0-9]{8,}-\d{10,}-[a-f0-9]{32,}\b/g },
|
|
12124
|
+
{ label: "sendgrid-key", pattern: /\bSG\.[A-Za-z0-9_-]{20,24}\.[A-Za-z0-9_-]{40,50}\b/g },
|
|
12125
|
+
// (?<![-\w]) so `cache-key-<md5>` is not read as a mailgun key.
|
|
12126
|
+
{ label: "mailgun-key", pattern: /(?<![-\w])key-[a-f0-9]{32}\b/g },
|
|
12127
|
+
{ label: "digitalocean-pat", pattern: /\bdop_v1_[a-f0-9]{64}\b/g },
|
|
12128
|
+
{ label: "huggingface-token", pattern: /\bhf_[A-Za-z0-9]{30,}\b/g },
|
|
12129
|
+
{ label: "shopify-token", pattern: /\bshp(?:at|ca|pa|ss)_[a-fA-F0-9]{32}\b/g },
|
|
12130
|
+
{ label: "pypi-token", pattern: /\bpypi-AgEIcHlwaS5vcmc[A-Za-z0-9_-]{50,}\b/g },
|
|
12131
|
+
// No twilio-sid rule: AC…/SK… are Twilio SIDs, which are public account and
|
|
12132
|
+
// key identifiers, not the auth token. Redacting them hid nothing and
|
|
12133
|
+
// collided with 32-hex etags and MD5 sums.
|
|
12134
|
+
// No discord-bot-token rule either: the shape (word.word.word) matched
|
|
12135
|
+
// ordinary dotted symbols such as a.b.ApplicationContextProviderImpl.
|
|
12136
|
+
{ label: "telegram-bot-token", pattern: /\b\d{8,10}:AA[A-Za-z0-9_-]{32,}\b/g },
|
|
12137
|
+
{ label: "doppler-token", pattern: /\bdp\.(?:pt|st|sa|ct)\.[A-Za-z0-9_-]{20,}\b/g },
|
|
12138
|
+
// Azure Storage SAS signature — the `sig=` parameter is the actual secret.
|
|
12139
|
+
{ label: "azure-sas", secret: 1, pattern: /[?&]sig=([A-Za-z0-9%/+=]{20,})/g }
|
|
12140
|
+
];
|
|
12141
|
+
SECRET_CONTEXT_RE = /\b(?:secret|password|passwd|credential|access[ _-]?key|private[ _-]?key|api[ _-]?key|auth(?:oriz\w*)?|bearer|token)\b/gi;
|
|
12142
|
+
HIGH_ENTROPY_TOKEN_RE = /[A-Za-z0-9/+_-]{32,}/g;
|
|
12143
|
+
cache = null;
|
|
12144
|
+
CANARY = "the quick brown fox jumps over the lazy dog 0123456789 commit 8ba1f109551bd432803012645ac136ddd475d0fb src/index.ts:42";
|
|
12145
|
+
MAX_PACK_RULES = 200;
|
|
12146
|
+
MAX_PATTERN_LEN = 512;
|
|
12147
|
+
serverPack = null;
|
|
12148
|
+
globalRegexCache = /* @__PURE__ */ new Map();
|
|
12416
12149
|
}
|
|
12417
12150
|
});
|
|
12418
12151
|
|
|
@@ -13216,10 +12949,10 @@ var init_types = __esm({
|
|
|
13216
12949
|
|
|
13217
12950
|
// ../engine/dist/transcript/opencode.js
|
|
13218
12951
|
async function parseOpenCodeSubagents(parentSessionId) {
|
|
13219
|
-
const { existsSync:
|
|
12952
|
+
const { existsSync: existsSync51 } = await import("fs");
|
|
13220
12953
|
const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
|
|
13221
12954
|
const dbPath = opencodeBackend2.dbPath();
|
|
13222
|
-
if (!
|
|
12955
|
+
if (!existsSync51(dbPath))
|
|
13223
12956
|
return [];
|
|
13224
12957
|
const realParent = parentSessionId.replace("opencode_", "");
|
|
13225
12958
|
const db = openSqliteReadonlyOrThrow(dbPath);
|
|
@@ -13260,10 +12993,10 @@ async function parseOpenCodeSubagents(parentSessionId) {
|
|
|
13260
12993
|
}
|
|
13261
12994
|
}
|
|
13262
12995
|
async function parseOpenCodeTranscript(sessionId) {
|
|
13263
|
-
const { existsSync:
|
|
12996
|
+
const { existsSync: existsSync51 } = await import("fs");
|
|
13264
12997
|
const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
|
|
13265
12998
|
const dbPath = opencodeBackend2.dbPath();
|
|
13266
|
-
if (!
|
|
12999
|
+
if (!existsSync51(dbPath))
|
|
13267
13000
|
return [];
|
|
13268
13001
|
const realId = sessionId.replace("opencode_", "");
|
|
13269
13002
|
const db = openSqliteReadonlyOrThrow(dbPath);
|
|
@@ -13340,6 +13073,7 @@ __export(transcript_exports, {
|
|
|
13340
13073
|
shadowFileFor: () => shadowFileFor,
|
|
13341
13074
|
shadowRoot: () => shadowRoot,
|
|
13342
13075
|
shadowSizeOnDisk: () => shadowSizeOnDisk,
|
|
13076
|
+
shadowUncompressedBytes: () => shadowUncompressedBytes,
|
|
13343
13077
|
snapshotShadow: () => snapshotShadow,
|
|
13344
13078
|
trimTranscriptForSync: () => trimTranscriptForSync,
|
|
13345
13079
|
updateShadow: () => updateShadow,
|
|
@@ -14446,111 +14180,6 @@ var init_source_registry = __esm({
|
|
|
14446
14180
|
}
|
|
14447
14181
|
});
|
|
14448
14182
|
|
|
14449
|
-
// ../engine/dist/parsers/session-source.js
|
|
14450
|
-
var SessionSource;
|
|
14451
|
-
var init_session_source = __esm({
|
|
14452
|
-
"../engine/dist/parsers/session-source.js"() {
|
|
14453
|
-
"use strict";
|
|
14454
|
-
init_session();
|
|
14455
|
-
init_chunker();
|
|
14456
|
-
init_settings();
|
|
14457
|
-
SessionSource = class {
|
|
14458
|
-
sourceType = "session";
|
|
14459
|
-
claudeDir;
|
|
14460
|
-
constructor(claudeDir) {
|
|
14461
|
-
this.claudeDir = claudeDir;
|
|
14462
|
-
}
|
|
14463
|
-
async *discover() {
|
|
14464
|
-
if (!isSourceEnabled("claude", "sessions"))
|
|
14465
|
-
return;
|
|
14466
|
-
for (const [entry, sessionPath] of getAllSessions(this.claudeDir)) {
|
|
14467
|
-
yield this.entryToItem(entry, sessionPath);
|
|
14468
|
-
}
|
|
14469
|
-
}
|
|
14470
|
-
async parse(item) {
|
|
14471
|
-
const content = await parseSessionFile(item.filePath);
|
|
14472
|
-
const entry = this.itemToEntry(item);
|
|
14473
|
-
const sessionChunks = chunkSession(entry, content);
|
|
14474
|
-
item.extra = {
|
|
14475
|
-
...item.extra || {},
|
|
14476
|
-
...content.metadata
|
|
14477
|
-
};
|
|
14478
|
-
return sessionChunks.map((sc) => ({
|
|
14479
|
-
chunkId: sc.chunkId,
|
|
14480
|
-
itemId: sc.sessionId,
|
|
14481
|
-
sourceType: "session",
|
|
14482
|
-
// Empty title is preferable to "Session abc12345" placeholder text:
|
|
14483
|
-
// the latter pollutes search results and gets surfaced in the UI as
|
|
14484
|
-
// a misleading heading. Downstream renderers fall back to
|
|
14485
|
-
// project path / time when title is empty.
|
|
14486
|
-
title: sc.firstPrompt.slice(0, 100),
|
|
14487
|
-
text: sc.text,
|
|
14488
|
-
chunkType: sc.chunkType,
|
|
14489
|
-
projectPath: sc.projectPath,
|
|
14490
|
-
filePath: sc.sourceFile,
|
|
14491
|
-
mtime: sc.mtime
|
|
14492
|
-
}));
|
|
14493
|
-
}
|
|
14494
|
-
async extractLinks(item) {
|
|
14495
|
-
if (item.filePath) {
|
|
14496
|
-
try {
|
|
14497
|
-
const { detectForkPredecessor: detectForkPredecessor2 } = await Promise.resolve().then(() => (init_raw(), raw_exports));
|
|
14498
|
-
const predecessor = detectForkPredecessor2(item.filePath);
|
|
14499
|
-
if (predecessor) {
|
|
14500
|
-
return [{
|
|
14501
|
-
sourceType: "session",
|
|
14502
|
-
sourceId: item.id,
|
|
14503
|
-
targetType: "session",
|
|
14504
|
-
targetId: predecessor,
|
|
14505
|
-
linkType: "forked_from",
|
|
14506
|
-
confidence: 1
|
|
14507
|
-
}];
|
|
14508
|
-
}
|
|
14509
|
-
} catch {
|
|
14510
|
-
}
|
|
14511
|
-
}
|
|
14512
|
-
return [];
|
|
14513
|
-
}
|
|
14514
|
-
entryToItem(entry, sessionPath) {
|
|
14515
|
-
return {
|
|
14516
|
-
id: entry.sessionId,
|
|
14517
|
-
sourceType: "session",
|
|
14518
|
-
// Empty title when there's no first prompt — the UI then knows to
|
|
14519
|
-
// fall back to summary or project path instead of rendering the
|
|
14520
|
-
// useless "Session abc12345" placeholder as a heading.
|
|
14521
|
-
title: entry.firstPrompt ? entry.firstPrompt.replace(/\n/g, " ").trim().slice(0, 100) : "",
|
|
14522
|
-
projectPath: entry.projectPath,
|
|
14523
|
-
filePath: sessionPath,
|
|
14524
|
-
mtime: entry.fileMtime,
|
|
14525
|
-
contentPreview: entry.firstPrompt?.slice(0, 200),
|
|
14526
|
-
extra: {
|
|
14527
|
-
messageCount: entry.messageCount,
|
|
14528
|
-
gitBranch: entry.gitBranch,
|
|
14529
|
-
created: entry.created,
|
|
14530
|
-
modified: entry.modified,
|
|
14531
|
-
isSidechain: entry.isSidechain
|
|
14532
|
-
}
|
|
14533
|
-
};
|
|
14534
|
-
}
|
|
14535
|
-
itemToEntry(item) {
|
|
14536
|
-
const extra = item.extra || {};
|
|
14537
|
-
return {
|
|
14538
|
-
sessionId: item.id,
|
|
14539
|
-
fullPath: item.filePath,
|
|
14540
|
-
fileMtime: item.mtime,
|
|
14541
|
-
firstPrompt: item.contentPreview || "",
|
|
14542
|
-
messageCount: extra.messageCount || 0,
|
|
14543
|
-
created: extra.created || "",
|
|
14544
|
-
modified: extra.modified || "",
|
|
14545
|
-
gitBranch: extra.gitBranch || "",
|
|
14546
|
-
projectPath: item.projectPath,
|
|
14547
|
-
isSidechain: extra.isSidechain || false
|
|
14548
|
-
};
|
|
14549
|
-
}
|
|
14550
|
-
};
|
|
14551
|
-
}
|
|
14552
|
-
});
|
|
14553
|
-
|
|
14554
14183
|
// ../engine/dist/core/utils.js
|
|
14555
14184
|
import { existsSync as existsSync21, readdirSync as readdirSync15 } from "fs";
|
|
14556
14185
|
import { homedir as homedir6 } from "os";
|
|
@@ -17907,7 +17536,6 @@ var init_plugins_source = __esm({
|
|
|
17907
17536
|
|
|
17908
17537
|
// ../engine/dist/parsers/all-sources.js
|
|
17909
17538
|
function buildSourceRegistry(registry = new SourceRegistry()) {
|
|
17910
|
-
registry.register(new SessionSource());
|
|
17911
17539
|
registry.register(new PlanSource());
|
|
17912
17540
|
registry.register(new TaskSource());
|
|
17913
17541
|
registry.register(new ClaudeMdSource());
|
|
@@ -17931,7 +17559,6 @@ var init_all_sources = __esm({
|
|
|
17931
17559
|
"../engine/dist/parsers/all-sources.js"() {
|
|
17932
17560
|
"use strict";
|
|
17933
17561
|
init_source_registry();
|
|
17934
|
-
init_session_source();
|
|
17935
17562
|
init_plan_source();
|
|
17936
17563
|
init_task_source();
|
|
17937
17564
|
init_claude_md_source();
|
|
@@ -18303,7 +17930,7 @@ var init_sync_fields = __esm({
|
|
|
18303
17930
|
});
|
|
18304
17931
|
|
|
18305
17932
|
// ../engine/dist/core/index-lock.js
|
|
18306
|
-
import { existsSync as existsSync39, mkdirSync as mkdirSync8, openSync as
|
|
17933
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync8, openSync as openSync8, writeFileSync as writeFileSync7, readFileSync as readFileSync31, rmSync as rmSync4, statSync as statSync35, closeSync as closeSync8 } from "fs";
|
|
18307
17934
|
import { dirname as dirname17, join as join40 } from "path";
|
|
18308
17935
|
function acquireIndexLock(opts) {
|
|
18309
17936
|
const lockPath = lockFilePath();
|
|
@@ -18315,7 +17942,7 @@ function acquireIndexLock(opts) {
|
|
|
18315
17942
|
}
|
|
18316
17943
|
let fd;
|
|
18317
17944
|
try {
|
|
18318
|
-
fd =
|
|
17945
|
+
fd = openSync8(lockPath, "wx", 384);
|
|
18319
17946
|
} catch (err) {
|
|
18320
17947
|
if (err.code === "EEXIST")
|
|
18321
17948
|
return null;
|
|
@@ -18325,7 +17952,7 @@ function acquireIndexLock(opts) {
|
|
|
18325
17952
|
try {
|
|
18326
17953
|
writeFileSync7(fd, JSON.stringify(holder));
|
|
18327
17954
|
} finally {
|
|
18328
|
-
|
|
17955
|
+
closeSync8(fd);
|
|
18329
17956
|
}
|
|
18330
17957
|
let released = false;
|
|
18331
17958
|
return {
|
|
@@ -18416,7 +18043,7 @@ var init_http = __esm({
|
|
|
18416
18043
|
CLI_VERSION = JSON.parse(readFileSync32(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
|
|
18417
18044
|
} catch {
|
|
18418
18045
|
try {
|
|
18419
|
-
if (true) CLI_VERSION = "0.
|
|
18046
|
+
if (true) CLI_VERSION = "0.7.1";
|
|
18420
18047
|
} catch {
|
|
18421
18048
|
}
|
|
18422
18049
|
}
|
|
@@ -18911,7 +18538,7 @@ var init_telemetry = __esm({
|
|
|
18911
18538
|
init_sync_client();
|
|
18912
18539
|
cliVersion = () => {
|
|
18913
18540
|
try {
|
|
18914
|
-
return true ? "0.
|
|
18541
|
+
return true ? "0.7.1" : "dev";
|
|
18915
18542
|
} catch {
|
|
18916
18543
|
return "dev";
|
|
18917
18544
|
}
|
|
@@ -19136,16 +18763,17 @@ __export(auto_update_exports, {
|
|
|
19136
18763
|
noteUpdateFailure: () => noteUpdateFailure,
|
|
19137
18764
|
planAutoUpdate: () => planAutoUpdate,
|
|
19138
18765
|
readUpdateState: () => readUpdateState,
|
|
18766
|
+
resolveNpm: () => resolveNpm,
|
|
19139
18767
|
runAutoUpdate: () => runAutoUpdate,
|
|
19140
18768
|
runningPrefix: () => runningPrefix,
|
|
19141
18769
|
sweepStaleStaging: () => sweepStaleStaging,
|
|
19142
18770
|
tarballUrl: () => tarballUrl
|
|
19143
18771
|
});
|
|
19144
|
-
import { execSync as execSync2 } from "node:child_process";
|
|
19145
|
-
import { readFileSync as readFileSync34 } from "node:fs";
|
|
18772
|
+
import { execSync as execSync2, execFileSync as execFileSync3 } from "node:child_process";
|
|
18773
|
+
import { readFileSync as readFileSync34, existsSync as existsSync41 } from "node:fs";
|
|
19146
18774
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
19147
18775
|
import { writeFileSync as writeFileSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync5, readdirSync as readdirSync31, statSync as statSync36, mkdirSync as mkdirSync9 } from "node:fs";
|
|
19148
|
-
import { join as join41, dirname as dirname18, sep as sep2 } from "node:path";
|
|
18776
|
+
import { join as join41, dirname as dirname18, sep as sep2, posix as pathPosix, win32 as pathWin32 } from "node:path";
|
|
19149
18777
|
import { tmpdir as tmpdir2, homedir as homedir15 } from "node:os";
|
|
19150
18778
|
import { createHash as createHash7 } from "node:crypto";
|
|
19151
18779
|
function compareVersions(a, b) {
|
|
@@ -19260,25 +18888,46 @@ function runningPrefix(moduleUrl = import.meta.url) {
|
|
|
19260
18888
|
const beforeNodeModules = here.slice(0, at);
|
|
19261
18889
|
return beforeNodeModules.endsWith(`${sep2}lib`) ? beforeNodeModules.slice(0, -4) : beforeNodeModules;
|
|
19262
18890
|
}
|
|
19263
|
-
function npmInstall(
|
|
18891
|
+
function npmInstall(npm, args) {
|
|
19264
18892
|
try {
|
|
19265
|
-
|
|
18893
|
+
const opts = { stdio: ["ignore", "ignore", "pipe"], env: npm.env };
|
|
18894
|
+
if (npm.shell) execSync2([npm.file, ...npm.args, ...args].map((a) => `"${a}"`).join(" "), opts);
|
|
18895
|
+
else execFileSync3(npm.file, [...npm.args, ...args], opts);
|
|
19266
18896
|
} catch (e) {
|
|
19267
18897
|
const err = e;
|
|
19268
18898
|
const detail = String(err.stderr ?? "").trim().split("\n").filter((l) => l.trim() && !/^npm (notice|warn)\b/i.test(l)).slice(-4).join(" | ").slice(0, 400);
|
|
19269
18899
|
throw new Error(detail ? `${err.message ?? "install failed"} \u2014 ${detail}` : err.message ?? "install failed");
|
|
19270
18900
|
}
|
|
19271
18901
|
}
|
|
18902
|
+
function resolveNpm(loc) {
|
|
18903
|
+
const win = loc.platform === "win32";
|
|
18904
|
+
const p = win ? pathWin32 : pathPosix;
|
|
18905
|
+
const nodeDir = p.dirname(loc.execPath);
|
|
18906
|
+
const cliUnder = (root) => win ? p.join(root, "node_modules", "npm", "bin", "npm-cli.js") : p.join(root, "lib", "node_modules", "npm", "bin", "npm-cli.js");
|
|
18907
|
+
const candidates2 = [
|
|
18908
|
+
cliUnder(win ? nodeDir : p.join(nodeDir, "..")),
|
|
18909
|
+
...loc.prefix ? [cliUnder(loc.prefix)] : []
|
|
18910
|
+
];
|
|
18911
|
+
const pathKey = Object.keys(loc.env).find((k) => k.toUpperCase() === "PATH") ?? "PATH";
|
|
18912
|
+
const binDirs = [nodeDir, ...loc.prefix ? [win ? loc.prefix : p.join(loc.prefix, "bin")] : []];
|
|
18913
|
+
const delimiter3 = win ? ";" : ":";
|
|
18914
|
+
const current = loc.env[pathKey] ?? "";
|
|
18915
|
+
const env = { ...loc.env, [pathKey]: [...binDirs, ...current ? [current] : []].join(delimiter3) };
|
|
18916
|
+
const cli = candidates2.find((c) => loc.exists(c));
|
|
18917
|
+
if (cli) return { file: loc.execPath, args: [cli], env, shell: false };
|
|
18918
|
+
return { file: "npm", args: [], env, shell: true };
|
|
18919
|
+
}
|
|
19272
18920
|
function realInstall(tgz) {
|
|
19273
18921
|
const prefix = runningPrefix();
|
|
18922
|
+
const npm = resolveNpm({ execPath: process.execPath, prefix, platform: process.platform, env: process.env, exists: existsSync41 });
|
|
19274
18923
|
if (prefix) {
|
|
19275
|
-
npmInstall(
|
|
18924
|
+
npmInstall(npm, ["install", "-g", "--prefix", prefix, tgz]);
|
|
19276
18925
|
return;
|
|
19277
18926
|
}
|
|
19278
18927
|
try {
|
|
19279
|
-
npmInstall(
|
|
18928
|
+
npmInstall(npm, ["install", "-g", tgz]);
|
|
19280
18929
|
} catch {
|
|
19281
|
-
npmInstall(
|
|
18930
|
+
npmInstall(npm, ["install", "-g", "--prefix", join41(homedir15(), ".local"), tgz]);
|
|
19282
18931
|
}
|
|
19283
18932
|
}
|
|
19284
18933
|
function realRestart(platform2) {
|
|
@@ -20081,16 +19730,53 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
20081
19730
|
clearTimeout(timer);
|
|
20082
19731
|
}
|
|
20083
19732
|
};
|
|
19733
|
+
const postChunkedHead = async (conv, fileSize) => {
|
|
19734
|
+
const headers = { "content-type": "application/json" };
|
|
19735
|
+
if (cred.token) headers.authorization = `Bearer ${cred.token}`;
|
|
19736
|
+
const ac = new AbortController();
|
|
19737
|
+
const timer = setTimeout(() => ac.abort(new Error(`upload timed out after ${UPLOAD_TIMEOUT_MS}ms`)), UPLOAD_TIMEOUT_MS);
|
|
19738
|
+
try {
|
|
19739
|
+
const res = await fetch(`${base2}/api/sync`, {
|
|
19740
|
+
method: "POST",
|
|
19741
|
+
headers,
|
|
19742
|
+
body: JSON.stringify({ conversations: [conv] }),
|
|
19743
|
+
signal: ac.signal
|
|
19744
|
+
});
|
|
19745
|
+
if (!res.ok) throw new Error(`chunked head sync failed: HTTP ${res.status} ${await res.text().catch(() => "")}`);
|
|
19746
|
+
const body = await res.json().catch(() => ({}));
|
|
19747
|
+
const id = conv.session_id;
|
|
19748
|
+
const guarded = (body.shrink_guarded ?? []).find((g) => g.session_id === id);
|
|
19749
|
+
const mtime = conv.mtime;
|
|
19750
|
+
try {
|
|
19751
|
+
if (!guarded) {
|
|
19752
|
+
markSynced(base2, [{ id, mtime, offset: conv.from_offset, size: conv.from_offset, acked: true }]);
|
|
19753
|
+
} else if (typeof guarded.o === "number" && guarded.o > 0 && guarded.o <= fileSize) {
|
|
19754
|
+
markSynced(base2, [{ id, mtime, offset: guarded.o, size: guarded.o, acked: true }]);
|
|
19755
|
+
} else {
|
|
19756
|
+
markSynced(base2, [{ id, mtime }]);
|
|
19757
|
+
}
|
|
19758
|
+
} catch {
|
|
19759
|
+
}
|
|
19760
|
+
appendResults.uploaded++;
|
|
19761
|
+
} finally {
|
|
19762
|
+
clearTimeout(timer);
|
|
19763
|
+
}
|
|
19764
|
+
};
|
|
20084
19765
|
let appendBatch = [];
|
|
19766
|
+
let appendBatchBytes = 0;
|
|
20085
19767
|
const APPEND_BATCH_SIZE = 50;
|
|
19768
|
+
const APPEND_BATCH_MAX_BYTES = SYNC_CHUNK_BYTES;
|
|
20086
19769
|
const appendFlush = async () => {
|
|
20087
19770
|
if (appendBatch.length === 0) return;
|
|
20088
19771
|
const batch = appendBatch;
|
|
20089
19772
|
appendBatch = [];
|
|
19773
|
+
appendBatchBytes = 0;
|
|
20090
19774
|
await postAppend(batch);
|
|
20091
19775
|
};
|
|
20092
|
-
const appendAdd = async (conv) => {
|
|
19776
|
+
const appendAdd = async (conv, bytes = 0) => {
|
|
19777
|
+
if (appendBatch.length > 0 && appendBatchBytes + bytes > APPEND_BATCH_MAX_BYTES) await appendFlush();
|
|
20093
19778
|
appendBatch.push(conv);
|
|
19779
|
+
appendBatchBytes += bytes;
|
|
20094
19780
|
if (appendBatch.length >= APPEND_BATCH_SIZE) await appendFlush();
|
|
20095
19781
|
};
|
|
20096
19782
|
const itemsBatch = makeBatcher("items", 100);
|
|
@@ -20169,7 +19855,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
20169
19855
|
let externalFindings = null;
|
|
20170
19856
|
if (scanSecrets) {
|
|
20171
19857
|
externalFindings = /* @__PURE__ */ new Map();
|
|
20172
|
-
const toScan = slice.filter((ref) => modeOf(ref) === "full" &&
|
|
19858
|
+
const toScan = slice.filter((ref) => modeOf(ref) === "full" && sessionBuildBytes(ref) <= FULL_BUILD_MAX_BYTES);
|
|
20173
19859
|
trace?.(`batch secret scan start: ${toScan.length} session(s), \u2264${(BATCH_SCAN_MAX_BYTES / 1048576).toFixed(0)}MB per slice`);
|
|
20174
19860
|
const batch = batchScanExternal(
|
|
20175
19861
|
toScan.map((ref) => ({
|
|
@@ -20213,11 +19899,17 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
20213
19899
|
const ack = ledger.get(ref.prefixedId);
|
|
20214
19900
|
try {
|
|
20215
19901
|
const tail = await buildConversationTail(ref, ack.o ?? 0, { mapPath, tenantRules });
|
|
20216
|
-
|
|
19902
|
+
const chunked = sessionBuildBytes(ref) > FULL_BUILD_MAX_BYTES;
|
|
19903
|
+
if (tail && tail.findings.length > 0 && !chunked) {
|
|
20217
19904
|
promotedForFindings = true;
|
|
20218
19905
|
} else if (tail) {
|
|
20219
19906
|
redactions += tail.redactions;
|
|
20220
|
-
await appendAdd(tail.conv);
|
|
19907
|
+
await appendAdd(tail.conv, tail.bytes);
|
|
19908
|
+
if (tail.findings.length > 0) {
|
|
19909
|
+
await appendFlush();
|
|
19910
|
+
const all = await scanTranscriptFindingsChunked(ref, tenantRules);
|
|
19911
|
+
if (all.length > 0) await findingsBatch.add(all);
|
|
19912
|
+
}
|
|
20221
19913
|
walked++;
|
|
20222
19914
|
if (walked % 250 === 0) console.error(`[sync] ${walked}/${slice.length} sessions\u2026`);
|
|
20223
19915
|
continue;
|
|
@@ -20265,12 +19957,13 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
20265
19957
|
}
|
|
20266
19958
|
redactions += built.redactions;
|
|
20267
19959
|
if (built.srcHash) shippedHash.set(ref.prefixedId, built.srcHash);
|
|
20268
|
-
await
|
|
19960
|
+
if (built.conv.chunked) await postChunkedHead(built.conv, sessionFileBytes(ref));
|
|
19961
|
+
else await convBatch.add(built.conv);
|
|
20269
19962
|
if (built.findings.length > 0) await findingsBatch.add(built.findings);
|
|
20270
19963
|
if (upload.sessionMeta) {
|
|
20271
19964
|
await accumulateKg(extractEntities(built.kgText, { projectPath: built.projectPath, sourceType: "session", sessionId: ref.prefixedId, validFrom: ref.mtime ? new Date(ref.mtime).toISOString().slice(0, 10) : void 0 }), ref.prefixedId);
|
|
20272
19965
|
}
|
|
20273
|
-
if (upload.sessionMeta && sessionFileBytes(ref) <=
|
|
19966
|
+
if (upload.sessionMeta && sessionFileBytes(ref) <= DERIVED_MAX_BYTES) {
|
|
20274
19967
|
try {
|
|
20275
19968
|
const count = { redactions: 0 };
|
|
20276
19969
|
const offloaded = await scanPool().runDerived({
|
|
@@ -20450,35 +20143,70 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
20450
20143
|
scanMs: Math.round(scanMs)
|
|
20451
20144
|
};
|
|
20452
20145
|
}
|
|
20146
|
+
async function scanTranscriptFindingsChunked(ref, tenantRules) {
|
|
20147
|
+
const backend = getBackendForId(ref.prefixedId);
|
|
20148
|
+
const findings = [];
|
|
20149
|
+
if (!backend?.readFromOffset) return findings;
|
|
20150
|
+
let offset = 0;
|
|
20151
|
+
let lineBase = 0;
|
|
20152
|
+
for (; ; ) {
|
|
20153
|
+
const chunk = await backend.readFromOffset(ref.prefixedId, offset, SYNC_CHUNK_BYTES);
|
|
20154
|
+
if (chunk.newOffset <= offset) break;
|
|
20155
|
+
offset = chunk.newOffset;
|
|
20156
|
+
if (chunk.skippedBytes) {
|
|
20157
|
+
lineBase += 1;
|
|
20158
|
+
continue;
|
|
20159
|
+
}
|
|
20160
|
+
try {
|
|
20161
|
+
for (const f of scanTextForFindings(chunk.text)) {
|
|
20162
|
+
findings.push({ session_id: ref.prefixedId, detector: "builtin", rule: f.rule, line: lineBase + f.line, preview: f.preview });
|
|
20163
|
+
}
|
|
20164
|
+
if (tenantRules && tenantRules.length > 0) {
|
|
20165
|
+
for (const f of scanTenantRules(chunk.text, tenantRules)) {
|
|
20166
|
+
findings.push({ session_id: ref.prefixedId, detector: f.detector, rule: f.rule, line: lineBase + f.line, preview: f.preview });
|
|
20167
|
+
}
|
|
20168
|
+
}
|
|
20169
|
+
} catch {
|
|
20170
|
+
}
|
|
20171
|
+
for (let i = chunk.text.indexOf("\n"); i >= 0; i = chunk.text.indexOf("\n", i + 1)) lineBase++;
|
|
20172
|
+
}
|
|
20173
|
+
return dropFuzzyFindings(findings, (f) => ({ detector: f.detector, rule: f.rule }));
|
|
20174
|
+
}
|
|
20453
20175
|
function sessionFileBytes(ref) {
|
|
20454
20176
|
const backend = getBackendForId(ref.prefixedId);
|
|
20455
20177
|
if (!backend?.isAppendOnly?.()) return 0;
|
|
20456
20178
|
return backend.fileSize?.(ref.prefixedId) ?? 0;
|
|
20457
20179
|
}
|
|
20180
|
+
function sessionBuildBytes(ref) {
|
|
20181
|
+
const main = sessionFileBytes(ref);
|
|
20182
|
+
if (main === 0) return 0;
|
|
20183
|
+
return Math.max(main, shadowUncompressedBytes(ref.toolId, ref.rawId));
|
|
20184
|
+
}
|
|
20458
20185
|
async function buildConversationSync(ref, mtime, opts = {}) {
|
|
20459
20186
|
const mapPath = opts.mapPath ?? ((p) => p);
|
|
20460
20187
|
let includeRaw = opts.includeRaw !== false;
|
|
20461
20188
|
let includeMeta = opts.includeMeta !== false;
|
|
20462
20189
|
let srcHash;
|
|
20463
|
-
const
|
|
20464
|
-
|
|
20465
|
-
let
|
|
20466
|
-
let tailOffsetEnd = 0;
|
|
20190
|
+
const oversized = sessionBuildBytes(ref) > FULL_BUILD_MAX_BYTES;
|
|
20191
|
+
let headOffsetEnd = 0;
|
|
20192
|
+
let oversizedFindings = null;
|
|
20467
20193
|
let transcript = null;
|
|
20468
20194
|
let fullestContainer = null;
|
|
20469
20195
|
if (oversized) {
|
|
20470
20196
|
const backend = getBackendForId(ref.prefixedId);
|
|
20471
20197
|
if (!backend?.readFromOffset) return null;
|
|
20472
20198
|
try {
|
|
20473
|
-
|
|
20474
|
-
|
|
20475
|
-
|
|
20476
|
-
|
|
20199
|
+
let head = await backend.readFromOffset(ref.prefixedId, 0, SYNC_CHUNK_BYTES);
|
|
20200
|
+
for (let hops = 0; head.skippedBytes && hops < 8; hops++) {
|
|
20201
|
+
head = await backend.readFromOffset(ref.prefixedId, head.newOffset, SYNC_CHUNK_BYTES);
|
|
20202
|
+
}
|
|
20203
|
+
headOffsetEnd = head.newOffset;
|
|
20204
|
+
const messages = parseTailMessages(ref.toolId, head.text);
|
|
20477
20205
|
if (messages.length > 0) transcript = { messages, subagents: [] };
|
|
20478
20206
|
} catch {
|
|
20479
20207
|
}
|
|
20480
20208
|
includeRaw = false;
|
|
20481
|
-
|
|
20209
|
+
oversizedFindings = await scanTranscriptFindingsChunked(ref, opts.tenantRules);
|
|
20482
20210
|
} else {
|
|
20483
20211
|
try {
|
|
20484
20212
|
const backend = getBackendForId(ref.prefixedId) ?? getBackend("claude");
|
|
@@ -20539,7 +20267,6 @@ async function buildConversationSync(ref, mtime, opts = {}) {
|
|
|
20539
20267
|
);
|
|
20540
20268
|
let projectPath = ref.projectPath;
|
|
20541
20269
|
const meta = { messageCount: textMessages.length };
|
|
20542
|
-
if (oversized) meta.truncated = true;
|
|
20543
20270
|
if (textMessages.filter((m) => m.role === "user").length <= 1) meta.oneShot = true;
|
|
20544
20271
|
try {
|
|
20545
20272
|
const cwd = ref.fullPath ? readCwdFromJsonl(ref.fullPath) : "";
|
|
@@ -20653,21 +20380,7 @@ async function buildConversationSync(ref, mtime, opts = {}) {
|
|
|
20653
20380
|
}
|
|
20654
20381
|
scanMs = performance.now() - t0;
|
|
20655
20382
|
}
|
|
20656
|
-
if (
|
|
20657
|
-
const t0 = performance.now();
|
|
20658
|
-
try {
|
|
20659
|
-
for (const f of scanTextForFindings(tailText)) {
|
|
20660
|
-
findings.push({ session_id: ref.prefixedId, detector: "builtin", rule: f.rule, line: f.line, preview: f.preview });
|
|
20661
|
-
}
|
|
20662
|
-
if (opts.tenantRules && opts.tenantRules.length > 0) {
|
|
20663
|
-
for (const f of scanTenantRules(tailText, opts.tenantRules)) {
|
|
20664
|
-
findings.push({ session_id: ref.prefixedId, detector: f.detector, rule: f.rule, line: f.line, preview: f.preview });
|
|
20665
|
-
}
|
|
20666
|
-
}
|
|
20667
|
-
} catch {
|
|
20668
|
-
}
|
|
20669
|
-
scanMs = performance.now() - t0;
|
|
20670
|
-
}
|
|
20383
|
+
if (oversizedFindings) findings.push(...oversizedFindings);
|
|
20671
20384
|
const keptFindings = dropFuzzyFindings(findings, (f) => ({ detector: f.detector, rule: f.rule }));
|
|
20672
20385
|
findings.length = 0;
|
|
20673
20386
|
findings.push(...keptFindings);
|
|
@@ -20713,7 +20426,10 @@ async function buildConversationSync(ref, mtime, opts = {}) {
|
|
|
20713
20426
|
// (convBatch onFlush uses this, not a recomputed size) so the next
|
|
20714
20427
|
// append's base_offset matches exactly — no spurious continuity misses on
|
|
20715
20428
|
// an actively-growing session. undefined for non-append-only backends.
|
|
20716
|
-
from_offset: oversized ?
|
|
20429
|
+
from_offset: oversized ? headOffsetEnd : getBackendForId(ref.prefixedId)?.fileSize?.(ref.rawId),
|
|
20430
|
+
// The head of a chunked session. The server reports back when its shrink
|
|
20431
|
+
// guard keeps a fuller stored copy, so the client appends from there.
|
|
20432
|
+
...oversized ? { chunked: true } : {},
|
|
20717
20433
|
meta,
|
|
20718
20434
|
mtime
|
|
20719
20435
|
},
|
|
@@ -20735,12 +20451,30 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
|
|
|
20735
20451
|
const backend = getBackendForId(ref.prefixedId);
|
|
20736
20452
|
if (!backend?.readFromOffset) return null;
|
|
20737
20453
|
const mapPath = opts.mapPath ?? ((p) => p);
|
|
20738
|
-
const { text: tailText, newOffset } = await backend.readFromOffset(ref.prefixedId, fromOffset);
|
|
20739
|
-
if (
|
|
20740
|
-
const messages = parseTailMessages(ref.toolId, tailText);
|
|
20741
|
-
if (messages.length === 0) return null;
|
|
20454
|
+
const { text: tailText, newOffset, skippedBytes } = await backend.readFromOffset(ref.prefixedId, fromOffset, SYNC_CHUNK_BYTES);
|
|
20455
|
+
if (newOffset <= fromOffset) return null;
|
|
20456
|
+
const messages = skippedBytes ? [] : parseTailMessages(ref.toolId, tailText);
|
|
20742
20457
|
const textMessages = messages.filter((m) => m.role !== "summary" && m.content?.trim());
|
|
20743
|
-
|
|
20458
|
+
const hasContent = textMessages.length > 0 || messages.some((m) => m.toolCalls?.length);
|
|
20459
|
+
if (!hasContent) {
|
|
20460
|
+
return {
|
|
20461
|
+
conv: {
|
|
20462
|
+
session_id: ref.prefixedId,
|
|
20463
|
+
tool: ref.toolId,
|
|
20464
|
+
project_path: mapPath(ref.projectPath),
|
|
20465
|
+
append: true,
|
|
20466
|
+
base_offset: fromOffset,
|
|
20467
|
+
from_offset: newOffset,
|
|
20468
|
+
redacted_text: "",
|
|
20469
|
+
envelope: { v: TRANSCRIPT_VERSION, messages: [], subagents: [] },
|
|
20470
|
+
mtime: Math.floor(ref.mtime) || 0
|
|
20471
|
+
},
|
|
20472
|
+
redactions: 0,
|
|
20473
|
+
newOffset,
|
|
20474
|
+
findings: [],
|
|
20475
|
+
bytes: 0
|
|
20476
|
+
};
|
|
20477
|
+
}
|
|
20744
20478
|
const firstUser = textMessages.find((m) => m.role === "user");
|
|
20745
20479
|
if (isInternalToolPrompt(firstUser?.content)) return null;
|
|
20746
20480
|
const count = { redactions: 0 };
|
|
@@ -20783,7 +20517,8 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
|
|
|
20783
20517
|
},
|
|
20784
20518
|
redactions: count.redactions,
|
|
20785
20519
|
newOffset,
|
|
20786
|
-
findings
|
|
20520
|
+
findings,
|
|
20521
|
+
bytes: Buffer.byteLength(tailText)
|
|
20787
20522
|
};
|
|
20788
20523
|
}
|
|
20789
20524
|
function isSyncSkip(r) {
|
|
@@ -20813,7 +20548,7 @@ async function syncIncremental(opts = {}) {
|
|
|
20813
20548
|
lock.release();
|
|
20814
20549
|
}
|
|
20815
20550
|
}
|
|
20816
|
-
var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, SYNC_RULES_REFUSAL, hashPath, acceptedUploads, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES,
|
|
20551
|
+
var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, SYNC_RULES_REFUSAL, hashPath, acceptedUploads, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, DERIVED_MAX_BYTES, SYNC_CHUNK_BYTES, BATCH_SCAN_MAX_BYTES;
|
|
20817
20552
|
var init_sync_client = __esm({
|
|
20818
20553
|
"src/sync-client.ts"() {
|
|
20819
20554
|
"use strict";
|
|
@@ -20858,7 +20593,7 @@ var init_sync_client = __esm({
|
|
|
20858
20593
|
ownVersion = JSON.parse(readFileSync35(join42(dir, "../package.json"), "utf-8")).version || ownVersion;
|
|
20859
20594
|
} catch {
|
|
20860
20595
|
try {
|
|
20861
|
-
if (true) ownVersion = "0.
|
|
20596
|
+
if (true) ownVersion = "0.7.1";
|
|
20862
20597
|
} catch {
|
|
20863
20598
|
}
|
|
20864
20599
|
}
|
|
@@ -20884,8 +20619,9 @@ var init_sync_client = __esm({
|
|
|
20884
20619
|
capsOk = /* @__PURE__ */ new Map();
|
|
20885
20620
|
advertisedIngestConc = /* @__PURE__ */ new Map();
|
|
20886
20621
|
advertisedGzipBody = /* @__PURE__ */ new Set();
|
|
20887
|
-
FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "
|
|
20888
|
-
|
|
20622
|
+
FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "24", 10)) * 1024 * 1024;
|
|
20623
|
+
DERIVED_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_DERIVED_MAX_MB || "64", 10)) * 1024 * 1024;
|
|
20624
|
+
SYNC_CHUNK_BYTES = 8 * 1024 * 1024;
|
|
20889
20625
|
BATCH_SCAN_MAX_BYTES = Math.max(1, parseInt(process.env.CHAT_RECALL_BATCHSCAN_MAX_MB || "64", 10)) * 1024 * 1024;
|
|
20890
20626
|
}
|
|
20891
20627
|
});
|
|
@@ -21034,7 +20770,7 @@ var init_artifact_codec = __esm({
|
|
|
21034
20770
|
});
|
|
21035
20771
|
|
|
21036
20772
|
// ../engine/dist/core/toolkit-sync.js
|
|
21037
|
-
import { existsSync as
|
|
20773
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync11, readFileSync as readFileSync37, writeFileSync as writeFileSync10, cpSync } from "fs";
|
|
21038
20774
|
import { dirname as dirname20, join as join44, basename as basename22 } from "path";
|
|
21039
20775
|
import { homedir as homedir17 } from "os";
|
|
21040
20776
|
function rowFromMemoryItem(type, item) {
|
|
@@ -21068,11 +20804,11 @@ function skillsDirFor(tool) {
|
|
|
21068
20804
|
}
|
|
21069
20805
|
function copySkill(row, toTool) {
|
|
21070
20806
|
const skillDir = row.extra.skillDir || dirname20(row.filePath);
|
|
21071
|
-
if (!
|
|
20807
|
+
if (!existsSync42(skillDir))
|
|
21072
20808
|
return { ok: false, status: 404, error: `Source dir missing: ${skillDir}` };
|
|
21073
20809
|
const name = row.extra.skillName || basename22(skillDir);
|
|
21074
20810
|
const targetDir = join44(skillsDirFor(toTool), name);
|
|
21075
|
-
if (
|
|
20811
|
+
if (existsSync42(targetDir))
|
|
21076
20812
|
return { ok: false, status: 409, error: `Already exists: ${targetDir}` };
|
|
21077
20813
|
try {
|
|
21078
20814
|
mkdirSync11(dirname20(targetDir), { recursive: true });
|
|
@@ -21104,7 +20840,7 @@ function readMcpEntry(tool, name) {
|
|
|
21104
20840
|
return entry;
|
|
21105
20841
|
}
|
|
21106
20842
|
for (const t of tries) {
|
|
21107
|
-
if (!
|
|
20843
|
+
if (!existsSync42(t.path))
|
|
21108
20844
|
continue;
|
|
21109
20845
|
try {
|
|
21110
20846
|
const cfg = JSON.parse(readFileSync37(t.path, "utf-8"));
|
|
@@ -21117,7 +20853,7 @@ function readMcpEntry(tool, name) {
|
|
|
21117
20853
|
return null;
|
|
21118
20854
|
}
|
|
21119
20855
|
function readCodexMcpEntry(path, target) {
|
|
21120
|
-
if (!
|
|
20856
|
+
if (!existsSync42(path))
|
|
21121
20857
|
return null;
|
|
21122
20858
|
let content;
|
|
21123
20859
|
try {
|
|
@@ -21171,7 +20907,7 @@ function readCodexMcpEntry(path, target) {
|
|
|
21171
20907
|
}
|
|
21172
20908
|
function writeCodexMcpEntry(path, name, entry) {
|
|
21173
20909
|
let content = "";
|
|
21174
|
-
if (
|
|
20910
|
+
if (existsSync42(path))
|
|
21175
20911
|
content = readFileSync37(path, "utf-8");
|
|
21176
20912
|
const header = `[mcp_servers.${name}]`;
|
|
21177
20913
|
if (content.includes(header))
|
|
@@ -21229,7 +20965,7 @@ function writeMcpEntry(toTool, name, entry) {
|
|
|
21229
20965
|
key2 = "mcp";
|
|
21230
20966
|
}
|
|
21231
20967
|
let cfg = {};
|
|
21232
|
-
if (
|
|
20968
|
+
if (existsSync42(path)) {
|
|
21233
20969
|
try {
|
|
21234
20970
|
cfg = JSON.parse(readFileSync37(path, "utf-8"));
|
|
21235
20971
|
} catch {
|
|
@@ -21293,7 +21029,7 @@ function copyCommandOrAgent(type, row, toTool) {
|
|
|
21293
21029
|
return { ok: false, status: 404, error: `Source unreadable: ${e instanceof Error ? e.message : "failed"}` };
|
|
21294
21030
|
}
|
|
21295
21031
|
const out = emit(type, art, toTool);
|
|
21296
|
-
if (
|
|
21032
|
+
if (existsSync42(out.path))
|
|
21297
21033
|
return { ok: false, status: 409, error: `Already exists: ${out.path}` };
|
|
21298
21034
|
try {
|
|
21299
21035
|
mkdirSync11(dirname20(out.path), { recursive: true });
|
|
@@ -21313,7 +21049,7 @@ function copyInstructions(row, toTool) {
|
|
|
21313
21049
|
return { ok: false, status: 404, error: `Source unreadable: ${e instanceof Error ? e.message : "failed"}` };
|
|
21314
21050
|
}
|
|
21315
21051
|
const out = emit("instructions", art, toTool, row.projectPath || void 0);
|
|
21316
|
-
if (
|
|
21052
|
+
if (existsSync42(out.path))
|
|
21317
21053
|
return { ok: false, status: 409, error: `Already exists: ${out.path}` };
|
|
21318
21054
|
try {
|
|
21319
21055
|
mkdirSync11(dirname20(out.path), { recursive: true });
|
|
@@ -21456,7 +21192,7 @@ var init_toolkit_sync = __esm({
|
|
|
21456
21192
|
});
|
|
21457
21193
|
|
|
21458
21194
|
// ../engine/dist/core/which.js
|
|
21459
|
-
import { existsSync as
|
|
21195
|
+
import { existsSync as existsSync44, statSync as statSync39, accessSync, constants } from "node:fs";
|
|
21460
21196
|
import { join as join46, isAbsolute as isAbsolute2, delimiter as delimiter2, sep as sep3 } from "node:path";
|
|
21461
21197
|
function executable(p) {
|
|
21462
21198
|
try {
|
|
@@ -21497,7 +21233,7 @@ function resolveOnPath(name) {
|
|
|
21497
21233
|
continue;
|
|
21498
21234
|
for (const c of candidates(name)) {
|
|
21499
21235
|
const full = join46(dir, c);
|
|
21500
|
-
if (
|
|
21236
|
+
if (existsSync44(full) && executable(full))
|
|
21501
21237
|
return full;
|
|
21502
21238
|
}
|
|
21503
21239
|
}
|
|
@@ -21522,7 +21258,7 @@ __export(toolkit_pull_exports, {
|
|
|
21522
21258
|
planPull: () => planPull,
|
|
21523
21259
|
portableCommand: () => portableCommand
|
|
21524
21260
|
});
|
|
21525
|
-
import { existsSync as
|
|
21261
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync13, statSync as statSync40, writeFileSync as writeFileSync12 } from "node:fs";
|
|
21526
21262
|
import { dirname as dirname22, join as join47, basename as basename23, isAbsolute as isAbsolute3 } from "node:path";
|
|
21527
21263
|
function isExecutableFile(p) {
|
|
21528
21264
|
try {
|
|
@@ -21675,7 +21411,7 @@ function executePull(rows, opts = {}) {
|
|
|
21675
21411
|
for (const tool of tools) {
|
|
21676
21412
|
const dir = join47(skillsDirFor(tool), name);
|
|
21677
21413
|
const file = join47(dir, "SKILL.md");
|
|
21678
|
-
if (
|
|
21414
|
+
if (existsSync45(file)) {
|
|
21679
21415
|
outcomes.push({ type: "skill", name, tool, status: "present" });
|
|
21680
21416
|
continue;
|
|
21681
21417
|
}
|
|
@@ -21720,7 +21456,7 @@ function executePull(rows, opts = {}) {
|
|
|
21720
21456
|
outcomes.push({ type, name, tool, status: "failed", reason: e instanceof Error ? e.message : "emit failed" });
|
|
21721
21457
|
continue;
|
|
21722
21458
|
}
|
|
21723
|
-
if (
|
|
21459
|
+
if (existsSync45(out.path)) {
|
|
21724
21460
|
outcomes.push({ type, name, tool, status: "present" });
|
|
21725
21461
|
continue;
|
|
21726
21462
|
}
|
|
@@ -21896,6 +21632,9 @@ async function repairSession(id, opts = {}) {
|
|
|
21896
21632
|
note: "archive count is a lower bound; the rebuild also unions the on-disk tail"
|
|
21897
21633
|
};
|
|
21898
21634
|
}
|
|
21635
|
+
if (!opts.force && endpoints.every((ep) => (currentOnServer.get(ep.url) ?? 0) >= fullest.messages)) {
|
|
21636
|
+
return { sessionId: id, status: "already-full", fullestMessages: fullest.messages, fullestSource: fullest.source, pushed: [] };
|
|
21637
|
+
}
|
|
21899
21638
|
seedShadow(rawId, fullest.container);
|
|
21900
21639
|
const located = backend.findSession(rawId);
|
|
21901
21640
|
const projectPath = located?.projectPath || cwdFromContainer(fullest.container);
|
|
@@ -21938,7 +21677,7 @@ async function repairSession(id, opts = {}) {
|
|
|
21938
21677
|
try {
|
|
21939
21678
|
const res = await fetchWithTimeout(`${ep.url}/api/sync`, { method: "POST", headers, body: JSON.stringify({ conversations: [built.conv] }) }, 6e4);
|
|
21940
21679
|
if (!res.ok) {
|
|
21941
|
-
pushed.push({ server: ep.url, before, after: before });
|
|
21680
|
+
pushed.push({ server: ep.url, before, after: before, error: `HTTP ${res.status}` });
|
|
21942
21681
|
log6(`push to ${ep.url} FAILED: HTTP ${res.status}`);
|
|
21943
21682
|
continue;
|
|
21944
21683
|
}
|
|
@@ -21947,10 +21686,21 @@ async function repairSession(id, opts = {}) {
|
|
|
21947
21686
|
pushed.push({ server: ep.url, before, after: afterN });
|
|
21948
21687
|
log6(`pushed to ${ep.url}: ${before} \u2192 ${afterN} msg(s)`);
|
|
21949
21688
|
} catch (e) {
|
|
21950
|
-
|
|
21951
|
-
|
|
21689
|
+
const error = e instanceof Error ? e.message : String(e);
|
|
21690
|
+
pushed.push({ server: ep.url, before, after: before, error });
|
|
21691
|
+
log6(`push to ${ep.url} errored: ${error}`);
|
|
21952
21692
|
}
|
|
21953
21693
|
}
|
|
21694
|
+
if (pushed.every((p) => p.error)) {
|
|
21695
|
+
return {
|
|
21696
|
+
sessionId: id,
|
|
21697
|
+
status: "error",
|
|
21698
|
+
fullestMessages: builtCount,
|
|
21699
|
+
fullestSource: fullest.source,
|
|
21700
|
+
pushed,
|
|
21701
|
+
note: `every push failed: ${pushed.map((p) => `${p.server}: ${p.error}`).join("; ")}`
|
|
21702
|
+
};
|
|
21703
|
+
}
|
|
21954
21704
|
return { sessionId: id, status: "repaired", fullestMessages: builtCount, fullestSource: fullest.source, pushed };
|
|
21955
21705
|
}
|
|
21956
21706
|
async function repairSessions(ids, opts = {}) {
|
|
@@ -22160,7 +21910,7 @@ var init_code_intel = __esm({
|
|
|
22160
21910
|
});
|
|
22161
21911
|
|
|
22162
21912
|
// ../engine/dist/core/companions.js
|
|
22163
|
-
import { existsSync as
|
|
21913
|
+
import { existsSync as existsSync48, mkdirSync as mkdirSync16, statSync as statSync43, chmodSync as chmodSync4, unlinkSync as unlinkSync4, renameSync as renameSync5, createWriteStream, readFileSync as readFileSync42, writeFileSync as writeFileSync15 } from "node:fs";
|
|
22164
21914
|
import { homedir as homedir19 } from "node:os";
|
|
22165
21915
|
import { join as join51 } from "node:path";
|
|
22166
21916
|
import { execSync as execSync3 } from "node:child_process";
|
|
@@ -22283,7 +22033,7 @@ Refusing to install. The release may have been re-uploaded, or something is serv
|
|
|
22283
22033
|
renameSync5(tmpPath, CODEINDEX_BIN_PATH);
|
|
22284
22034
|
} catch (err) {
|
|
22285
22035
|
try {
|
|
22286
|
-
if (
|
|
22036
|
+
if (existsSync48(tmpPath))
|
|
22287
22037
|
unlinkSync4(tmpPath);
|
|
22288
22038
|
} catch {
|
|
22289
22039
|
}
|
|
@@ -23367,7 +23117,7 @@ __export(service_installer_exports, {
|
|
|
23367
23117
|
uninstallService: () => uninstallService
|
|
23368
23118
|
});
|
|
23369
23119
|
import { execSync as execSync5 } from "child_process";
|
|
23370
|
-
import { existsSync as
|
|
23120
|
+
import { existsSync as existsSync49, mkdirSync as mkdirSync17, readFileSync as readFileSync44, rmSync as rmSync7, writeFileSync as writeFileSync16 } from "fs";
|
|
23371
23121
|
import { homedir as homedir20 } from "os";
|
|
23372
23122
|
import { join as join53 } from "path";
|
|
23373
23123
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
@@ -23394,7 +23144,7 @@ function isServiceRunning() {
|
|
|
23394
23144
|
return /com\.chat-recall\.watch/.test(tryCmd("launchctl list 2>/dev/null"));
|
|
23395
23145
|
}
|
|
23396
23146
|
if (process.platform === "linux") {
|
|
23397
|
-
return
|
|
23147
|
+
return /^active$/m.test(tryCmd("systemctl --user is-active chat-recall-watch.service 2>/dev/null"));
|
|
23398
23148
|
}
|
|
23399
23149
|
return false;
|
|
23400
23150
|
}
|
|
@@ -23403,7 +23153,7 @@ function refreshServiceDefinition() {
|
|
|
23403
23153
|
try {
|
|
23404
23154
|
const { watchJs, node, logFile } = resolveDaemonPaths();
|
|
23405
23155
|
const unitPath = join53(homedir20(), ".config", "systemd", "user", "chat-recall-watch.service");
|
|
23406
|
-
if (!
|
|
23156
|
+
if (!existsSync49(unitPath)) return false;
|
|
23407
23157
|
const current = readFileSync44(unitPath, "utf8");
|
|
23408
23158
|
if (!current.includes(watchJs)) return false;
|
|
23409
23159
|
const wanted = renderSystemdUnit(watchJs, node, logFile);
|
|
@@ -23470,7 +23220,12 @@ function renderSystemdUnit(watchJs, node, logFile) {
|
|
|
23470
23220
|
// sawtooth. 1536MB keeps GC honest (768 OOMed on the largest subagent-fanout session) with plenty of headroom for the
|
|
23471
23221
|
// largest transcripts.
|
|
23472
23222
|
`ExecStart=${node} --max-old-space-size=1536 ${watchJs}`,
|
|
23473
|
-
|
|
23223
|
+
// ALWAYS: the collector never ends its own work, so any exit systemd did
|
|
23224
|
+
// not ask for is a fault. Under on-failure a clean exit left it dead: on
|
|
23225
|
+
// 2026-09-16 the unit went "inactive (dead) … status=0/SUCCESS" and stayed
|
|
23226
|
+
// down for 8 days, so nothing synced and the auto-update tick, which runs
|
|
23227
|
+
// inside the daemon, never ran. `systemctl stop` still stops it.
|
|
23228
|
+
"Restart=always",
|
|
23474
23229
|
"RestartSec=10",
|
|
23475
23230
|
"Nice=10",
|
|
23476
23231
|
// NEVER STOP RETRYING. systemd's default start limiter gives up after a
|
|
@@ -23566,7 +23321,7 @@ function uninstallService() {
|
|
|
23566
23321
|
} catch {
|
|
23567
23322
|
}
|
|
23568
23323
|
const unitPath = join53(homedir20(), ".config", "systemd", "user", "chat-recall-watch.service");
|
|
23569
|
-
if (
|
|
23324
|
+
if (existsSync49(unitPath)) {
|
|
23570
23325
|
rmSync7(unitPath, { force: true });
|
|
23571
23326
|
removed = true;
|
|
23572
23327
|
}
|
|
@@ -23583,7 +23338,7 @@ function uninstallService() {
|
|
|
23583
23338
|
} catch {
|
|
23584
23339
|
}
|
|
23585
23340
|
let removed = false;
|
|
23586
|
-
if (
|
|
23341
|
+
if (existsSync49(plistPath)) {
|
|
23587
23342
|
rmSync7(plistPath, { force: true });
|
|
23588
23343
|
removed = true;
|
|
23589
23344
|
}
|
|
@@ -23640,12 +23395,12 @@ init_codex2();
|
|
|
23640
23395
|
init_cursor();
|
|
23641
23396
|
init_opencode();
|
|
23642
23397
|
init_agy();
|
|
23643
|
-
import { existsSync as
|
|
23398
|
+
import { existsSync as existsSync46, readFileSync as readFileSync39, writeFileSync as writeFileSync13, appendFileSync, mkdirSync as mkdirSync14 } from "node:fs";
|
|
23644
23399
|
import { join as join48 } from "node:path";
|
|
23645
23400
|
|
|
23646
23401
|
// src/project-tasks.ts
|
|
23647
23402
|
init_http();
|
|
23648
|
-
import { existsSync as
|
|
23403
|
+
import { existsSync as existsSync43, readFileSync as readFileSync38, writeFileSync as writeFileSync11, mkdirSync as mkdirSync12, statSync as statSync38 } from "node:fs";
|
|
23649
23404
|
import { join as join45, dirname as dirname21 } from "node:path";
|
|
23650
23405
|
import { homedir as homedir18 } from "node:os";
|
|
23651
23406
|
|
|
@@ -23753,7 +23508,7 @@ async function pushProjectTaskStatuses(base2, authHeaders, opts = {}) {
|
|
|
23753
23508
|
}
|
|
23754
23509
|
for (const project of projects) {
|
|
23755
23510
|
const file = join45(project, fk.filename);
|
|
23756
|
-
if (!
|
|
23511
|
+
if (!existsSync43(file)) continue;
|
|
23757
23512
|
let mtime;
|
|
23758
23513
|
try {
|
|
23759
23514
|
mtime = statSync38(file).mtimeMs;
|
|
@@ -23814,7 +23569,7 @@ function instructionTargets(rootPath, isGlobal) {
|
|
|
23814
23569
|
const tools = installed.length ? installed : [INSTRUCTION_TOOLS[0]];
|
|
23815
23570
|
if (isGlobal) return [...new Set(tools.map((t) => instructionsPath(t.id)))];
|
|
23816
23571
|
if (!rootPath) return [];
|
|
23817
|
-
const present = INSTRUCTION_FILENAMES.map((f) => join48(rootPath, f)).filter((p) =>
|
|
23572
|
+
const present = INSTRUCTION_FILENAMES.map((f) => join48(rootPath, f)).filter((p) => existsSync46(p));
|
|
23818
23573
|
if (present.length) return present;
|
|
23819
23574
|
return [...new Set(tools.map((t) => join48(rootPath, instructionsFilename(t.id))))];
|
|
23820
23575
|
}
|
|
@@ -23842,7 +23597,7 @@ function applyCodeRecommendation(intent) {
|
|
|
23842
23597
|
mkdirSync14(dir, { recursive: true });
|
|
23843
23598
|
} catch {
|
|
23844
23599
|
}
|
|
23845
|
-
const existing =
|
|
23600
|
+
const existing = existsSync46(file) ? readFileSync39(file, "utf8") : "";
|
|
23846
23601
|
if (existing.includes(text)) {
|
|
23847
23602
|
skipped.push(file);
|
|
23848
23603
|
continue;
|
|
@@ -23944,7 +23699,7 @@ try {
|
|
|
23944
23699
|
ownVersion2 = JSON.parse(readFileSync39(join48(dir, "../package.json"), "utf-8")).version || ownVersion2;
|
|
23945
23700
|
} catch {
|
|
23946
23701
|
try {
|
|
23947
|
-
if (true) ownVersion2 = "0.
|
|
23702
|
+
if (true) ownVersion2 = "0.7.1";
|
|
23948
23703
|
} catch {
|
|
23949
23704
|
}
|
|
23950
23705
|
}
|
|
@@ -24248,8 +24003,8 @@ function rotateLogIfLarge(logPath, maxBytes = LOG_MAX_BYTES) {
|
|
|
24248
24003
|
|
|
24249
24004
|
// src/collector-migrate.ts
|
|
24250
24005
|
init_http();
|
|
24251
|
-
import { existsSync as
|
|
24252
|
-
function selectProjectsToReindex(projects, current, existsFn =
|
|
24006
|
+
import { existsSync as existsSync47 } from "node:fs";
|
|
24007
|
+
function selectProjectsToReindex(projects, current, existsFn = existsSync47) {
|
|
24253
24008
|
const reindex = [];
|
|
24254
24009
|
const missing = [];
|
|
24255
24010
|
for (const p of projects) {
|
|
@@ -24291,8 +24046,8 @@ async function runCollectorMigration(opts) {
|
|
|
24291
24046
|
// auto-indexer/indexer.ts
|
|
24292
24047
|
init_sync_ledger();
|
|
24293
24048
|
init_collector_health();
|
|
24294
|
-
import { existsSync as
|
|
24295
|
-
var CLI_VERSION3 = true ? "0.
|
|
24049
|
+
import { existsSync as existsSync50, readFileSync as readFileSync45, statSync as statSync44 } from "fs";
|
|
24050
|
+
var CLI_VERSION3 = true ? "0.7.1" : "0.0.0";
|
|
24296
24051
|
var DEBOUNCE_MS = 5e3;
|
|
24297
24052
|
var CLAUDE_DIR = claudeBackend.projectsDir();
|
|
24298
24053
|
var PLANS_DIR = claudeBackend.plansDir();
|
|
@@ -24697,7 +24452,7 @@ function discoverWorkspaces() {
|
|
|
24697
24452
|
const isExcluded = (p) => excluded.some((x) => p.includes(x));
|
|
24698
24453
|
const candidates2 = [...newest.entries()].filter(([p]) => !isExcluded(p)).filter(([p]) => !isPersonalPath(p, includeProjects)).filter(([p]) => {
|
|
24699
24454
|
try {
|
|
24700
|
-
return
|
|
24455
|
+
return existsSync50(p);
|
|
24701
24456
|
} catch {
|
|
24702
24457
|
return false;
|
|
24703
24458
|
}
|
|
@@ -24729,7 +24484,7 @@ function isCodeWorkspace(p) {
|
|
|
24729
24484
|
}
|
|
24730
24485
|
return WORKSPACE_MARKERS.some((m) => {
|
|
24731
24486
|
try {
|
|
24732
|
-
return
|
|
24487
|
+
return existsSync50(join54(p, m));
|
|
24733
24488
|
} catch {
|
|
24734
24489
|
return false;
|
|
24735
24490
|
}
|