@wrongstack/core 0.119.1 → 0.148.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-bridge-PRAbEX-9.d.ts → agent-bridge-r9y6gdn4.d.ts} +1 -1
- package/dist/{agent-subagent-runner-DXWImnNV.d.ts → agent-subagent-runner-1GeQE_L0.d.ts} +7 -7
- package/dist/{brain-NZZqL8sn.d.ts → brain-Cp_3GIS2.d.ts} +14 -3
- package/dist/{compactor-C3stu664.d.ts → compactor-BueGt7LG.d.ts} +9 -1
- package/dist/{config-DZV9VK9n.d.ts → config-BaVThgnT.d.ts} +1 -1
- package/dist/{context-hvXINa6a.d.ts → context-C7G_MtLV.d.ts} +7 -0
- package/dist/coordination/index.d.ts +12 -12
- package/dist/coordination/index.js +35 -21
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +24 -24
- package/dist/defaults/index.js +869 -788
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +14 -14
- package/dist/execution/index.js +454 -321
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/{goal-preamble-COpt55W8.d.ts → goal-preamble-CYJLg0wk.d.ts} +55 -17
- package/dist/{index-Cj3le7TI.d.ts → index-BZdezm3g.d.ts} +8 -8
- package/dist/{index-4VVHuTZF.d.ts → index-CPweVoFM.d.ts} +5 -5
- package/dist/index.d.ts +48 -48
- package/dist/index.js +612 -513
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +64 -19
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js.map +1 -1
- package/dist/{llm-selector-Bmx4PsVM.d.ts → llm-selector-CP72f1lC.d.ts} +2 -2
- package/dist/{mcp-servers-CofNHOqn.d.ts → mcp-servers-Bl5LTvQg.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +53 -20
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-BzlsjKcy.d.ts → models-registry-D90K9UnM.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-CmDmUgq2.d.ts → multi-agent-coordinator-QWEzJDlm.d.ts} +1 -1
- package/dist/{null-fleet-bus-CyH2YqP4.d.ts → null-fleet-bus-BUyfqh23.d.ts} +13 -10
- package/dist/observability/index.d.ts +2 -2
- package/dist/{parallel-eternal-engine-uxVm4y4V.d.ts → parallel-eternal-engine-C75QuhAI.d.ts} +33 -11
- package/dist/{path-resolver-gfoGxpdd.d.ts → path-resolver-DRjQBkoO.d.ts} +3 -3
- package/dist/{permission-DhLrgA30.d.ts → permission-B7nKnEvQ.d.ts} +1 -1
- package/dist/{permission-policy-DGOvne7i.d.ts → permission-policy-8-6zBmfA.d.ts} +2 -13
- package/dist/{pipeline-CqF7uju2.d.ts → pipeline-BG7UgbDc.d.ts} +2 -2
- package/dist/{plan-templates-DoKhzenv.d.ts → plan-templates-CkKNPU3I.d.ts} +5 -5
- package/dist/{provider-runner-BPlI5sPZ.d.ts → provider-runner-BNpuIyOL.d.ts} +3 -3
- package/dist/{retry-policy-B4Q-FZgE.d.ts → retry-policy-rutAfVeR.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +10 -1
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-B7GnL6o9.d.ts → secret-vault-BTcC_T5v.d.ts} +3 -2
- package/dist/security/index.d.ts +5 -7
- package/dist/security/index.js +5 -36
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-BakeMnWR.d.ts → selector-4vDFZKt3.d.ts} +1 -1
- package/dist/{session-event-bridge-DE5Oekwx.d.ts → session-event-bridge-DWlvglC2.d.ts} +2 -2
- package/dist/{session-reader-DFC81-4p.d.ts → session-reader-BAtCxdaw.d.ts} +1 -1
- package/dist/storage/index.d.ts +13 -13
- package/dist/storage/index.js +100 -103
- package/dist/storage/index.js.map +1 -1
- package/dist/types/index.d.ts +18 -18
- package/dist/types/index.js +276 -103
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +37 -13
- package/dist/utils/index.js +63 -35
- package/dist/utils/index.js.map +1 -1
- package/package.json +2 -2
- package/skills/tech-stack/SKILL.md +253 -97
package/dist/defaults/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as crypto2 from 'crypto';
|
|
2
|
-
import { randomBytes,
|
|
2
|
+
import { randomBytes, createCipheriv, createDecipheriv, randomUUID, createHash } from 'crypto';
|
|
3
3
|
import * as fsp from 'fs/promises';
|
|
4
4
|
import * as path11 from 'path';
|
|
5
5
|
import { isAbsolute, resolve } from 'path';
|
|
6
|
-
import * as
|
|
6
|
+
import * as fs4 from 'fs';
|
|
7
7
|
import * as os from 'os';
|
|
8
8
|
import { hostname } from 'os';
|
|
9
9
|
import { execFile } from 'child_process';
|
|
@@ -218,7 +218,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
218
218
|
this.stderr = opts.stderr !== false;
|
|
219
219
|
if (this.file) {
|
|
220
220
|
try {
|
|
221
|
-
|
|
221
|
+
fs4.mkdirSync(path11.dirname(this.file), { recursive: true });
|
|
222
222
|
} catch {
|
|
223
223
|
}
|
|
224
224
|
}
|
|
@@ -258,7 +258,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
258
258
|
}
|
|
259
259
|
if (this.file) {
|
|
260
260
|
try {
|
|
261
|
-
|
|
261
|
+
fs4.appendFileSync(this.file, `${JSON.stringify(entry)}
|
|
262
262
|
`);
|
|
263
263
|
} catch {
|
|
264
264
|
}
|
|
@@ -452,8 +452,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
452
452
|
onClose: (s) => this.appendToIndex(s)
|
|
453
453
|
});
|
|
454
454
|
} catch (err) {
|
|
455
|
-
await handle.close().catch(() => {
|
|
456
|
-
});
|
|
455
|
+
await handle.close().catch((e) => console.warn(`[session-store] handle.close() failed: ${e}`));
|
|
457
456
|
throw err;
|
|
458
457
|
}
|
|
459
458
|
}
|
|
@@ -484,8 +483,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
484
483
|
);
|
|
485
484
|
return { writer, data };
|
|
486
485
|
} catch (err) {
|
|
487
|
-
await handle.close().catch(() => {
|
|
488
|
-
});
|
|
486
|
+
await handle.close().catch((e) => console.warn(`[session-store] handle.close() failed: ${e}`));
|
|
489
487
|
throw err;
|
|
490
488
|
}
|
|
491
489
|
}
|
|
@@ -674,15 +672,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
674
672
|
* sidecars, and the session directory (fleet.json, shared/, subagents/).
|
|
675
673
|
*/
|
|
676
674
|
async deleteSession(id) {
|
|
677
|
-
await fsp.unlink(this.sessionPath(id, ".jsonl")).catch(() =>
|
|
678
|
-
await fsp.unlink(this.sessionPath(id, ".summary.json")).catch(() =>
|
|
675
|
+
await fsp.unlink(this.sessionPath(id, ".jsonl")).catch((err) => console.warn(`[session-store] delete .jsonl failed: ${err}`));
|
|
676
|
+
await fsp.unlink(this.sessionPath(id, ".summary.json")).catch((err) => console.warn(`[session-store] delete .summary.json failed: ${err}`));
|
|
679
677
|
const shardDir = path11.dirname(path11.join(this.dir, id));
|
|
680
678
|
const base = path11.basename(id);
|
|
681
679
|
for (const ext of [".plan.json", ".todos.json"]) {
|
|
682
|
-
await fsp.unlink(path11.join(shardDir, `${base}${ext}`)).catch(() =>
|
|
680
|
+
await fsp.unlink(path11.join(shardDir, `${base}${ext}`)).catch((err) => console.warn(`[session-store] delete ${ext} failed: ${err}`));
|
|
683
681
|
}
|
|
684
682
|
const sessDir = path11.join(shardDir, base);
|
|
685
|
-
await fsp.rm(sessDir, { recursive: true, force: true }).catch(() =>
|
|
683
|
+
await fsp.rm(sessDir, { recursive: true, force: true }).catch((err) => console.warn(`[session-store] delete session dir failed: ${err}`));
|
|
686
684
|
await this.writeTombstone(id);
|
|
687
685
|
}
|
|
688
686
|
async delete(id) {
|
|
@@ -759,7 +757,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
759
757
|
let toolErrorCount = 0;
|
|
760
758
|
let fileChangeCount = 0;
|
|
761
759
|
const toolBreakdown = {};
|
|
762
|
-
let outcome
|
|
760
|
+
let outcome;
|
|
763
761
|
const lastEvent = data.events[data.events.length - 1];
|
|
764
762
|
for (const e of data.events) {
|
|
765
763
|
if (e.type === "in_flight_start") iterationCount++;
|
|
@@ -1985,20 +1983,101 @@ function deepFreeze(obj) {
|
|
|
1985
1983
|
return Object.freeze(obj);
|
|
1986
1984
|
}
|
|
1987
1985
|
|
|
1988
|
-
// src/
|
|
1989
|
-
|
|
1986
|
+
// src/types/secret-vault.ts
|
|
1987
|
+
var ENCRYPTED_PREFIX = "enc:v1:";
|
|
1988
|
+
|
|
1989
|
+
// src/security/secret-vault.ts
|
|
1990
|
+
init_atomic_write();
|
|
1991
|
+
var KEY_BYTES = 32;
|
|
1992
|
+
var IV_BYTES = 12;
|
|
1993
|
+
var TAG_BYTES = 16;
|
|
1994
|
+
var ALGO = "aes-256-gcm";
|
|
1995
|
+
var DefaultSecretVault = class {
|
|
1996
|
+
keyFile;
|
|
1997
|
+
key;
|
|
1998
|
+
constructor(opts) {
|
|
1999
|
+
this.keyFile = opts.keyFile;
|
|
2000
|
+
}
|
|
2001
|
+
isEncrypted(value) {
|
|
2002
|
+
return typeof value === "string" && value.startsWith(ENCRYPTED_PREFIX);
|
|
2003
|
+
}
|
|
2004
|
+
encrypt(plaintext) {
|
|
2005
|
+
if (this.isEncrypted(plaintext)) return plaintext;
|
|
2006
|
+
const key = this.loadOrCreateKey();
|
|
2007
|
+
const iv = randomBytes(IV_BYTES);
|
|
2008
|
+
const cipher = createCipheriv(ALGO, key, iv);
|
|
2009
|
+
const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
2010
|
+
const tag = cipher.getAuthTag();
|
|
2011
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
|
|
2012
|
+
}
|
|
2013
|
+
decrypt(value) {
|
|
2014
|
+
if (!this.isEncrypted(value)) return value;
|
|
2015
|
+
const rest = value.slice(ENCRYPTED_PREFIX.length);
|
|
2016
|
+
const parts = rest.split(":");
|
|
2017
|
+
if (parts.length !== 3) {
|
|
2018
|
+
throw new Error("SecretVault: malformed encrypted value");
|
|
2019
|
+
}
|
|
2020
|
+
const [ivB64, tagB64, ctB64] = parts;
|
|
2021
|
+
const iv = Buffer.from(ivB64, "base64");
|
|
2022
|
+
const tag = Buffer.from(tagB64, "base64");
|
|
2023
|
+
const ct = Buffer.from(ctB64, "base64");
|
|
2024
|
+
if (iv.length !== IV_BYTES) throw new Error("SecretVault: bad IV length");
|
|
2025
|
+
if (tag.length !== TAG_BYTES) throw new Error("SecretVault: bad tag length");
|
|
2026
|
+
const key = this.loadOrCreateKey();
|
|
2027
|
+
const decipher = createDecipheriv(ALGO, key, iv);
|
|
2028
|
+
decipher.setAuthTag(tag);
|
|
2029
|
+
const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
2030
|
+
return pt.toString("utf8");
|
|
2031
|
+
}
|
|
2032
|
+
loadOrCreateKey() {
|
|
2033
|
+
if (this.key) return this.key;
|
|
2034
|
+
try {
|
|
2035
|
+
const buf = fs4.readFileSync(this.keyFile);
|
|
2036
|
+
if (buf.length !== KEY_BYTES) {
|
|
2037
|
+
throw new Error(
|
|
2038
|
+
`SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES}). Remove it manually to generate a new key.`
|
|
2039
|
+
);
|
|
2040
|
+
}
|
|
2041
|
+
this.key = buf;
|
|
2042
|
+
return this.key;
|
|
2043
|
+
} catch (err) {
|
|
2044
|
+
if (err.code !== "ENOENT") throw err;
|
|
2045
|
+
}
|
|
2046
|
+
fs4.mkdirSync(path11.dirname(this.keyFile), { recursive: true });
|
|
2047
|
+
const key = randomBytes(KEY_BYTES);
|
|
2048
|
+
try {
|
|
2049
|
+
fs4.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
|
|
2050
|
+
} catch (err) {
|
|
2051
|
+
if (err.code !== "EEXIST") throw err;
|
|
2052
|
+
const buf = fs4.readFileSync(this.keyFile);
|
|
2053
|
+
if (buf.length !== KEY_BYTES) {
|
|
2054
|
+
throw new Error(
|
|
2055
|
+
`SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES}). Remove it manually to generate a new key.`
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
this.key = buf;
|
|
2059
|
+
return this.key;
|
|
2060
|
+
}
|
|
2061
|
+
this.key = key;
|
|
2062
|
+
return key;
|
|
2063
|
+
}
|
|
2064
|
+
};
|
|
2065
|
+
function decryptConfigSecrets(cfg, vault, opts) {
|
|
2066
|
+
const warn = opts?.warn ?? ((msg) => console.warn(msg));
|
|
1990
2067
|
return walk(cfg, vault, (v, key) => {
|
|
1991
2068
|
try {
|
|
1992
2069
|
return vault.decrypt(v);
|
|
1993
2070
|
} catch (err) {
|
|
1994
|
-
|
|
1995
|
-
`[secret-vault] Failed to decrypt "${key}"
|
|
1996
|
-
err instanceof Error ? err.message : err
|
|
2071
|
+
warn(
|
|
2072
|
+
`[secret-vault] Failed to decrypt "${key}": ${err instanceof Error ? err.message : err}`
|
|
1997
2073
|
);
|
|
1998
2074
|
return "";
|
|
1999
2075
|
}
|
|
2000
2076
|
});
|
|
2001
2077
|
}
|
|
2078
|
+
function encryptConfigSecrets(cfg, vault, _opts) {
|
|
2079
|
+
return walk(cfg, vault, (v) => vault.encrypt(v));
|
|
2080
|
+
}
|
|
2002
2081
|
function walk(node, vault, transform) {
|
|
2003
2082
|
if (node === null || node === void 0) return node;
|
|
2004
2083
|
if (typeof node !== "object") return node;
|
|
@@ -2024,6 +2103,114 @@ function isSecretField(name) {
|
|
|
2024
2103
|
if (NON_SECRET_OVERRIDES.has(lc)) return false;
|
|
2025
2104
|
return SECRET_KEY_PATTERN.test(lc);
|
|
2026
2105
|
}
|
|
2106
|
+
async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
2107
|
+
let current = {};
|
|
2108
|
+
try {
|
|
2109
|
+
const raw = await fsp.readFile(configPath, "utf8");
|
|
2110
|
+
current = JSON.parse(raw);
|
|
2111
|
+
} catch {
|
|
2112
|
+
}
|
|
2113
|
+
const merged = deepMerge(current, patch ?? {});
|
|
2114
|
+
const encrypted = encryptConfigSecrets(merged, vault);
|
|
2115
|
+
await fsp.mkdir(path11.dirname(configPath), { recursive: true });
|
|
2116
|
+
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
2117
|
+
await restrictFilePermissions(configPath);
|
|
2118
|
+
}
|
|
2119
|
+
async function migratePlaintextSecrets(configPath, vault) {
|
|
2120
|
+
let raw;
|
|
2121
|
+
try {
|
|
2122
|
+
raw = await fsp.readFile(configPath, "utf8");
|
|
2123
|
+
} catch {
|
|
2124
|
+
return { migrated: 0, file: configPath };
|
|
2125
|
+
}
|
|
2126
|
+
let parsed;
|
|
2127
|
+
try {
|
|
2128
|
+
parsed = JSON.parse(raw);
|
|
2129
|
+
} catch {
|
|
2130
|
+
return { migrated: 0, file: configPath };
|
|
2131
|
+
}
|
|
2132
|
+
const counter = { n: 0 };
|
|
2133
|
+
const migrated = walkCount(parsed, vault, counter);
|
|
2134
|
+
if (counter.n === 0) return { migrated: 0, file: configPath };
|
|
2135
|
+
await atomicWrite(configPath, JSON.stringify(migrated, null, 2), { mode: 384 });
|
|
2136
|
+
await restrictFilePermissions(configPath);
|
|
2137
|
+
return { migrated: counter.n, file: configPath };
|
|
2138
|
+
}
|
|
2139
|
+
async function restrictFilePermissions(filePath, opts) {
|
|
2140
|
+
const warn = ((msg) => console.warn(msg));
|
|
2141
|
+
if (process.platform === "win32") {
|
|
2142
|
+
try {
|
|
2143
|
+
const { execFile: execFile2 } = await import('child_process');
|
|
2144
|
+
const { promisify: promisify2 } = await import('util');
|
|
2145
|
+
const execFileAsync = promisify2(execFile2);
|
|
2146
|
+
const user = windowsAccountName();
|
|
2147
|
+
if (!user) {
|
|
2148
|
+
warn(
|
|
2149
|
+
`[secret-vault] Could not determine the current Windows user for ${filePath}; skipping icacls hardening.`
|
|
2150
|
+
);
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
await execFileAsync("icacls", [filePath, "/inheritance:r", "/grant:r", `${user}:(F)`]);
|
|
2154
|
+
} catch {
|
|
2155
|
+
warn(
|
|
2156
|
+
`[secret-vault] Could not restrict permissions on ${filePath} \u2014 config file may be readable by other users on this system.`
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
2159
|
+
} else {
|
|
2160
|
+
try {
|
|
2161
|
+
await fsp.chmod(filePath, 384);
|
|
2162
|
+
} catch {
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
function windowsAccountName() {
|
|
2167
|
+
const username = process.env.USERNAME || process.env.USER;
|
|
2168
|
+
if (!username || username.includes("\0")) return void 0;
|
|
2169
|
+
const domain = process.env.USERDOMAIN;
|
|
2170
|
+
if (domain && !domain.includes("\0")) return `${domain}\\${username}`;
|
|
2171
|
+
return username;
|
|
2172
|
+
}
|
|
2173
|
+
function walkCount(node, vault, counter) {
|
|
2174
|
+
if (node === null || node === void 0) return node;
|
|
2175
|
+
if (typeof node !== "object") return node;
|
|
2176
|
+
if (Array.isArray(node)) {
|
|
2177
|
+
return node.map((item) => walkCount(item, vault, counter));
|
|
2178
|
+
}
|
|
2179
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
2180
|
+
for (const [k, v] of Object.entries(node)) {
|
|
2181
|
+
if (typeof v === "string" && isSecretField(k) && !vault.isEncrypted(v) && v.length > 0) {
|
|
2182
|
+
out[k] = vault.encrypt(v);
|
|
2183
|
+
counter.n++;
|
|
2184
|
+
} else if (typeof v === "object" && v !== null) {
|
|
2185
|
+
out[k] = walkCount(v, vault, counter);
|
|
2186
|
+
} else {
|
|
2187
|
+
out[k] = v;
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
return out;
|
|
2191
|
+
}
|
|
2192
|
+
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
2193
|
+
"__proto__",
|
|
2194
|
+
"constructor",
|
|
2195
|
+
"prototype",
|
|
2196
|
+
"__defineGetter__",
|
|
2197
|
+
"__defineSetter__",
|
|
2198
|
+
"__lookupGetter__",
|
|
2199
|
+
"__lookupSetter__"
|
|
2200
|
+
]);
|
|
2201
|
+
function deepMerge(a, b) {
|
|
2202
|
+
const out = { ...a };
|
|
2203
|
+
for (const [k, v] of Object.entries(b)) {
|
|
2204
|
+
if (FORBIDDEN_PROTO_KEYS.has(k)) continue;
|
|
2205
|
+
const existing = out[k];
|
|
2206
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
2207
|
+
out[k] = deepMerge(existing, v);
|
|
2208
|
+
} else {
|
|
2209
|
+
out[k] = v;
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
return out;
|
|
2213
|
+
}
|
|
2027
2214
|
|
|
2028
2215
|
// src/storage/config-loader.ts
|
|
2029
2216
|
init_atomic_write();
|
|
@@ -2209,7 +2396,7 @@ var defaultIndexing = {
|
|
|
2209
2396
|
function isPrimitiveArray(a) {
|
|
2210
2397
|
return a.every((v) => v === null || typeof v !== "object");
|
|
2211
2398
|
}
|
|
2212
|
-
var
|
|
2399
|
+
var FORBIDDEN_PROTO_KEYS2 = /* @__PURE__ */ new Set([
|
|
2213
2400
|
"__proto__",
|
|
2214
2401
|
"constructor",
|
|
2215
2402
|
"prototype",
|
|
@@ -2218,12 +2405,12 @@ var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
|
2218
2405
|
"__lookupGetter__",
|
|
2219
2406
|
"__lookupSetter__"
|
|
2220
2407
|
]);
|
|
2221
|
-
function
|
|
2408
|
+
function deepMerge2(base, patch) {
|
|
2222
2409
|
if (typeof base !== "object" || base === null) return patch ?? base;
|
|
2223
2410
|
if (typeof patch !== "object" || patch === null) return base;
|
|
2224
2411
|
const out = { ...base };
|
|
2225
2412
|
for (const [k, v] of Object.entries(patch)) {
|
|
2226
|
-
if (
|
|
2413
|
+
if (FORBIDDEN_PROTO_KEYS2.has(k)) continue;
|
|
2227
2414
|
const existing = out[k];
|
|
2228
2415
|
if (Array.isArray(v)) {
|
|
2229
2416
|
if (Array.isArray(existing) && isPrimitiveArray(v) && isPrimitiveArray(existing)) {
|
|
@@ -2237,7 +2424,7 @@ function deepMerge(base, patch) {
|
|
|
2237
2424
|
}
|
|
2238
2425
|
}
|
|
2239
2426
|
} else if (typeof v === "object" && v !== null && typeof existing === "object" && existing !== null) {
|
|
2240
|
-
out[k] =
|
|
2427
|
+
out[k] = deepMerge2(existing, v);
|
|
2241
2428
|
} else if (v !== void 0) {
|
|
2242
2429
|
out[k] = v;
|
|
2243
2430
|
}
|
|
@@ -2262,9 +2449,9 @@ var DefaultConfigLoader = class {
|
|
|
2262
2449
|
this.readJson(this.paths.projectLocalConfig),
|
|
2263
2450
|
this.readJson(this.paths.inProjectConfig)
|
|
2264
2451
|
]);
|
|
2265
|
-
cfg =
|
|
2266
|
-
cfg =
|
|
2267
|
-
cfg =
|
|
2452
|
+
cfg = deepMerge2(cfg, global);
|
|
2453
|
+
cfg = deepMerge2(cfg, local);
|
|
2454
|
+
cfg = deepMerge2(cfg, inProject);
|
|
2268
2455
|
for (const [key, fn] of Object.entries(ENV_MAP)) {
|
|
2269
2456
|
const v = process.env[key];
|
|
2270
2457
|
if (v) fn(cfg, v);
|
|
@@ -2278,14 +2465,14 @@ var DefaultConfigLoader = class {
|
|
|
2278
2465
|
try {
|
|
2279
2466
|
const patch = await src.read();
|
|
2280
2467
|
if (patch && Object.keys(patch).length > 0) {
|
|
2281
|
-
cfg =
|
|
2468
|
+
cfg = deepMerge2(cfg, patch);
|
|
2282
2469
|
}
|
|
2283
2470
|
} catch (err) {
|
|
2284
2471
|
console.warn(`Config source "${src.name}" failed`, err);
|
|
2285
2472
|
}
|
|
2286
2473
|
}
|
|
2287
2474
|
if (opts.cliFlags) {
|
|
2288
|
-
cfg =
|
|
2475
|
+
cfg = deepMerge2(cfg, opts.cliFlags);
|
|
2289
2476
|
}
|
|
2290
2477
|
if (this.vault) {
|
|
2291
2478
|
cfg = decryptConfigSecrets(cfg, this.vault);
|
|
@@ -3697,277 +3884,51 @@ var DefaultSecretScrubber = class {
|
|
|
3697
3884
|
}
|
|
3698
3885
|
};
|
|
3699
3886
|
|
|
3700
|
-
// src/
|
|
3701
|
-
var
|
|
3887
|
+
// src/security/capabilities.ts
|
|
3888
|
+
var ToolCapabilities = {
|
|
3889
|
+
/** Can execute arbitrary commands in the user's shell (the `bash` tool). */
|
|
3890
|
+
SHELL_ARBITRARY: "shell.arbitrary",
|
|
3891
|
+
/** Can execute a restricted set of commands (the `exec` tool). */
|
|
3892
|
+
SHELL_RESTRICTED: "shell.restricted",
|
|
3893
|
+
/** Can write / modify / delete files inside the project. */
|
|
3894
|
+
FS_WRITE: "fs.write",
|
|
3895
|
+
/** Can write files outside the current project root (very high risk). */
|
|
3896
|
+
FS_WRITE_OUTSIDE_PROJECT: "fs.write.outside-project",
|
|
3897
|
+
/** Proxies tools from external MCP servers (unknown capability). */
|
|
3898
|
+
MCP_PROXY: "mcp.proxy",
|
|
3899
|
+
/** Can spawn or manage subagents / multi-agent tasks. */
|
|
3900
|
+
SUBAGENT_SPAWN: "subagent.spawn",
|
|
3901
|
+
/** Can mutate global or session configuration / trust state. */
|
|
3902
|
+
CONFIG_MUTATE: "config.mutate",
|
|
3903
|
+
/** Can install packages or run package managers with side effects. */
|
|
3904
|
+
PACKAGE_INSTALL: "package.install"
|
|
3905
|
+
};
|
|
3906
|
+
var DANGEROUS_FOR_SUBAGENTS = [
|
|
3907
|
+
ToolCapabilities.SHELL_ARBITRARY,
|
|
3908
|
+
ToolCapabilities.SHELL_RESTRICTED,
|
|
3909
|
+
ToolCapabilities.FS_WRITE,
|
|
3910
|
+
ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
|
|
3911
|
+
ToolCapabilities.MCP_PROXY,
|
|
3912
|
+
ToolCapabilities.SUBAGENT_SPAWN,
|
|
3913
|
+
ToolCapabilities.CONFIG_MUTATE,
|
|
3914
|
+
ToolCapabilities.PACKAGE_INSTALL
|
|
3915
|
+
];
|
|
3916
|
+
function hasDangerousCapabilityForSubagents(toolOrCaps) {
|
|
3917
|
+
if (!toolOrCaps) return false;
|
|
3918
|
+
const input = toolOrCaps;
|
|
3919
|
+
const caps = Array.isArray(toolOrCaps) ? toolOrCaps : input.capabilities ?? [];
|
|
3920
|
+
return caps.some((c) => DANGEROUS_FOR_SUBAGENTS.includes(c));
|
|
3921
|
+
}
|
|
3922
|
+
function getDangerousCapabilities(toolOrCaps) {
|
|
3923
|
+
if (!toolOrCaps) return [];
|
|
3924
|
+
const input = toolOrCaps;
|
|
3925
|
+
const caps = Array.isArray(toolOrCaps) ? toolOrCaps : input.capabilities ?? [];
|
|
3926
|
+
return caps.filter(
|
|
3927
|
+
(c) => DANGEROUS_FOR_SUBAGENTS.includes(c)
|
|
3928
|
+
);
|
|
3929
|
+
}
|
|
3702
3930
|
|
|
3703
|
-
// src/security/
|
|
3704
|
-
init_atomic_write();
|
|
3705
|
-
var KEY_BYTES = 32;
|
|
3706
|
-
var IV_BYTES = 12;
|
|
3707
|
-
var TAG_BYTES = 16;
|
|
3708
|
-
var ALGO = "aes-256-gcm";
|
|
3709
|
-
var DefaultSecretVault = class {
|
|
3710
|
-
keyFile;
|
|
3711
|
-
key;
|
|
3712
|
-
constructor(opts) {
|
|
3713
|
-
this.keyFile = opts.keyFile;
|
|
3714
|
-
}
|
|
3715
|
-
isEncrypted(value) {
|
|
3716
|
-
return typeof value === "string" && value.startsWith(ENCRYPTED_PREFIX);
|
|
3717
|
-
}
|
|
3718
|
-
encrypt(plaintext) {
|
|
3719
|
-
if (this.isEncrypted(plaintext)) return plaintext;
|
|
3720
|
-
const key = this.loadOrCreateKey();
|
|
3721
|
-
const iv = randomBytes(IV_BYTES);
|
|
3722
|
-
const cipher = createCipheriv(ALGO, key, iv);
|
|
3723
|
-
const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
3724
|
-
const tag = cipher.getAuthTag();
|
|
3725
|
-
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
|
|
3726
|
-
}
|
|
3727
|
-
decrypt(value) {
|
|
3728
|
-
if (!this.isEncrypted(value)) return value;
|
|
3729
|
-
const rest = value.slice(ENCRYPTED_PREFIX.length);
|
|
3730
|
-
const parts = rest.split(":");
|
|
3731
|
-
if (parts.length !== 3) {
|
|
3732
|
-
throw new Error("SecretVault: malformed encrypted value");
|
|
3733
|
-
}
|
|
3734
|
-
const [ivB64, tagB64, ctB64] = parts;
|
|
3735
|
-
const iv = Buffer.from(ivB64, "base64");
|
|
3736
|
-
const tag = Buffer.from(tagB64, "base64");
|
|
3737
|
-
const ct = Buffer.from(ctB64, "base64");
|
|
3738
|
-
if (iv.length !== IV_BYTES) throw new Error("SecretVault: bad IV length");
|
|
3739
|
-
if (tag.length !== TAG_BYTES) throw new Error("SecretVault: bad tag length");
|
|
3740
|
-
const key = this.loadOrCreateKey();
|
|
3741
|
-
const decipher = createDecipheriv(ALGO, key, iv);
|
|
3742
|
-
decipher.setAuthTag(tag);
|
|
3743
|
-
const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
3744
|
-
return pt.toString("utf8");
|
|
3745
|
-
}
|
|
3746
|
-
loadOrCreateKey() {
|
|
3747
|
-
if (this.key) return this.key;
|
|
3748
|
-
try {
|
|
3749
|
-
const buf = fs5.readFileSync(this.keyFile);
|
|
3750
|
-
if (buf.length !== KEY_BYTES) {
|
|
3751
|
-
throw new Error(
|
|
3752
|
-
`SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES}). Remove it manually to generate a new key.`
|
|
3753
|
-
);
|
|
3754
|
-
}
|
|
3755
|
-
this.key = buf;
|
|
3756
|
-
return this.key;
|
|
3757
|
-
} catch (err) {
|
|
3758
|
-
if (err.code !== "ENOENT") throw err;
|
|
3759
|
-
}
|
|
3760
|
-
fs5.mkdirSync(path11.dirname(this.keyFile), { recursive: true });
|
|
3761
|
-
const key = randomBytes(KEY_BYTES);
|
|
3762
|
-
try {
|
|
3763
|
-
fs5.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
|
|
3764
|
-
} catch (err) {
|
|
3765
|
-
if (err.code !== "EEXIST") throw err;
|
|
3766
|
-
const buf = fs5.readFileSync(this.keyFile);
|
|
3767
|
-
if (buf.length !== KEY_BYTES) {
|
|
3768
|
-
throw new Error(
|
|
3769
|
-
`SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES}). Remove it manually to generate a new key.`
|
|
3770
|
-
);
|
|
3771
|
-
}
|
|
3772
|
-
this.key = buf;
|
|
3773
|
-
return this.key;
|
|
3774
|
-
}
|
|
3775
|
-
this.key = key;
|
|
3776
|
-
return key;
|
|
3777
|
-
}
|
|
3778
|
-
};
|
|
3779
|
-
function decryptConfigSecrets2(cfg, vault, opts) {
|
|
3780
|
-
const warn = opts?.warn ?? ((msg) => console.warn(msg));
|
|
3781
|
-
return walk2(cfg, vault, (v, key) => {
|
|
3782
|
-
try {
|
|
3783
|
-
return vault.decrypt(v);
|
|
3784
|
-
} catch (err) {
|
|
3785
|
-
warn(
|
|
3786
|
-
`[secret-vault] Failed to decrypt "${key}": ${err instanceof Error ? err.message : err}`
|
|
3787
|
-
);
|
|
3788
|
-
return "";
|
|
3789
|
-
}
|
|
3790
|
-
});
|
|
3791
|
-
}
|
|
3792
|
-
function encryptConfigSecrets(cfg, vault, _opts) {
|
|
3793
|
-
return walk2(cfg, vault, (v) => vault.encrypt(v));
|
|
3794
|
-
}
|
|
3795
|
-
function walk2(node, vault, transform) {
|
|
3796
|
-
if (node === null || node === void 0) return node;
|
|
3797
|
-
if (typeof node !== "object") return node;
|
|
3798
|
-
if (Array.isArray(node)) {
|
|
3799
|
-
return node.map((item) => walk2(item, vault, transform));
|
|
3800
|
-
}
|
|
3801
|
-
const out = /* @__PURE__ */ Object.create(null);
|
|
3802
|
-
for (const [k, v] of Object.entries(node)) {
|
|
3803
|
-
if (typeof v === "string" && isSecretField2(k)) {
|
|
3804
|
-
out[k] = transform(v, k);
|
|
3805
|
-
} else if (typeof v === "object" && v !== null) {
|
|
3806
|
-
out[k] = walk2(v, vault, transform);
|
|
3807
|
-
} else {
|
|
3808
|
-
out[k] = v;
|
|
3809
|
-
}
|
|
3810
|
-
}
|
|
3811
|
-
return out;
|
|
3812
|
-
}
|
|
3813
|
-
var SECRET_KEY_PATTERN2 = /(?:apikey|api_key|authtoken|auth_token|bearer|secret|password|passwd|pwd|refreshtoken|refresh_token|sessionkey|session_key|access[_-]?token|private[_-]?key)/i;
|
|
3814
|
-
var NON_SECRET_OVERRIDES2 = /* @__PURE__ */ new Set(["publickey", "public_key"]);
|
|
3815
|
-
function isSecretField2(name) {
|
|
3816
|
-
const lc = name.toLowerCase();
|
|
3817
|
-
if (NON_SECRET_OVERRIDES2.has(lc)) return false;
|
|
3818
|
-
return SECRET_KEY_PATTERN2.test(lc);
|
|
3819
|
-
}
|
|
3820
|
-
async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
3821
|
-
let current = {};
|
|
3822
|
-
try {
|
|
3823
|
-
const raw = await fsp.readFile(configPath, "utf8");
|
|
3824
|
-
current = JSON.parse(raw);
|
|
3825
|
-
} catch {
|
|
3826
|
-
}
|
|
3827
|
-
const merged = deepMerge2(current, patch ?? {});
|
|
3828
|
-
const encrypted = encryptConfigSecrets(merged, vault);
|
|
3829
|
-
await fsp.mkdir(path11.dirname(configPath), { recursive: true });
|
|
3830
|
-
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
3831
|
-
await restrictFilePermissions(configPath);
|
|
3832
|
-
}
|
|
3833
|
-
async function migratePlaintextSecrets(configPath, vault) {
|
|
3834
|
-
let raw;
|
|
3835
|
-
try {
|
|
3836
|
-
raw = await fsp.readFile(configPath, "utf8");
|
|
3837
|
-
} catch {
|
|
3838
|
-
return { migrated: 0, file: configPath };
|
|
3839
|
-
}
|
|
3840
|
-
let parsed;
|
|
3841
|
-
try {
|
|
3842
|
-
parsed = JSON.parse(raw);
|
|
3843
|
-
} catch {
|
|
3844
|
-
return { migrated: 0, file: configPath };
|
|
3845
|
-
}
|
|
3846
|
-
const counter = { n: 0 };
|
|
3847
|
-
const migrated = walkCount(parsed, vault, counter);
|
|
3848
|
-
if (counter.n === 0) return { migrated: 0, file: configPath };
|
|
3849
|
-
await atomicWrite(configPath, JSON.stringify(migrated, null, 2), { mode: 384 });
|
|
3850
|
-
await restrictFilePermissions(configPath);
|
|
3851
|
-
return { migrated: counter.n, file: configPath };
|
|
3852
|
-
}
|
|
3853
|
-
async function restrictFilePermissions(filePath, opts) {
|
|
3854
|
-
const warn = ((msg) => console.warn(msg));
|
|
3855
|
-
if (process.platform === "win32") {
|
|
3856
|
-
try {
|
|
3857
|
-
const { execFile: execFile2 } = await import('child_process');
|
|
3858
|
-
const { promisify: promisify2 } = await import('util');
|
|
3859
|
-
const execFileAsync = promisify2(execFile2);
|
|
3860
|
-
const user = windowsAccountName();
|
|
3861
|
-
if (!user) {
|
|
3862
|
-
warn(
|
|
3863
|
-
`[secret-vault] Could not determine the current Windows user for ${filePath}; skipping icacls hardening.`
|
|
3864
|
-
);
|
|
3865
|
-
return;
|
|
3866
|
-
}
|
|
3867
|
-
await execFileAsync("icacls", [filePath, "/inheritance:r", "/grant:r", `${user}:(F)`]);
|
|
3868
|
-
} catch {
|
|
3869
|
-
warn(
|
|
3870
|
-
`[secret-vault] Could not restrict permissions on ${filePath} \u2014 config file may be readable by other users on this system.`
|
|
3871
|
-
);
|
|
3872
|
-
}
|
|
3873
|
-
} else {
|
|
3874
|
-
try {
|
|
3875
|
-
await fsp.chmod(filePath, 384);
|
|
3876
|
-
} catch {
|
|
3877
|
-
}
|
|
3878
|
-
}
|
|
3879
|
-
}
|
|
3880
|
-
function windowsAccountName() {
|
|
3881
|
-
const username = process.env.USERNAME || process.env.USER;
|
|
3882
|
-
if (!username || username.includes("\0")) return void 0;
|
|
3883
|
-
const domain = process.env.USERDOMAIN;
|
|
3884
|
-
if (domain && !domain.includes("\0")) return `${domain}\\${username}`;
|
|
3885
|
-
return username;
|
|
3886
|
-
}
|
|
3887
|
-
function walkCount(node, vault, counter) {
|
|
3888
|
-
if (node === null || node === void 0) return node;
|
|
3889
|
-
if (typeof node !== "object") return node;
|
|
3890
|
-
if (Array.isArray(node)) {
|
|
3891
|
-
return node.map((item) => walkCount(item, vault, counter));
|
|
3892
|
-
}
|
|
3893
|
-
const out = /* @__PURE__ */ Object.create(null);
|
|
3894
|
-
for (const [k, v] of Object.entries(node)) {
|
|
3895
|
-
if (typeof v === "string" && isSecretField2(k) && !vault.isEncrypted(v) && v.length > 0) {
|
|
3896
|
-
out[k] = vault.encrypt(v);
|
|
3897
|
-
counter.n++;
|
|
3898
|
-
} else if (typeof v === "object" && v !== null) {
|
|
3899
|
-
out[k] = walkCount(v, vault, counter);
|
|
3900
|
-
} else {
|
|
3901
|
-
out[k] = v;
|
|
3902
|
-
}
|
|
3903
|
-
}
|
|
3904
|
-
return out;
|
|
3905
|
-
}
|
|
3906
|
-
var FORBIDDEN_PROTO_KEYS2 = /* @__PURE__ */ new Set([
|
|
3907
|
-
"__proto__",
|
|
3908
|
-
"constructor",
|
|
3909
|
-
"prototype",
|
|
3910
|
-
"__defineGetter__",
|
|
3911
|
-
"__defineSetter__",
|
|
3912
|
-
"__lookupGetter__",
|
|
3913
|
-
"__lookupSetter__"
|
|
3914
|
-
]);
|
|
3915
|
-
function deepMerge2(a, b) {
|
|
3916
|
-
const out = { ...a };
|
|
3917
|
-
for (const [k, v] of Object.entries(b)) {
|
|
3918
|
-
if (FORBIDDEN_PROTO_KEYS2.has(k)) continue;
|
|
3919
|
-
const existing = out[k];
|
|
3920
|
-
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
3921
|
-
out[k] = deepMerge2(existing, v);
|
|
3922
|
-
} else {
|
|
3923
|
-
out[k] = v;
|
|
3924
|
-
}
|
|
3925
|
-
}
|
|
3926
|
-
return out;
|
|
3927
|
-
}
|
|
3928
|
-
|
|
3929
|
-
// src/security/capabilities.ts
|
|
3930
|
-
var ToolCapabilities = {
|
|
3931
|
-
/** Can execute arbitrary commands in the user's shell (the `bash` tool). */
|
|
3932
|
-
SHELL_ARBITRARY: "shell.arbitrary",
|
|
3933
|
-
/** Can write / modify / delete files inside the project. */
|
|
3934
|
-
FS_WRITE: "fs.write",
|
|
3935
|
-
/** Can write files outside the current project root (very high risk). */
|
|
3936
|
-
FS_WRITE_OUTSIDE_PROJECT: "fs.write.outside-project",
|
|
3937
|
-
/** Proxies tools from external MCP servers (unknown capability). */
|
|
3938
|
-
MCP_PROXY: "mcp.proxy",
|
|
3939
|
-
/** Can spawn or manage subagents / multi-agent tasks. */
|
|
3940
|
-
SUBAGENT_SPAWN: "subagent.spawn",
|
|
3941
|
-
/** Can mutate global or session configuration / trust state. */
|
|
3942
|
-
CONFIG_MUTATE: "config.mutate",
|
|
3943
|
-
/** Can install packages or run package managers with side effects. */
|
|
3944
|
-
PACKAGE_INSTALL: "package.install"
|
|
3945
|
-
};
|
|
3946
|
-
var DANGEROUS_FOR_SUBAGENTS = [
|
|
3947
|
-
ToolCapabilities.SHELL_ARBITRARY,
|
|
3948
|
-
ToolCapabilities.FS_WRITE,
|
|
3949
|
-
ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
|
|
3950
|
-
ToolCapabilities.MCP_PROXY,
|
|
3951
|
-
ToolCapabilities.SUBAGENT_SPAWN,
|
|
3952
|
-
ToolCapabilities.CONFIG_MUTATE,
|
|
3953
|
-
ToolCapabilities.PACKAGE_INSTALL
|
|
3954
|
-
];
|
|
3955
|
-
function hasDangerousCapabilityForSubagents(toolOrCaps) {
|
|
3956
|
-
if (!toolOrCaps) return false;
|
|
3957
|
-
const input = toolOrCaps;
|
|
3958
|
-
const caps = Array.isArray(toolOrCaps) ? toolOrCaps : input.capabilities ?? [];
|
|
3959
|
-
return caps.some((c) => DANGEROUS_FOR_SUBAGENTS.includes(c));
|
|
3960
|
-
}
|
|
3961
|
-
function getDangerousCapabilities(toolOrCaps) {
|
|
3962
|
-
if (!toolOrCaps) return [];
|
|
3963
|
-
const input = toolOrCaps;
|
|
3964
|
-
const caps = Array.isArray(toolOrCaps) ? toolOrCaps : input.capabilities ?? [];
|
|
3965
|
-
return caps.filter(
|
|
3966
|
-
(c) => DANGEROUS_FOR_SUBAGENTS.includes(c)
|
|
3967
|
-
);
|
|
3968
|
-
}
|
|
3969
|
-
|
|
3970
|
-
// src/security/permission-policy.ts
|
|
3931
|
+
// src/security/permission-policy.ts
|
|
3971
3932
|
init_atomic_write();
|
|
3972
3933
|
|
|
3973
3934
|
// src/utils/glob-match.ts
|
|
@@ -4357,7 +4318,7 @@ var DefaultPermissionPolicy = class {
|
|
|
4357
4318
|
subjectFor(toolName, input, subjectKey) {
|
|
4358
4319
|
if (!input || typeof input !== "object") return void 0;
|
|
4359
4320
|
const obj = input;
|
|
4360
|
-
const globChars = /[
|
|
4321
|
+
const globChars = /[*?[\]]/g;
|
|
4361
4322
|
const escapeGlob = (s) => s.replace(globChars, (c) => `\\${c}`);
|
|
4362
4323
|
const normalizePath = (s) => escapeGlob(s.replace(/\\/g, "/"));
|
|
4363
4324
|
if (subjectKey) {
|
|
@@ -4388,38 +4349,15 @@ var DefaultPermissionPolicy = class {
|
|
|
4388
4349
|
}
|
|
4389
4350
|
};
|
|
4390
4351
|
var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
4391
|
-
/**
|
|
4392
|
-
* Legacy name-based denylist.
|
|
4393
|
-
* @deprecated Prefer declaring `capabilities` on the Tool and using capability-based checks.
|
|
4394
|
-
*/
|
|
4395
|
-
static LEGACY_NAME_DENY = /* @__PURE__ */ new Set([
|
|
4396
|
-
"bash",
|
|
4397
|
-
"write",
|
|
4398
|
-
"edit",
|
|
4399
|
-
"replace",
|
|
4400
|
-
"scaffold",
|
|
4401
|
-
"patch",
|
|
4402
|
-
"install",
|
|
4403
|
-
"exec"
|
|
4404
|
-
]);
|
|
4405
|
-
// Note: hasDangerousCapabilityForSubagents is now the shared helper from capabilities.ts
|
|
4406
|
-
// The old private method was removed in favor of the centralized utility.
|
|
4407
|
-
/**
|
|
4408
|
-
* Tools from MCP servers (`mcp__<server>__<tool>`) are external code of
|
|
4409
|
-
* unknown capability — they may wrap a shell or filesystem. They are
|
|
4410
|
-
* fail-closed here: not auto-approved for subagents by default, so the
|
|
4411
|
-
* leader must allow them explicitly per-spawn.
|
|
4412
|
-
*/
|
|
4413
4352
|
static isMcpTool(name) {
|
|
4414
4353
|
return name.startsWith("mcp__");
|
|
4415
4354
|
}
|
|
4416
4355
|
async evaluate(tool) {
|
|
4417
4356
|
const hasDangerousCap = hasDangerousCapabilityForSubagents(tool);
|
|
4418
|
-
const legacyNameBlock = _AutoApprovePermissionPolicy.LEGACY_NAME_DENY.has(tool.name);
|
|
4419
4357
|
const isMcp = _AutoApprovePermissionPolicy.isMcpTool(tool.name);
|
|
4420
|
-
const blocked = tool.permission === "deny" || hasDangerousCap ||
|
|
4358
|
+
const blocked = tool.permission === "deny" || hasDangerousCap || isMcp;
|
|
4421
4359
|
if (blocked) {
|
|
4422
|
-
const reason = hasDangerousCap ? `tool declares dangerous capability (${tool.capabilities?.join(", ")}) \u2014 not auto-approved for subagents` :
|
|
4360
|
+
const reason = hasDangerousCap ? `tool declares dangerous capability (${tool.capabilities?.join(", ")}) \u2014 not auto-approved for subagents` : isMcp ? `MCP tool ${tool.name} is not auto-approved for subagents \u2014 ask the leader to allow it explicitly` : "tool default deny";
|
|
4423
4361
|
return {
|
|
4424
4362
|
permission: "deny",
|
|
4425
4363
|
source: "subagent_guard",
|
|
@@ -5088,53 +5026,63 @@ async function streamProviderToResponse(provider, req, signal, ctx, events) {
|
|
|
5088
5026
|
const next = await iter.next();
|
|
5089
5027
|
if (next.done) break;
|
|
5090
5028
|
const ev = next.value;
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5029
|
+
try {
|
|
5030
|
+
switch (ev.type) {
|
|
5031
|
+
case "message_start":
|
|
5032
|
+
handleMessageStart(state, ev.model);
|
|
5033
|
+
break;
|
|
5034
|
+
case "content_block_start":
|
|
5035
|
+
handleContentBlockStart(state, ev);
|
|
5036
|
+
break;
|
|
5037
|
+
case "content_block_stop":
|
|
5038
|
+
handleContentBlockStop(state, ev);
|
|
5039
|
+
break;
|
|
5040
|
+
case "text_delta":
|
|
5041
|
+
handleTextDelta(state, ev.text);
|
|
5042
|
+
events.emit("provider.text_delta", { ctx, text: ev.text });
|
|
5043
|
+
break;
|
|
5044
|
+
case "tool_use_start": {
|
|
5045
|
+
const idVal = ev.id;
|
|
5046
|
+
const nameVal = ev.name;
|
|
5047
|
+
handleToolUseStart(state, { id: idVal, name: nameVal });
|
|
5048
|
+
const emittedPayload = { ctx, id: idVal ?? "unknown", name: nameVal ?? "unknown" };
|
|
5049
|
+
events.emit("provider.tool_use_start", emittedPayload);
|
|
5050
|
+
break;
|
|
5051
|
+
}
|
|
5052
|
+
case "tool_use_input_delta":
|
|
5053
|
+
handleToolUseInputDelta(state, ev);
|
|
5054
|
+
break;
|
|
5055
|
+
case "tool_use_stop": {
|
|
5056
|
+
const stoppedName = state.tools.get(ev.id)?.name ?? "unknown";
|
|
5057
|
+
handleToolUseStop(state, ev);
|
|
5058
|
+
events.emit("provider.tool_use_stop", { ctx, id: ev.id, name: stoppedName });
|
|
5059
|
+
break;
|
|
5060
|
+
}
|
|
5061
|
+
case "thinking_start":
|
|
5062
|
+
handleThinkingStart(state, ev);
|
|
5063
|
+
break;
|
|
5064
|
+
case "thinking_delta":
|
|
5065
|
+
handleThinkingDelta(state, ev.text);
|
|
5066
|
+
events.emit("provider.thinking_delta", { ctx, text: ev.text });
|
|
5067
|
+
break;
|
|
5068
|
+
case "thinking_signature":
|
|
5069
|
+
handleThinkingSignature(state, ev.signature);
|
|
5070
|
+
break;
|
|
5071
|
+
case "thinking_stop":
|
|
5072
|
+
handleThinkingStop(state);
|
|
5073
|
+
break;
|
|
5074
|
+
case "message_stop":
|
|
5075
|
+
handleMessageStop(state, ev);
|
|
5076
|
+
break;
|
|
5077
|
+
default:
|
|
5078
|
+
break;
|
|
5121
5079
|
}
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
break;
|
|
5129
|
-
case "thinking_signature":
|
|
5130
|
-
handleThinkingSignature(state, ev.signature);
|
|
5131
|
-
break;
|
|
5132
|
-
case "thinking_stop":
|
|
5133
|
-
handleThinkingStop(state);
|
|
5134
|
-
break;
|
|
5135
|
-
case "message_stop":
|
|
5136
|
-
handleMessageStop(state, ev);
|
|
5137
|
-
break;
|
|
5080
|
+
} catch (handlerErr) {
|
|
5081
|
+
events.emit("provider.stream_error", {
|
|
5082
|
+
ctx,
|
|
5083
|
+
eventType: ev.type,
|
|
5084
|
+
msg: handlerErr instanceof Error ? handlerErr.message : String(handlerErr)
|
|
5085
|
+
});
|
|
5138
5086
|
}
|
|
5139
5087
|
}
|
|
5140
5088
|
} catch (err) {
|
|
@@ -5147,8 +5095,10 @@ async function streamProviderToResponse(provider, req, signal, ctx, events) {
|
|
|
5147
5095
|
try {
|
|
5148
5096
|
let drainTimer = null;
|
|
5149
5097
|
try {
|
|
5098
|
+
const drainPromise = Promise.resolve(iter.return?.()).catch(() => {
|
|
5099
|
+
});
|
|
5150
5100
|
await Promise.race([
|
|
5151
|
-
|
|
5101
|
+
drainPromise,
|
|
5152
5102
|
new Promise((resolve5) => {
|
|
5153
5103
|
drainTimer = setTimeout(resolve5, STREAM_DRAIN_TIMEOUT_MS);
|
|
5154
5104
|
})
|
|
@@ -5246,6 +5196,17 @@ var DefaultProviderRunner = class {
|
|
|
5246
5196
|
|
|
5247
5197
|
// src/utils/token-estimate.ts
|
|
5248
5198
|
var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
|
|
5199
|
+
var CALIBRATION_GLOBAL_KEY = "__global__";
|
|
5200
|
+
var _cals = /* @__PURE__ */ new Map();
|
|
5201
|
+
function calState(key) {
|
|
5202
|
+
let state = _cals.get(key);
|
|
5203
|
+
if (!state) {
|
|
5204
|
+
state = { ratio: 1, count: 0, prevEst: 0 };
|
|
5205
|
+
_cals.set(key, state);
|
|
5206
|
+
}
|
|
5207
|
+
return state;
|
|
5208
|
+
}
|
|
5209
|
+
var MIN_SAMPLES_FOR_CALIBRATION = 3;
|
|
5249
5210
|
var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
|
|
5250
5211
|
var ESTIMATE_CACHE_MAX_SIZE = 1e4;
|
|
5251
5212
|
function getCachedEstimate(key, compute) {
|
|
@@ -5257,108 +5218,372 @@ function getCachedEstimate(key, compute) {
|
|
|
5257
5218
|
ESTIMATE_CACHE.delete(expectDefined(keys[i]));
|
|
5258
5219
|
}
|
|
5259
5220
|
}
|
|
5260
|
-
const estimate = compute();
|
|
5261
|
-
ESTIMATE_CACHE.set(key, estimate);
|
|
5262
|
-
return estimate;
|
|
5263
|
-
}
|
|
5264
|
-
function estimateToolInputTokens(input) {
|
|
5265
|
-
if (typeof input === "string") return RoughTokenEstimate(input);
|
|
5266
|
-
if (input === null || typeof input !== "object") {
|
|
5267
|
-
return RoughTokenEstimate(String(input));
|
|
5221
|
+
const estimate = compute();
|
|
5222
|
+
ESTIMATE_CACHE.set(key, estimate);
|
|
5223
|
+
return estimate;
|
|
5224
|
+
}
|
|
5225
|
+
function estimateToolInputTokens(input) {
|
|
5226
|
+
if (typeof input === "string") return RoughTokenEstimate(input);
|
|
5227
|
+
if (input === null || typeof input !== "object") {
|
|
5228
|
+
return RoughTokenEstimate(String(input));
|
|
5229
|
+
}
|
|
5230
|
+
const key = JSON.stringify(input);
|
|
5231
|
+
return getCachedEstimate(key, () => RoughTokenEstimate(key));
|
|
5232
|
+
}
|
|
5233
|
+
function estimateToolResultTokens(content) {
|
|
5234
|
+
if (typeof content === "string") return RoughTokenEstimate(content);
|
|
5235
|
+
const key = JSON.stringify(content);
|
|
5236
|
+
return getCachedEstimate(key, () => RoughTokenEstimate(key));
|
|
5237
|
+
}
|
|
5238
|
+
function estimateTextTokens(text) {
|
|
5239
|
+
return RoughTokenEstimate(text);
|
|
5240
|
+
}
|
|
5241
|
+
function estimateMessageTokens(messages) {
|
|
5242
|
+
let total = 0;
|
|
5243
|
+
for (const m of messages) {
|
|
5244
|
+
if (typeof m.content === "string") {
|
|
5245
|
+
total += estimateTextTokens(m.content);
|
|
5246
|
+
} else {
|
|
5247
|
+
for (const b of m.content) {
|
|
5248
|
+
if (b.type === "text") total += estimateTextTokens(b.text);
|
|
5249
|
+
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
5250
|
+
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
5251
|
+
}
|
|
5252
|
+
}
|
|
5253
|
+
}
|
|
5254
|
+
return total;
|
|
5255
|
+
}
|
|
5256
|
+
function estimateToolDefTokens(tool) {
|
|
5257
|
+
return RoughTokenEstimate(tool.name) + RoughTokenEstimate(tool.description ?? "") + RoughTokenEstimate(JSON.stringify(tool.inputSchema));
|
|
5258
|
+
}
|
|
5259
|
+
function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
5260
|
+
let messagesTokens = 0;
|
|
5261
|
+
if (typeof messages === "string") {
|
|
5262
|
+
messagesTokens = RoughTokenEstimate(messages);
|
|
5263
|
+
} else if (Array.isArray(messages)) {
|
|
5264
|
+
for (const m of messages) {
|
|
5265
|
+
if (typeof m === "object" && m !== null && "content" in m) {
|
|
5266
|
+
const content = m.content;
|
|
5267
|
+
if (typeof content === "string") {
|
|
5268
|
+
messagesTokens += RoughTokenEstimate(content);
|
|
5269
|
+
} else if (Array.isArray(content)) {
|
|
5270
|
+
for (const b of content) {
|
|
5271
|
+
if (typeof b === "object" && b !== null) {
|
|
5272
|
+
if (b.type === "text") {
|
|
5273
|
+
messagesTokens += RoughTokenEstimate(b.text);
|
|
5274
|
+
} else {
|
|
5275
|
+
messagesTokens += RoughTokenEstimate(JSON.stringify(b));
|
|
5276
|
+
}
|
|
5277
|
+
}
|
|
5278
|
+
}
|
|
5279
|
+
}
|
|
5280
|
+
}
|
|
5281
|
+
}
|
|
5282
|
+
}
|
|
5283
|
+
let systemTokens = 0;
|
|
5284
|
+
if (typeof systemPrompt === "string") {
|
|
5285
|
+
systemTokens = RoughTokenEstimate(systemPrompt);
|
|
5286
|
+
} else if (Array.isArray(systemPrompt)) {
|
|
5287
|
+
for (const b of systemPrompt) {
|
|
5288
|
+
if (typeof b === "object" && b !== null && b.type === "text") {
|
|
5289
|
+
systemTokens += RoughTokenEstimate(b.text);
|
|
5290
|
+
}
|
|
5291
|
+
}
|
|
5292
|
+
}
|
|
5293
|
+
let toolsTokens = 0;
|
|
5294
|
+
for (const t of tools) {
|
|
5295
|
+
toolsTokens += estimateToolDefTokens(t);
|
|
5296
|
+
}
|
|
5297
|
+
const total = messagesTokens + systemTokens + toolsTokens;
|
|
5298
|
+
calState(calibrationKey).prevEst = total;
|
|
5299
|
+
return {
|
|
5300
|
+
messages: messagesTokens,
|
|
5301
|
+
systemPrompt: systemTokens,
|
|
5302
|
+
tools: toolsTokens,
|
|
5303
|
+
total
|
|
5304
|
+
};
|
|
5305
|
+
}
|
|
5306
|
+
function estimateRequestTokensCalibrated(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
5307
|
+
const result = estimateRequestTokens(messages, systemPrompt, tools, calibrationKey);
|
|
5308
|
+
const cal = calState(calibrationKey);
|
|
5309
|
+
if (cal.count >= MIN_SAMPLES_FOR_CALIBRATION) {
|
|
5310
|
+
const safeRatio = Math.min(1.5, Math.max(0.5, cal.ratio));
|
|
5311
|
+
return {
|
|
5312
|
+
messages: Math.round(result.messages * safeRatio),
|
|
5313
|
+
systemPrompt: Math.round(result.systemPrompt * safeRatio),
|
|
5314
|
+
tools: Math.round(result.tools * safeRatio),
|
|
5315
|
+
total: Math.round(result.total * safeRatio)
|
|
5316
|
+
};
|
|
5317
|
+
}
|
|
5318
|
+
return result;
|
|
5319
|
+
}
|
|
5320
|
+
|
|
5321
|
+
// src/types/blocks.ts
|
|
5322
|
+
function isTextBlock(b) {
|
|
5323
|
+
return b.type === "text";
|
|
5324
|
+
}
|
|
5325
|
+
|
|
5326
|
+
// src/execution/compaction-core.ts
|
|
5327
|
+
var estimateMessages = estimateMessageTokens;
|
|
5328
|
+
function hasTextContent(m) {
|
|
5329
|
+
if (typeof m.content === "string") return m.content.trim().length > 0;
|
|
5330
|
+
return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
|
|
5331
|
+
}
|
|
5332
|
+
function findPreserveStart(messages, preserveK) {
|
|
5333
|
+
let pairCount = 0;
|
|
5334
|
+
let preserveStart = messages.length;
|
|
5335
|
+
for (let i = messages.length - 1; i >= 0 && pairCount < preserveK; i--) {
|
|
5336
|
+
const m = messages[i];
|
|
5337
|
+
if (!m) continue;
|
|
5338
|
+
if (m.role === "user" || m.role === "assistant") {
|
|
5339
|
+
pairCount++;
|
|
5340
|
+
preserveStart = i;
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
for (let i = preserveStart; i < messages.length; i++) {
|
|
5344
|
+
const m = messages[i];
|
|
5345
|
+
if (!m || typeof m.content === "string" || !Array.isArray(m.content)) continue;
|
|
5346
|
+
const hasToolUse3 = m.content.some((b) => b.type === "tool_use");
|
|
5347
|
+
if (hasToolUse3 && i + 1 < messages.length) {
|
|
5348
|
+
const next = messages[i + 1];
|
|
5349
|
+
if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => b.type === "tool_result")) {
|
|
5350
|
+
preserveStart = i + 1;
|
|
5351
|
+
}
|
|
5352
|
+
}
|
|
5353
|
+
}
|
|
5354
|
+
return preserveStart;
|
|
5355
|
+
}
|
|
5356
|
+
function eliseOldToolResults(messages, opts) {
|
|
5357
|
+
const preserveStart = findPreserveStart(messages, opts.preserveK);
|
|
5358
|
+
let saved = 0;
|
|
5359
|
+
let changed = false;
|
|
5360
|
+
const next = new Array(messages.length);
|
|
5361
|
+
for (let i = 0; i < messages.length; i++) {
|
|
5362
|
+
const msg = messages[i];
|
|
5363
|
+
if (i >= preserveStart || !msg || !Array.isArray(msg.content)) {
|
|
5364
|
+
next[i] = msg;
|
|
5365
|
+
continue;
|
|
5366
|
+
}
|
|
5367
|
+
const original = msg.content;
|
|
5368
|
+
const newContent = original.map((b) => {
|
|
5369
|
+
if (b.type !== "tool_result") return b;
|
|
5370
|
+
const tokens = estimateToolResultTokens(b.content);
|
|
5371
|
+
if (tokens < opts.eliseThreshold) return b;
|
|
5372
|
+
saved += tokens;
|
|
5373
|
+
const elided = {
|
|
5374
|
+
type: "tool_result",
|
|
5375
|
+
tool_use_id: b.tool_use_id,
|
|
5376
|
+
content: `[elided: ~${tokens} tokens]`,
|
|
5377
|
+
is_error: b.is_error
|
|
5378
|
+
};
|
|
5379
|
+
return elided;
|
|
5380
|
+
});
|
|
5381
|
+
if (newContent.every((b, idx) => b === original[idx])) {
|
|
5382
|
+
next[i] = msg;
|
|
5383
|
+
} else {
|
|
5384
|
+
next[i] = { ...msg, content: newContent };
|
|
5385
|
+
changed = true;
|
|
5386
|
+
}
|
|
5387
|
+
}
|
|
5388
|
+
return { messages: changed ? next : messages, saved, changed };
|
|
5389
|
+
}
|
|
5390
|
+
function buildLosslessDigest(messages) {
|
|
5391
|
+
const lines = [];
|
|
5392
|
+
for (const m of messages) {
|
|
5393
|
+
let text;
|
|
5394
|
+
let omitted = 0;
|
|
5395
|
+
if (typeof m.content === "string") {
|
|
5396
|
+
text = m.content;
|
|
5397
|
+
} else {
|
|
5398
|
+
const parts = [];
|
|
5399
|
+
for (const b of m.content) {
|
|
5400
|
+
if (isTextBlock(b)) parts.push(b.text);
|
|
5401
|
+
else if (b.type === "tool_use" || b.type === "tool_result") omitted++;
|
|
5402
|
+
}
|
|
5403
|
+
text = parts.join(" ");
|
|
5404
|
+
}
|
|
5405
|
+
if (text.trim().length === 0 && omitted === 0) continue;
|
|
5406
|
+
const marker = omitted > 0 ? ` [${omitted} tool call(s) omitted \u2014 see session log]` : "";
|
|
5407
|
+
lines.push(`[${m.role}]: ${text}${marker}`);
|
|
5408
|
+
}
|
|
5409
|
+
return lines.join("\n");
|
|
5410
|
+
}
|
|
5411
|
+
function extractText(m) {
|
|
5412
|
+
if (typeof m.content === "string") return m.content;
|
|
5413
|
+
return m.content.filter(isTextBlock).map((b) => b.text).join(" ");
|
|
5414
|
+
}
|
|
5415
|
+
function hasToolUse2(m) {
|
|
5416
|
+
if (typeof m.content === "string") return false;
|
|
5417
|
+
return m.content.some((b) => b.type === "tool_use");
|
|
5418
|
+
}
|
|
5419
|
+
function hasLargeToolResult(m, threshold = 3e3) {
|
|
5420
|
+
if (typeof m.content === "string") return false;
|
|
5421
|
+
return m.content.some(
|
|
5422
|
+
(b) => b.type === "tool_result" && b.content && (typeof b.content === "string" ? b.content.length : JSON.stringify(b.content).length) > threshold
|
|
5423
|
+
);
|
|
5424
|
+
}
|
|
5425
|
+
function scoreMessage(m, context) {
|
|
5426
|
+
const text = extractText(m).toLowerCase();
|
|
5427
|
+
if (text.trim().length === 0 && (hasToolUse2(m) || typeof m.content !== "string")) {
|
|
5428
|
+
const hasResult = typeof m.content !== "string" && m.content.some((b) => b.type === "tool_result");
|
|
5429
|
+
if (hasToolUse2(m) || hasResult) return 0;
|
|
5430
|
+
}
|
|
5431
|
+
if (context?.failureCounts && m.role === "user" && hasToolUse2(m) === false) {
|
|
5432
|
+
const isFailure = /error|fail|exception|timeout|enonet|eacces|eperm|enoent|abort/i.test(text);
|
|
5433
|
+
if (isFailure) {
|
|
5434
|
+
const errKey = /(error|fail|exception|timeout|enonet|eacces|eperm|enoent|abort)/i.exec(text)?.[0]?.toLowerCase() ?? "error";
|
|
5435
|
+
const count = (context.failureCounts.get(errKey) ?? 0) + 1;
|
|
5436
|
+
context.failureCounts.set(errKey, count);
|
|
5437
|
+
if (count >= 5) return 0;
|
|
5438
|
+
if (count >= 3) return 1;
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5441
|
+
if (m.role === "user") {
|
|
5442
|
+
if (/\b(wrong|no\b|stop\b|don'?t\b|actually|fix that|undo|revert|forget|ignore|skip)\b/i.test(
|
|
5443
|
+
text
|
|
5444
|
+
)) {
|
|
5445
|
+
return 5;
|
|
5446
|
+
}
|
|
5447
|
+
}
|
|
5448
|
+
if (/\b(error|exception|fatal|critical|crash|panic|abort|segfault|core dump|undefined is not|null pointer|typeerror|referenceerror|syntaxerror)\b/i.test(
|
|
5449
|
+
text
|
|
5450
|
+
)) {
|
|
5451
|
+
return 5;
|
|
5452
|
+
}
|
|
5453
|
+
if (/\b(security|vulnerability|injection|xss|csrf|secret|apikey|api.key|hardcoded|leak|exploit|cve)\b/i.test(
|
|
5454
|
+
text
|
|
5455
|
+
)) {
|
|
5456
|
+
return 5;
|
|
5457
|
+
}
|
|
5458
|
+
if (m.role === "assistant" && /\b(architecture|design|approach|strategy|pattern|refactor|migrate|restructure|decision|trade.?off)\b/i.test(
|
|
5459
|
+
text
|
|
5460
|
+
)) {
|
|
5461
|
+
return 5;
|
|
5462
|
+
}
|
|
5463
|
+
if (hasLargeToolResult(m)) return 1;
|
|
5464
|
+
if (m.role === "user" && !hasToolUse2(m) && /\b(files_with_matches|count|found \d+ match|directory tree|\.\.\. and \d+ more)\b/i.test(text)) {
|
|
5465
|
+
return 1;
|
|
5466
|
+
}
|
|
5467
|
+
return 3;
|
|
5468
|
+
}
|
|
5469
|
+
function buildSmartDigest(messages) {
|
|
5470
|
+
const lines = [];
|
|
5471
|
+
const failureCounts = /* @__PURE__ */ new Map();
|
|
5472
|
+
let noiseCount = 0;
|
|
5473
|
+
for (const m of messages) {
|
|
5474
|
+
const score = scoreMessage(m, { failureCounts });
|
|
5475
|
+
const text = extractText(m);
|
|
5476
|
+
const toolCount = countToolBlocks(m);
|
|
5477
|
+
if (score === 0) {
|
|
5478
|
+
noiseCount++;
|
|
5479
|
+
continue;
|
|
5480
|
+
}
|
|
5481
|
+
const marker = toolCount > 0 ? ` [${toolCount} tool call(s)]` : "";
|
|
5482
|
+
let display;
|
|
5483
|
+
switch (score) {
|
|
5484
|
+
case 5:
|
|
5485
|
+
display = text.trim();
|
|
5486
|
+
break;
|
|
5487
|
+
case 3:
|
|
5488
|
+
display = firstSentence(text);
|
|
5489
|
+
break;
|
|
5490
|
+
case 1:
|
|
5491
|
+
display = oneLineSummary(m, text);
|
|
5492
|
+
break;
|
|
5493
|
+
default:
|
|
5494
|
+
display = firstSentence(text);
|
|
5495
|
+
}
|
|
5496
|
+
if (display.length === 0 && toolCount === 0) continue;
|
|
5497
|
+
lines.push(`[${m.role}]: ${display}${marker}`);
|
|
5498
|
+
}
|
|
5499
|
+
if (noiseCount > 0) {
|
|
5500
|
+
lines.push(`[system]: ${noiseCount} low-importance turn(s) collapsed (repeated failures / pure tool I/O)`);
|
|
5268
5501
|
}
|
|
5269
|
-
|
|
5270
|
-
return getCachedEstimate(key, () => RoughTokenEstimate(key));
|
|
5271
|
-
}
|
|
5272
|
-
function estimateToolResultTokens(content) {
|
|
5273
|
-
if (typeof content === "string") return RoughTokenEstimate(content);
|
|
5274
|
-
const key = JSON.stringify(content);
|
|
5275
|
-
return getCachedEstimate(key, () => RoughTokenEstimate(key));
|
|
5502
|
+
return lines.join("\n");
|
|
5276
5503
|
}
|
|
5277
|
-
function
|
|
5278
|
-
|
|
5504
|
+
function countToolBlocks(m) {
|
|
5505
|
+
if (typeof m.content === "string") return 0;
|
|
5506
|
+
return m.content.filter(
|
|
5507
|
+
(b) => b.type === "tool_use" || b.type === "tool_result"
|
|
5508
|
+
).length;
|
|
5279
5509
|
}
|
|
5280
|
-
function
|
|
5281
|
-
|
|
5510
|
+
function firstSentence(text) {
|
|
5511
|
+
const trimmed = text.trim();
|
|
5512
|
+
if (trimmed.length === 0) return "";
|
|
5513
|
+
const dot = trimmed.indexOf(". ");
|
|
5514
|
+
if (dot === -1) return trimmed.length > 150 ? `${trimmed.slice(0, 147)}\u2026` : trimmed;
|
|
5515
|
+
const sentence = trimmed.slice(0, dot + 1);
|
|
5516
|
+
return sentence.length > 150 ? `${sentence.slice(0, 147)}\u2026` : sentence;
|
|
5282
5517
|
}
|
|
5283
|
-
function
|
|
5284
|
-
|
|
5285
|
-
if (
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
const content = m.content;
|
|
5291
|
-
if (typeof content === "string") {
|
|
5292
|
-
messagesTokens += RoughTokenEstimate(content);
|
|
5293
|
-
} else if (Array.isArray(content)) {
|
|
5294
|
-
for (const b of content) {
|
|
5295
|
-
if (typeof b === "object" && b !== null) {
|
|
5296
|
-
if (b.type === "text") {
|
|
5297
|
-
messagesTokens += RoughTokenEstimate(b.text);
|
|
5298
|
-
} else {
|
|
5299
|
-
messagesTokens += RoughTokenEstimate(JSON.stringify(b));
|
|
5300
|
-
}
|
|
5301
|
-
}
|
|
5302
|
-
}
|
|
5303
|
-
}
|
|
5518
|
+
function oneLineSummary(m, text) {
|
|
5519
|
+
const trimmed = text.trim();
|
|
5520
|
+
if (trimmed.length === 0) {
|
|
5521
|
+
if (typeof m.content !== "string") {
|
|
5522
|
+
const results = m.content.filter((b) => b.type === "tool_result");
|
|
5523
|
+
if (results.length > 0) {
|
|
5524
|
+
return `[${results.length} tool result(s) \u2014 see session log]`;
|
|
5304
5525
|
}
|
|
5305
5526
|
}
|
|
5527
|
+
return "[no text content]";
|
|
5306
5528
|
}
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5529
|
+
const firstLine = trimmed.split("\n")[0] ?? "";
|
|
5530
|
+
return firstLine.length > 100 ? `${firstLine.slice(0, 97)}\u2026` : firstLine;
|
|
5531
|
+
}
|
|
5532
|
+
function findSafeBoundary(messages, from, to) {
|
|
5533
|
+
for (let i = to; i >= from; i--) {
|
|
5534
|
+
const m = messages[i];
|
|
5535
|
+
if (!m) continue;
|
|
5536
|
+
if (m.role === "user" && hasTextContent(m)) {
|
|
5537
|
+
return findExchangeStart(messages, i);
|
|
5315
5538
|
}
|
|
5316
5539
|
}
|
|
5317
|
-
|
|
5318
|
-
for (const t of tools) {
|
|
5319
|
-
toolsTokens += estimateToolDefTokens(t);
|
|
5320
|
-
}
|
|
5321
|
-
const total = messagesTokens + systemTokens + toolsTokens;
|
|
5322
|
-
return {
|
|
5323
|
-
messages: messagesTokens,
|
|
5324
|
-
systemPrompt: systemTokens,
|
|
5325
|
-
tools: toolsTokens,
|
|
5326
|
-
total
|
|
5327
|
-
};
|
|
5540
|
+
return -1;
|
|
5328
5541
|
}
|
|
5329
|
-
function
|
|
5330
|
-
|
|
5331
|
-
|
|
5542
|
+
function findExchangeStart(messages, userIndex) {
|
|
5543
|
+
for (let i = userIndex - 1; i >= 0; i--) {
|
|
5544
|
+
const m = messages[i];
|
|
5545
|
+
if (!m) continue;
|
|
5546
|
+
if (m.role === "assistant") {
|
|
5547
|
+
const hasToolUse3 = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
|
|
5548
|
+
if (!hasToolUse3) return i + 1;
|
|
5549
|
+
} else if (m.role === "user") {
|
|
5550
|
+
return i;
|
|
5551
|
+
}
|
|
5552
|
+
}
|
|
5553
|
+
return 0;
|
|
5332
5554
|
}
|
|
5333
5555
|
|
|
5334
5556
|
// src/execution/compactor.ts
|
|
5335
5557
|
var HybridCompactor = class {
|
|
5336
5558
|
preserveK;
|
|
5337
5559
|
eliseThreshold;
|
|
5338
|
-
|
|
5560
|
+
smart;
|
|
5339
5561
|
constructor(opts = {}) {
|
|
5340
5562
|
this.preserveK = opts.preserveK ?? 5;
|
|
5341
5563
|
this.eliseThreshold = opts.eliseThreshold ?? 2e3;
|
|
5342
|
-
this.
|
|
5564
|
+
this.smart = opts.smart ?? false;
|
|
5343
5565
|
}
|
|
5344
5566
|
async compact(ctx, opts = {}) {
|
|
5345
|
-
const beforeTokens =
|
|
5567
|
+
const beforeTokens = estimateMessages(ctx.messages);
|
|
5346
5568
|
const beforeFull = this.estimateFullRequest(ctx);
|
|
5347
5569
|
const reductions = [];
|
|
5348
5570
|
const policy = readContextWindowPolicy(ctx);
|
|
5349
5571
|
const preserveK = policy?.preserveK ?? this.preserveK;
|
|
5350
5572
|
const eliseThreshold = policy?.eliseThreshold ?? this.eliseThreshold;
|
|
5351
|
-
const
|
|
5352
|
-
if (
|
|
5573
|
+
const elide = eliseOldToolResults(ctx.messages, { preserveK, eliseThreshold });
|
|
5574
|
+
if (elide.changed) ctx.state.replaceMessages(elide.messages);
|
|
5575
|
+
if (elide.saved > 0) reductions.push({ phase: "elision", saved: elide.saved });
|
|
5576
|
+
let collapsedDigest;
|
|
5353
5577
|
if (opts.aggressive) {
|
|
5354
|
-
const
|
|
5355
|
-
if (
|
|
5578
|
+
const phase2 = this.collapseAncientTurns(ctx, preserveK);
|
|
5579
|
+
if (phase2.saved > 0) reductions.push({ phase: "summary", saved: phase2.saved });
|
|
5580
|
+
collapsedDigest = phase2.digest;
|
|
5356
5581
|
}
|
|
5357
5582
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
5358
5583
|
if (repaired.report.changed) {
|
|
5359
5584
|
ctx.state.replaceMessages(repaired.messages);
|
|
5360
5585
|
}
|
|
5361
|
-
const afterTokens =
|
|
5586
|
+
const afterTokens = estimateMessages(ctx.messages);
|
|
5362
5587
|
const afterFull = this.estimateFullRequest(ctx);
|
|
5363
5588
|
return {
|
|
5364
5589
|
before: beforeTokens,
|
|
@@ -5366,6 +5591,7 @@ var HybridCompactor = class {
|
|
|
5366
5591
|
fullRequestTokensBefore: beforeFull,
|
|
5367
5592
|
fullRequestTokensAfter: afterFull,
|
|
5368
5593
|
reductions,
|
|
5594
|
+
collapsedDigest,
|
|
5369
5595
|
repaired: repaired.report.changed ? {
|
|
5370
5596
|
removedToolUses: repaired.report.removedToolUses,
|
|
5371
5597
|
removedToolResults: repaired.report.removedToolResults,
|
|
@@ -5378,72 +5604,23 @@ var HybridCompactor = class {
|
|
|
5378
5604
|
* This is the accurate figure for context-window pressure monitoring.
|
|
5379
5605
|
*/
|
|
5380
5606
|
estimateFullRequest(ctx) {
|
|
5381
|
-
|
|
5382
|
-
return breakdown.total;
|
|
5383
|
-
}
|
|
5384
|
-
eliseOldToolResults(ctx, preserveK = this.preserveK, eliseThreshold = this.eliseThreshold) {
|
|
5385
|
-
const messages = ctx.messages;
|
|
5386
|
-
let pairCount = 0;
|
|
5387
|
-
let preserveStart = messages.length;
|
|
5388
|
-
for (let i = messages.length - 1; i >= 0 && pairCount < preserveK; i--) {
|
|
5389
|
-
const m = messages[i];
|
|
5390
|
-
if (!m) continue;
|
|
5391
|
-
if (m.role === "user" || m.role === "assistant") {
|
|
5392
|
-
pairCount++;
|
|
5393
|
-
preserveStart = i;
|
|
5394
|
-
}
|
|
5395
|
-
}
|
|
5396
|
-
for (let i = preserveStart; i < messages.length; i++) {
|
|
5397
|
-
const m = messages[i];
|
|
5398
|
-
if (!m || typeof m.content === "string" || !Array.isArray(m.content)) continue;
|
|
5399
|
-
const hasToolUse2 = m.content.some((b) => b.type === "tool_use");
|
|
5400
|
-
if (hasToolUse2 && i + 1 < messages.length) {
|
|
5401
|
-
const next = messages[i + 1];
|
|
5402
|
-
if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => b.type === "tool_result")) {
|
|
5403
|
-
preserveStart = i + 1;
|
|
5404
|
-
}
|
|
5405
|
-
}
|
|
5406
|
-
}
|
|
5407
|
-
let saved = 0;
|
|
5408
|
-
let changed = false;
|
|
5409
|
-
const nextMessages = new Array(messages.length);
|
|
5410
|
-
for (let i = 0; i < messages.length; i++) {
|
|
5411
|
-
const msg = messages[i];
|
|
5412
|
-
if (i >= preserveStart) {
|
|
5413
|
-
nextMessages[i] = msg;
|
|
5414
|
-
continue;
|
|
5415
|
-
}
|
|
5416
|
-
if (!msg || !Array.isArray(msg.content)) {
|
|
5417
|
-
nextMessages[i] = msg;
|
|
5418
|
-
continue;
|
|
5419
|
-
}
|
|
5420
|
-
const newContent = msg.content.map((b) => {
|
|
5421
|
-
if (b.type !== "tool_result") return b;
|
|
5422
|
-
const tokens = estimateToolResultTokens(b.content);
|
|
5423
|
-
if (tokens < eliseThreshold) return b;
|
|
5424
|
-
saved += tokens;
|
|
5425
|
-
const elided = {
|
|
5426
|
-
type: "tool_result",
|
|
5427
|
-
tool_use_id: b.tool_use_id,
|
|
5428
|
-
content: `[elided: ~${tokens} tokens]`,
|
|
5429
|
-
is_error: b.is_error
|
|
5430
|
-
};
|
|
5431
|
-
return elided;
|
|
5432
|
-
});
|
|
5433
|
-
if (newContent.length === msg.content.length && newContent.every((b, idx) => b === msg.content[idx])) {
|
|
5434
|
-
nextMessages[i] = msg;
|
|
5435
|
-
} else {
|
|
5436
|
-
nextMessages[i] = { ...msg, content: newContent };
|
|
5437
|
-
changed = true;
|
|
5438
|
-
}
|
|
5439
|
-
}
|
|
5440
|
-
if (changed) ctx.state.replaceMessages(nextMessages);
|
|
5441
|
-
return saved;
|
|
5607
|
+
return estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
|
|
5442
5608
|
}
|
|
5609
|
+
/**
|
|
5610
|
+
* Lossless rule-based collapse of ancient turns into a single digest message.
|
|
5611
|
+
*
|
|
5612
|
+
* Preserves ALL textual content of the collapsed range — user instructions,
|
|
5613
|
+
* assistant decisions/conclusions, and any prior digests (chained forward so
|
|
5614
|
+
* the digest stays lossless across repeated compactions). Only `tool_use` /
|
|
5615
|
+
* `tool_result` protocol blocks are dropped and replaced with a count marker;
|
|
5616
|
+
* their full payload already lives in the session log. No sub-LLM call.
|
|
5617
|
+
*
|
|
5618
|
+
* Returns the token savings and the digest text (for audit logging).
|
|
5619
|
+
*/
|
|
5443
5620
|
collapseAncientTurns(ctx, preserveK = this.preserveK) {
|
|
5444
5621
|
const messages = ctx.messages;
|
|
5445
5622
|
const cutTarget = Math.max(0, messages.length - preserveK * 2);
|
|
5446
|
-
if (cutTarget <= 0) return 0;
|
|
5623
|
+
if (cutTarget <= 0) return { saved: 0 };
|
|
5447
5624
|
let boundary = -1;
|
|
5448
5625
|
for (let i = cutTarget; i < messages.length; i++) {
|
|
5449
5626
|
const m = messages[i];
|
|
@@ -5453,34 +5630,20 @@ var HybridCompactor = class {
|
|
|
5453
5630
|
break;
|
|
5454
5631
|
}
|
|
5455
5632
|
}
|
|
5456
|
-
if (boundary <= 0) return 0;
|
|
5633
|
+
if (boundary <= 0) return { saved: 0 };
|
|
5457
5634
|
const removed = messages.slice(0, boundary);
|
|
5458
|
-
const removedTokens =
|
|
5459
|
-
const
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
{ role: "assistant", content: "Continuing from compacted context." }
|
|
5465
|
-
];
|
|
5635
|
+
const removedTokens = estimateMessages(removed);
|
|
5636
|
+
const digest = this.smart ? buildSmartDigest(removed) || `${removed.length} earlier turns (no textual content; tool I/O omitted \u2014 see session log)` : buildLosslessDigest(removed) || `${removed.length} earlier turns (no textual content; tool I/O omitted \u2014 see session log)`;
|
|
5637
|
+
const summaryMsg = {
|
|
5638
|
+
role: "system",
|
|
5639
|
+
content: `[prior_turns_digest: ${digest}]`
|
|
5640
|
+
};
|
|
5466
5641
|
const tail = ctx.messages.slice(boundary);
|
|
5467
|
-
ctx.state.replaceMessages([
|
|
5468
|
-
return
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
for (const m of messages) {
|
|
5473
|
-
if (typeof m.content === "string") {
|
|
5474
|
-
total += this.estimator(m.content);
|
|
5475
|
-
} else {
|
|
5476
|
-
for (const b of m.content) {
|
|
5477
|
-
if (b.type === "text") total += this.estimator(b.text);
|
|
5478
|
-
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
5479
|
-
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
5480
|
-
}
|
|
5481
|
-
}
|
|
5482
|
-
}
|
|
5483
|
-
return total;
|
|
5642
|
+
ctx.state.replaceMessages([summaryMsg, ...tail]);
|
|
5643
|
+
return {
|
|
5644
|
+
saved: Math.max(0, removedTokens - estimateMessages([summaryMsg])),
|
|
5645
|
+
digest
|
|
5646
|
+
};
|
|
5484
5647
|
}
|
|
5485
5648
|
};
|
|
5486
5649
|
function readContextWindowPolicy(ctx) {
|
|
@@ -5492,15 +5655,6 @@ function readContextWindowPolicy(ctx) {
|
|
|
5492
5655
|
}
|
|
5493
5656
|
return candidate;
|
|
5494
5657
|
}
|
|
5495
|
-
function hasTextContent(m) {
|
|
5496
|
-
if (typeof m.content === "string") return m.content.trim().length > 0;
|
|
5497
|
-
return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
|
|
5498
|
-
}
|
|
5499
|
-
|
|
5500
|
-
// src/types/blocks.ts
|
|
5501
|
-
function isTextBlock(b) {
|
|
5502
|
-
return b.type === "text";
|
|
5503
|
-
}
|
|
5504
5658
|
|
|
5505
5659
|
// src/execution/intelligent-compactor.ts
|
|
5506
5660
|
var IntelligentCompactor = class {
|
|
@@ -5525,23 +5679,25 @@ var IntelligentCompactor = class {
|
|
|
5525
5679
|
this.summarizerModel = opts.summarizerModel;
|
|
5526
5680
|
}
|
|
5527
5681
|
async compact(ctx, opts = {}) {
|
|
5528
|
-
const beforeTokens =
|
|
5682
|
+
const beforeTokens = estimateMessages(ctx.messages);
|
|
5529
5683
|
const beforeFull = this.estimateFullRequest(ctx);
|
|
5530
5684
|
const reductions = [];
|
|
5531
5685
|
const load = beforeFull / this.maxContext;
|
|
5532
5686
|
const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
|
|
5533
|
-
const saved1 = this.
|
|
5687
|
+
const saved1 = this.elide(ctx);
|
|
5534
5688
|
if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
|
|
5689
|
+
let collapsedDigest;
|
|
5535
5690
|
if (aggressive) {
|
|
5536
|
-
const
|
|
5537
|
-
if (
|
|
5691
|
+
const phase2 = await this.summarizeAncientTurns(ctx);
|
|
5692
|
+
if (phase2.saved > 0) reductions.push({ phase: "summary", saved: phase2.saved });
|
|
5693
|
+
collapsedDigest = phase2.digest;
|
|
5538
5694
|
} else if (load >= this.warnThreshold) {
|
|
5539
|
-
const saved2 = this.
|
|
5695
|
+
const saved2 = this.elide(ctx);
|
|
5540
5696
|
if (saved2 > 0) reductions.push({ phase: "elision", saved: saved2 });
|
|
5541
5697
|
}
|
|
5542
5698
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
5543
5699
|
if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
|
|
5544
|
-
const afterTokens =
|
|
5700
|
+
const afterTokens = estimateMessages(ctx.messages);
|
|
5545
5701
|
const afterFull = this.estimateFullRequest(ctx);
|
|
5546
5702
|
return {
|
|
5547
5703
|
before: beforeTokens,
|
|
@@ -5549,6 +5705,7 @@ var IntelligentCompactor = class {
|
|
|
5549
5705
|
fullRequestTokensBefore: beforeFull,
|
|
5550
5706
|
fullRequestTokensAfter: afterFull,
|
|
5551
5707
|
reductions,
|
|
5708
|
+
collapsedDigest,
|
|
5552
5709
|
repaired: repaired.report.changed ? {
|
|
5553
5710
|
removedToolUses: repaired.report.removedToolUses,
|
|
5554
5711
|
removedToolResults: repaired.report.removedToolResults,
|
|
@@ -5561,75 +5718,39 @@ var IntelligentCompactor = class {
|
|
|
5561
5718
|
* This is the accurate figure for context-window pressure monitoring.
|
|
5562
5719
|
*/
|
|
5563
5720
|
estimateFullRequest(ctx) {
|
|
5564
|
-
|
|
5565
|
-
|
|
5721
|
+
return estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
|
|
5722
|
+
}
|
|
5723
|
+
/** Run shared tool-result elision and commit through ConversationState. */
|
|
5724
|
+
elide(ctx) {
|
|
5725
|
+
const result = eliseOldToolResults(ctx.messages, {
|
|
5726
|
+
preserveK: this.preserveK,
|
|
5727
|
+
eliseThreshold: this.eliseThreshold
|
|
5728
|
+
});
|
|
5729
|
+
if (result.changed) ctx.state.replaceMessages(result.messages);
|
|
5730
|
+
return result.saved;
|
|
5566
5731
|
}
|
|
5567
5732
|
async summarizeAncientTurns(ctx) {
|
|
5568
5733
|
const messages = ctx.messages;
|
|
5569
5734
|
const cutoff = Math.max(0, messages.length - this.preserveK * 2);
|
|
5570
|
-
if (cutoff <= 2) return 0;
|
|
5571
|
-
const boundary =
|
|
5572
|
-
if (boundary <= 1) return 0;
|
|
5735
|
+
if (cutoff <= 2) return { saved: 0 };
|
|
5736
|
+
const boundary = findSafeBoundary(messages, 0, cutoff);
|
|
5737
|
+
if (boundary <= 1) return { saved: 0 };
|
|
5573
5738
|
const toSummarize = messages.slice(0, boundary);
|
|
5574
|
-
const removedTokens =
|
|
5739
|
+
const removedTokens = estimateMessages(toSummarize);
|
|
5575
5740
|
let summaryText;
|
|
5576
5741
|
try {
|
|
5577
5742
|
summaryText = await this.callSummarizer(toSummarize, ctx);
|
|
5578
5743
|
} catch {
|
|
5579
|
-
|
|
5580
|
-
for (const m of toSummarize) {
|
|
5581
|
-
if (m.role === "system") continue;
|
|
5582
|
-
if (typeof m.content === "string") {
|
|
5583
|
-
preservedMessages.push(m);
|
|
5584
|
-
} else if (Array.isArray(m.content)) {
|
|
5585
|
-
const textParts = m.content.filter(isTextBlock);
|
|
5586
|
-
const toolParts = m.content.filter((b) => b.type === "tool_use" || b.type === "tool_result");
|
|
5587
|
-
const next = {
|
|
5588
|
-
role: m.role,
|
|
5589
|
-
content: [
|
|
5590
|
-
...textParts,
|
|
5591
|
-
...toolParts.length > 0 ? [{ type: "text", text: `[${toolParts.length} tool call(s) omitted]` }] : []
|
|
5592
|
-
]
|
|
5593
|
-
};
|
|
5594
|
-
preservedMessages.push(next);
|
|
5595
|
-
}
|
|
5596
|
-
}
|
|
5597
|
-
summaryText = preservedMessages.map((m) => `[${m.role}]: ${typeof m.content === "string" ? m.content : m.content.filter(isTextBlock).map((b) => b.text).join(" ")}`).join("\n");
|
|
5598
|
-
if (!summaryText) summaryText = `${toSummarize.length} earlier turns (semantic content preserved)`;
|
|
5744
|
+
summaryText = buildLosslessDigest(toSummarize) || `${toSummarize.length} earlier turns (semantic content preserved)`;
|
|
5599
5745
|
}
|
|
5600
5746
|
const summaryMsg = {
|
|
5601
5747
|
role: "system",
|
|
5602
5748
|
content: `[prior_turns_summary: ${summaryText}]`
|
|
5603
5749
|
};
|
|
5604
|
-
const summaryTokens =
|
|
5750
|
+
const summaryTokens = estimateMessages([summaryMsg]);
|
|
5605
5751
|
const tail = ctx.messages.slice(boundary);
|
|
5606
5752
|
ctx.state.replaceMessages([summaryMsg, ...tail]);
|
|
5607
|
-
return Math.max(0, removedTokens - summaryTokens);
|
|
5608
|
-
}
|
|
5609
|
-
findSafeBoundary(messages, from, to) {
|
|
5610
|
-
for (let i = to; i >= from; i--) {
|
|
5611
|
-
const m = messages[i];
|
|
5612
|
-
if (!m) continue;
|
|
5613
|
-
if (m.role === "user" && this.hasTextContent(m)) {
|
|
5614
|
-
return this.findExchangeStart(messages, i);
|
|
5615
|
-
}
|
|
5616
|
-
}
|
|
5617
|
-
return -1;
|
|
5618
|
-
}
|
|
5619
|
-
findExchangeStart(messages, userIndex) {
|
|
5620
|
-
for (let i = userIndex - 1; i >= 0; i--) {
|
|
5621
|
-
const m = messages[i];
|
|
5622
|
-
if (!m) continue;
|
|
5623
|
-
if (m.role === "assistant") {
|
|
5624
|
-
const hasToolUse2 = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
|
|
5625
|
-
if (!hasToolUse2) {
|
|
5626
|
-
return i + 1;
|
|
5627
|
-
}
|
|
5628
|
-
} else if (m.role !== "user") ; else {
|
|
5629
|
-
return i;
|
|
5630
|
-
}
|
|
5631
|
-
}
|
|
5632
|
-
return 0;
|
|
5753
|
+
return { saved: Math.max(0, removedTokens - summaryTokens), digest: summaryText };
|
|
5633
5754
|
}
|
|
5634
5755
|
async callSummarizer(messages, ctx) {
|
|
5635
5756
|
const prompt = [
|
|
@@ -5664,86 +5785,6 @@ var IntelligentCompactor = class {
|
|
|
5664
5785
|
}
|
|
5665
5786
|
return [{ type: "text", text: lines.join("\n") }];
|
|
5666
5787
|
}
|
|
5667
|
-
lightweightCompact(ctx) {
|
|
5668
|
-
return this.eliseOldToolResults(ctx);
|
|
5669
|
-
}
|
|
5670
|
-
eliseOldToolResults(ctx) {
|
|
5671
|
-
const messages = ctx.messages;
|
|
5672
|
-
let pairCount = 0;
|
|
5673
|
-
let preserveStart = messages.length;
|
|
5674
|
-
for (let i = messages.length - 1; i >= 0 && pairCount < this.preserveK; i--) {
|
|
5675
|
-
const m = messages[i];
|
|
5676
|
-
if (!m) continue;
|
|
5677
|
-
if (m.role === "user" || m.role === "assistant") {
|
|
5678
|
-
pairCount++;
|
|
5679
|
-
preserveStart = i;
|
|
5680
|
-
}
|
|
5681
|
-
}
|
|
5682
|
-
for (let i = preserveStart; i < messages.length; i++) {
|
|
5683
|
-
const m = messages[i];
|
|
5684
|
-
if (!m || typeof m.content === "string" || !Array.isArray(m.content)) continue;
|
|
5685
|
-
const hasToolUse2 = m.content.some((b) => b.type === "tool_use");
|
|
5686
|
-
if (hasToolUse2 && i + 1 < messages.length) {
|
|
5687
|
-
const next = messages[i + 1];
|
|
5688
|
-
if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => b.type === "tool_result")) {
|
|
5689
|
-
preserveStart = i + 1;
|
|
5690
|
-
}
|
|
5691
|
-
}
|
|
5692
|
-
}
|
|
5693
|
-
let saved = 0;
|
|
5694
|
-
let changed = false;
|
|
5695
|
-
const nextMessages = new Array(messages.length);
|
|
5696
|
-
for (let i = 0; i < messages.length; i++) {
|
|
5697
|
-
const msg = messages[i];
|
|
5698
|
-
if (i >= preserveStart) {
|
|
5699
|
-
nextMessages[i] = msg;
|
|
5700
|
-
continue;
|
|
5701
|
-
}
|
|
5702
|
-
if (!msg || !Array.isArray(msg.content)) {
|
|
5703
|
-
nextMessages[i] = msg;
|
|
5704
|
-
continue;
|
|
5705
|
-
}
|
|
5706
|
-
const newContent = msg.content.map((b) => {
|
|
5707
|
-
if (b.type !== "tool_result") return b;
|
|
5708
|
-
const tokens = estimateToolResultTokens(b.content);
|
|
5709
|
-
if (tokens < this.eliseThreshold) return b;
|
|
5710
|
-
saved += tokens;
|
|
5711
|
-
return {
|
|
5712
|
-
type: "tool_result",
|
|
5713
|
-
tool_use_id: b.tool_use_id,
|
|
5714
|
-
content: `[elided: ~${tokens} tokens]`,
|
|
5715
|
-
is_error: b.is_error
|
|
5716
|
-
};
|
|
5717
|
-
});
|
|
5718
|
-
if (newContent.length === msg.content.length && newContent.every((b, idx) => b === msg.content[idx])) {
|
|
5719
|
-
nextMessages[i] = msg;
|
|
5720
|
-
} else {
|
|
5721
|
-
nextMessages[i] = { ...msg, content: newContent };
|
|
5722
|
-
changed = true;
|
|
5723
|
-
}
|
|
5724
|
-
}
|
|
5725
|
-
if (changed) ctx.state.replaceMessages(nextMessages);
|
|
5726
|
-
return saved;
|
|
5727
|
-
}
|
|
5728
|
-
hasTextContent(m) {
|
|
5729
|
-
if (typeof m.content === "string") return m.content.trim().length > 0;
|
|
5730
|
-
return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
|
|
5731
|
-
}
|
|
5732
|
-
estimateTokens(messages) {
|
|
5733
|
-
let total = 0;
|
|
5734
|
-
for (const m of messages) {
|
|
5735
|
-
if (typeof m.content === "string") {
|
|
5736
|
-
total += estimateTextTokens(m.content);
|
|
5737
|
-
} else {
|
|
5738
|
-
for (const b of m.content) {
|
|
5739
|
-
if (b.type === "text") total += estimateTextTokens(b.text);
|
|
5740
|
-
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
5741
|
-
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
5742
|
-
}
|
|
5743
|
-
}
|
|
5744
|
-
}
|
|
5745
|
-
return total;
|
|
5746
|
-
}
|
|
5747
5788
|
};
|
|
5748
5789
|
|
|
5749
5790
|
// src/models/llm-selector.ts
|
|
@@ -5769,20 +5810,6 @@ Rules:
|
|
|
5769
5810
|
- If unsure, keep rather than collapse (errors are more costly than waste)
|
|
5770
5811
|
|
|
5771
5812
|
Return ONLY the JSON object, no markdown, no explanation outside the JSON.`;
|
|
5772
|
-
function estimateTokens(messages) {
|
|
5773
|
-
let total = 0;
|
|
5774
|
-
for (const m of messages) {
|
|
5775
|
-
if (typeof m.content === "string") {
|
|
5776
|
-
total += Math.ceil(m.content.length / 4);
|
|
5777
|
-
} else if (Array.isArray(m.content)) {
|
|
5778
|
-
for (const b of m.content) {
|
|
5779
|
-
if (b.type === "text") total += Math.ceil(b.text.length / 4);
|
|
5780
|
-
else total += Math.ceil(JSON.stringify(b).length / 4);
|
|
5781
|
-
}
|
|
5782
|
-
}
|
|
5783
|
-
}
|
|
5784
|
-
return total;
|
|
5785
|
-
}
|
|
5786
5813
|
function formatMessages(messages, maxChars = 8e3) {
|
|
5787
5814
|
const lines = [];
|
|
5788
5815
|
let used = 0;
|
|
@@ -5821,7 +5848,7 @@ var LLMSelector = class {
|
|
|
5821
5848
|
async select(messages, maxToKeep) {
|
|
5822
5849
|
const effectiveBudget = Math.min(maxToKeep, this.maxContextTokens);
|
|
5823
5850
|
const historyText = formatMessages(messages);
|
|
5824
|
-
const totalTokens =
|
|
5851
|
+
const totalTokens = estimateMessageTokens(messages);
|
|
5825
5852
|
const systemText = `${this.systemPrompt}
|
|
5826
5853
|
|
|
5827
5854
|
Conversation (${messages.length} messages, ~${totalTokens} tokens, budget: ${effectiveBudget}):
|
|
@@ -5853,10 +5880,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
5853
5880
|
let startIdx = 0;
|
|
5854
5881
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
5855
5882
|
const m = expectDefined(messages[i]);
|
|
5856
|
-
const cost =
|
|
5857
|
-
(acc, b) => acc + (b.type === "text" ? Math.ceil(b.text.length / 4) : Math.ceil(JSON.stringify(b).length / 4)),
|
|
5858
|
-
0
|
|
5859
|
-
);
|
|
5883
|
+
const cost = estimateMessageTokens([m]);
|
|
5860
5884
|
if (tokenCount + cost <= budget) {
|
|
5861
5885
|
tokenCount += cost;
|
|
5862
5886
|
} else {
|
|
@@ -6089,52 +6113,12 @@ Summarize the following message range:`;
|
|
|
6089
6113
|
return Math.floor(this.maxContext * 0.75);
|
|
6090
6114
|
}
|
|
6091
6115
|
eliseOldToolResults(ctx) {
|
|
6092
|
-
const
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
if (m.role === "user" || m.role === "assistant") {
|
|
6099
|
-
pairCount++;
|
|
6100
|
-
preserveStart = i;
|
|
6101
|
-
}
|
|
6102
|
-
}
|
|
6103
|
-
let saved = 0;
|
|
6104
|
-
let changed = false;
|
|
6105
|
-
const nextMessages = new Array(messages.length);
|
|
6106
|
-
for (let i = 0; i < messages.length; i++) {
|
|
6107
|
-
const msg = messages[i];
|
|
6108
|
-
if (i >= preserveStart) {
|
|
6109
|
-
nextMessages[i] = msg;
|
|
6110
|
-
continue;
|
|
6111
|
-
}
|
|
6112
|
-
if (!msg || !Array.isArray(msg.content)) {
|
|
6113
|
-
nextMessages[i] = msg;
|
|
6114
|
-
continue;
|
|
6115
|
-
}
|
|
6116
|
-
const newContent = msg.content.map((b) => {
|
|
6117
|
-
if (b.type !== "tool_result") return b;
|
|
6118
|
-
const text = typeof b.content === "string" ? b.content : JSON.stringify(b.content);
|
|
6119
|
-
const tokens = this.roughTokenEstimate(text);
|
|
6120
|
-
if (tokens < this.eliseThreshold) return b;
|
|
6121
|
-
saved += tokens;
|
|
6122
|
-
return {
|
|
6123
|
-
type: "tool_result",
|
|
6124
|
-
tool_use_id: b.tool_use_id,
|
|
6125
|
-
content: `[elided: ~${tokens} tokens]`,
|
|
6126
|
-
is_error: b.is_error
|
|
6127
|
-
};
|
|
6128
|
-
});
|
|
6129
|
-
if (newContent.every((b, idx) => b === msg.content[idx])) {
|
|
6130
|
-
nextMessages[i] = msg;
|
|
6131
|
-
} else {
|
|
6132
|
-
nextMessages[i] = { ...msg, content: newContent };
|
|
6133
|
-
changed = true;
|
|
6134
|
-
}
|
|
6135
|
-
}
|
|
6136
|
-
if (changed) ctx.state.replaceMessages(nextMessages);
|
|
6137
|
-
return saved;
|
|
6116
|
+
const result = eliseOldToolResults(ctx.messages, {
|
|
6117
|
+
preserveK: this.preserveK,
|
|
6118
|
+
eliseThreshold: this.eliseThreshold
|
|
6119
|
+
});
|
|
6120
|
+
if (result.changed) ctx.state.replaceMessages(result.messages);
|
|
6121
|
+
return result.saved;
|
|
6138
6122
|
}
|
|
6139
6123
|
hasTextContent(m) {
|
|
6140
6124
|
if (typeof m.content === "string") return m.content.trim().length > 0;
|
|
@@ -6164,8 +6148,81 @@ Summarize the following message range:`;
|
|
|
6164
6148
|
}
|
|
6165
6149
|
};
|
|
6166
6150
|
|
|
6151
|
+
// src/execution/strategy-compactor.ts
|
|
6152
|
+
function createStrategyCompactor(opts = {}) {
|
|
6153
|
+
const requested = opts.strategy ?? (opts.llmSelector ? "selective" : "hybrid");
|
|
6154
|
+
const strategy = requested;
|
|
6155
|
+
if (strategy === "intelligent" || strategy === "selective") {
|
|
6156
|
+
return new ProviderBackedCompactor(strategy, opts);
|
|
6157
|
+
}
|
|
6158
|
+
return new HybridCompactor({
|
|
6159
|
+
preserveK: opts.preserveK,
|
|
6160
|
+
eliseThreshold: opts.eliseThreshold,
|
|
6161
|
+
smart: opts.smart
|
|
6162
|
+
});
|
|
6163
|
+
}
|
|
6164
|
+
var ProviderBackedCompactor = class {
|
|
6165
|
+
constructor(strategy, opts) {
|
|
6166
|
+
this.strategy = strategy;
|
|
6167
|
+
this.opts = opts;
|
|
6168
|
+
}
|
|
6169
|
+
strategy;
|
|
6170
|
+
opts;
|
|
6171
|
+
async compact(ctx, compactOpts = {}) {
|
|
6172
|
+
return this.resolveInner(ctx).compact(ctx, compactOpts);
|
|
6173
|
+
}
|
|
6174
|
+
/**
|
|
6175
|
+
* Construct the concrete compactor for this run. Rebuilt per call (cheap, no
|
|
6176
|
+
* I/O) so a model switch — which changes `ctx.provider.capabilities.maxContext`
|
|
6177
|
+
* — is always reflected. Reads the active ContextWindowPolicy from `ctx.meta`
|
|
6178
|
+
* so the LLM compactors honor the same thresholds/preserveK as the policy.
|
|
6179
|
+
*/
|
|
6180
|
+
resolveInner(ctx) {
|
|
6181
|
+
const provider = ctx.provider;
|
|
6182
|
+
if (!provider) {
|
|
6183
|
+
return new HybridCompactor({
|
|
6184
|
+
preserveK: this.opts.preserveK,
|
|
6185
|
+
eliseThreshold: this.opts.eliseThreshold
|
|
6186
|
+
});
|
|
6187
|
+
}
|
|
6188
|
+
const policy = readPolicy(ctx);
|
|
6189
|
+
const maxContext = provider.capabilities?.maxContext || void 0;
|
|
6190
|
+
const thresholds = policy?.thresholds;
|
|
6191
|
+
const common = {
|
|
6192
|
+
provider,
|
|
6193
|
+
maxContext,
|
|
6194
|
+
preserveK: this.opts.preserveK ?? policy?.preserveK,
|
|
6195
|
+
eliseThreshold: this.opts.eliseThreshold ?? policy?.eliseThreshold,
|
|
6196
|
+
...thresholds ? { warnThreshold: thresholds.warn, softThreshold: thresholds.soft, hardThreshold: thresholds.hard } : {}
|
|
6197
|
+
};
|
|
6198
|
+
if (this.strategy === "selective") {
|
|
6199
|
+
return new SelectiveCompactor({
|
|
6200
|
+
...common,
|
|
6201
|
+
selectorModel: this.opts.summarizerModel,
|
|
6202
|
+
summarizerModel: this.opts.summarizerModel
|
|
6203
|
+
});
|
|
6204
|
+
}
|
|
6205
|
+
return new IntelligentCompactor({
|
|
6206
|
+
...common,
|
|
6207
|
+
summarizerModel: this.opts.summarizerModel
|
|
6208
|
+
});
|
|
6209
|
+
}
|
|
6210
|
+
};
|
|
6211
|
+
function readPolicy(ctx) {
|
|
6212
|
+
const policy = ctx.meta?.["contextWindowPolicy"];
|
|
6213
|
+
if (!policy || typeof policy !== "object") return null;
|
|
6214
|
+
const candidate = policy;
|
|
6215
|
+
if (typeof candidate.preserveK !== "number" || !candidate.thresholds) return null;
|
|
6216
|
+
return candidate;
|
|
6217
|
+
}
|
|
6218
|
+
|
|
6167
6219
|
// src/execution/auto-compaction-middleware.ts
|
|
6168
6220
|
var LEVEL_RANK2 = { warn: 0, soft: 1, hard: 2 };
|
|
6221
|
+
var MAX_DIGEST_LOG_CHARS = 4e3;
|
|
6222
|
+
function truncateDigest(digest) {
|
|
6223
|
+
if (digest.length <= MAX_DIGEST_LOG_CHARS) return digest;
|
|
6224
|
+
return `${digest.slice(0, MAX_DIGEST_LOG_CHARS)}\u2026 [+${digest.length - MAX_DIGEST_LOG_CHARS} chars; full turns in session log]`;
|
|
6225
|
+
}
|
|
6169
6226
|
var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
6170
6227
|
name = "AutoCompaction";
|
|
6171
6228
|
compactor;
|
|
@@ -6226,7 +6283,12 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
6226
6283
|
}
|
|
6227
6284
|
handler() {
|
|
6228
6285
|
return async (ctx, next) => {
|
|
6229
|
-
const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokensCalibrated(
|
|
6286
|
+
const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokensCalibrated(
|
|
6287
|
+
ctx.messages,
|
|
6288
|
+
ctx.systemPrompt,
|
|
6289
|
+
ctx.tools ?? [],
|
|
6290
|
+
`${ctx.provider?.id ?? "unknown"}/${ctx.model}`
|
|
6291
|
+
).total;
|
|
6230
6292
|
const load = tokens / this._maxContext;
|
|
6231
6293
|
const policy = this.policyProvider?.(ctx);
|
|
6232
6294
|
const thresholds = policy?.thresholds ?? {
|
|
@@ -6288,7 +6350,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
6288
6350
|
after: report.after,
|
|
6289
6351
|
level: pressure.level,
|
|
6290
6352
|
aggressive,
|
|
6291
|
-
reductions: report.reductions?.map((r) => ({ phase: r.phase, saved: r.saved }))
|
|
6353
|
+
reductions: report.reductions?.map((r) => ({ phase: r.phase, saved: r.saved })),
|
|
6354
|
+
// Record what was collapsed so the audit trail shows the preserved
|
|
6355
|
+
// content, not just token counts. Bounded to keep the log line small;
|
|
6356
|
+
// the full original turns are already in the session JSONL.
|
|
6357
|
+
...report.collapsedDigest ? { digest: truncateDigest(report.collapsedDigest) } : {}
|
|
6292
6358
|
});
|
|
6293
6359
|
ctx.clearFileTracking();
|
|
6294
6360
|
} catch (err) {
|
|
@@ -6323,10 +6389,10 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
6323
6389
|
// src/utils/json-schema-validate.ts
|
|
6324
6390
|
function validateAgainstSchema(value, schema) {
|
|
6325
6391
|
const errors = [];
|
|
6326
|
-
|
|
6392
|
+
walk2(value, schema, "", errors);
|
|
6327
6393
|
return { ok: errors.length === 0, errors };
|
|
6328
6394
|
}
|
|
6329
|
-
function
|
|
6395
|
+
function walk2(value, schema, path19, errors) {
|
|
6330
6396
|
if (schema.enum !== void 0) {
|
|
6331
6397
|
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
6332
6398
|
errors.push({
|
|
@@ -6355,13 +6421,13 @@ function walk3(value, schema, path19, errors) {
|
|
|
6355
6421
|
if (schema.properties) {
|
|
6356
6422
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
6357
6423
|
if (key in obj) {
|
|
6358
|
-
|
|
6424
|
+
walk2(obj[key], subSchema, joinPath(path19, key), errors);
|
|
6359
6425
|
}
|
|
6360
6426
|
}
|
|
6361
6427
|
}
|
|
6362
6428
|
}
|
|
6363
6429
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
6364
|
-
value.forEach((item, i) =>
|
|
6430
|
+
value.forEach((item, i) => walk2(item, schema.items, `${path19}[${i}]`, errors));
|
|
6365
6431
|
}
|
|
6366
6432
|
}
|
|
6367
6433
|
function checkType(value, type) {
|
|
@@ -6824,7 +6890,7 @@ ${excerpt}`;
|
|
|
6824
6890
|
subjectFor(toolName, input, subjectKey) {
|
|
6825
6891
|
if (!input || typeof input !== "object") return void 0;
|
|
6826
6892
|
const obj = input;
|
|
6827
|
-
const globChars = /[
|
|
6893
|
+
const globChars = /[*?[\]]/g;
|
|
6828
6894
|
const escapeGlob = (s) => s.replace(globChars, (c) => `\\${c}`);
|
|
6829
6895
|
const normalizePath = (s) => escapeGlob(s.replace(/\\/g, "/"));
|
|
6830
6896
|
if (subjectKey) {
|
|
@@ -6875,6 +6941,13 @@ function extractMalformedRaw(input) {
|
|
|
6875
6941
|
}
|
|
6876
6942
|
}
|
|
6877
6943
|
|
|
6944
|
+
// src/utils/assert-never.ts
|
|
6945
|
+
function assertNever(x, message) {
|
|
6946
|
+
throw new Error(
|
|
6947
|
+
`Unhandled case: ${JSON.stringify(x)}`
|
|
6948
|
+
);
|
|
6949
|
+
}
|
|
6950
|
+
|
|
6878
6951
|
// src/execution/autonomous-runner.ts
|
|
6879
6952
|
var DoneConditionChecker = class {
|
|
6880
6953
|
constructor(condition) {
|
|
@@ -6928,6 +7001,12 @@ var DoneConditionChecker = class {
|
|
|
6928
7001
|
return { done: true, reason: `max tool calls (${this.condition.maxToolCalls}) reached`, ...state };
|
|
6929
7002
|
}
|
|
6930
7003
|
break;
|
|
7004
|
+
case "all_tasks_done":
|
|
7005
|
+
break;
|
|
7006
|
+
case "custom":
|
|
7007
|
+
break;
|
|
7008
|
+
default:
|
|
7009
|
+
return assertNever(this.condition.type);
|
|
6931
7010
|
}
|
|
6932
7011
|
return { done: false, iterations: state.iterations, toolCalls: state.toolCalls };
|
|
6933
7012
|
}
|
|
@@ -7147,12 +7226,12 @@ function appendJournal(goal, entry) {
|
|
|
7147
7226
|
};
|
|
7148
7227
|
}
|
|
7149
7228
|
function parseProgressFromText(text) {
|
|
7150
|
-
const re = /\[progress:\s*(\d{1,3})%\]\s*(?:[
|
|
7229
|
+
const re = /\[progress:\s*(\d{1,3})%\]\s*(?:[—-]\s*(.+))?/i;
|
|
7151
7230
|
const m = text.match(re);
|
|
7152
7231
|
if (!m) return null;
|
|
7153
|
-
const progress = Math.min(100, Math.max(0, Number.parseInt(m[1], 10)));
|
|
7232
|
+
const progress = Math.min(100, Math.max(0, Number.parseInt(m[1] ?? "0", 10)));
|
|
7154
7233
|
const note = m[2]?.trim() || void 0;
|
|
7155
|
-
return { progress, note };
|
|
7234
|
+
return note === void 0 ? { progress } : { progress, note };
|
|
7156
7235
|
}
|
|
7157
7236
|
function recordProgress(goal, progress, note) {
|
|
7158
7237
|
const clamped = Math.min(100, Math.max(0, progress));
|
|
@@ -7750,6 +7829,7 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
7750
7829
|
note: note.slice(0, 240)
|
|
7751
7830
|
});
|
|
7752
7831
|
await saveGoal(this.goalPath, withEntry);
|
|
7832
|
+
this.opts.onEternalStop?.();
|
|
7753
7833
|
}
|
|
7754
7834
|
/**
|
|
7755
7835
|
* Manually clear the goal — equivalent to `/goal clear` typed by the user.
|
|
@@ -12886,14 +12966,14 @@ async function expandGlob(pattern) {
|
|
|
12886
12966
|
const abs = isAbsolute(pattern);
|
|
12887
12967
|
const base = abs ? baseDir(pattern) : baseDir(pattern);
|
|
12888
12968
|
const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
|
|
12889
|
-
async function
|
|
12969
|
+
async function walk3(dir, pat) {
|
|
12890
12970
|
let entries;
|
|
12891
12971
|
try {
|
|
12892
12972
|
entries = await fsp.readdir(dir);
|
|
12893
12973
|
} catch {
|
|
12894
12974
|
return;
|
|
12895
12975
|
}
|
|
12896
|
-
const firstGlob = pat.search(/[*?[
|
|
12976
|
+
const firstGlob = pat.search(/[*?[[]/);
|
|
12897
12977
|
if (firstGlob < 0) {
|
|
12898
12978
|
const re = globToRegex(pat);
|
|
12899
12979
|
for (const e of entries) {
|
|
@@ -12907,12 +12987,12 @@ async function expandGlob(pattern) {
|
|
|
12907
12987
|
const before = pat.slice(0, firstGlob);
|
|
12908
12988
|
const rest = pat.slice(firstGlob);
|
|
12909
12989
|
if (before.endsWith("**")) {
|
|
12910
|
-
await
|
|
12990
|
+
await walk3(dir, rest);
|
|
12911
12991
|
for (const e of entries) {
|
|
12912
12992
|
const full = `${dir}${SEP}${e}`;
|
|
12913
12993
|
try {
|
|
12914
12994
|
const stat6 = await fsp.stat(full);
|
|
12915
|
-
if (stat6.isDirectory()) await
|
|
12995
|
+
if (stat6.isDirectory()) await walk3(full, rest);
|
|
12916
12996
|
} catch {
|
|
12917
12997
|
}
|
|
12918
12998
|
}
|
|
@@ -12930,13 +13010,13 @@ async function expandGlob(pattern) {
|
|
|
12930
13010
|
const full = `${dir}${SEP}${seg}`;
|
|
12931
13011
|
try {
|
|
12932
13012
|
const stat6 = await fsp.stat(full);
|
|
12933
|
-
if (stat6.isDirectory()) await
|
|
13013
|
+
if (stat6.isDirectory()) await walk3(full, rest);
|
|
12934
13014
|
} catch {
|
|
12935
13015
|
}
|
|
12936
13016
|
}
|
|
12937
13017
|
}
|
|
12938
13018
|
}
|
|
12939
|
-
await
|
|
13019
|
+
await walk3(base === "." ? "." : base, relPat);
|
|
12940
13020
|
return [...results];
|
|
12941
13021
|
}
|
|
12942
13022
|
|
|
@@ -12953,7 +13033,7 @@ var CollabSession = class extends EventEmitter {
|
|
|
12953
13033
|
bugs = /* @__PURE__ */ new Map();
|
|
12954
13034
|
plans = /* @__PURE__ */ new Map();
|
|
12955
13035
|
evaluations = /* @__PURE__ */ new Map();
|
|
12956
|
-
disposers =
|
|
13036
|
+
disposers = [];
|
|
12957
13037
|
settled = false;
|
|
12958
13038
|
timeoutMs;
|
|
12959
13039
|
cancelled = false;
|
|
@@ -14161,7 +14241,7 @@ var FleetUsageAggregator = class {
|
|
|
14161
14241
|
metaLookup;
|
|
14162
14242
|
perSubagent = /* @__PURE__ */ new Map();
|
|
14163
14243
|
total = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
14164
|
-
unsub =
|
|
14244
|
+
unsub = [];
|
|
14165
14245
|
/**
|
|
14166
14246
|
* Remove a terminated subagent's data from the aggregator and subtract its
|
|
14167
14247
|
* contribution from the running totals. Call this when a subagent is removed
|
|
@@ -14532,7 +14612,10 @@ var Director = class _Director {
|
|
|
14532
14612
|
workCompleteFlag = false;
|
|
14533
14613
|
/** Pending /btw notes stashed by the leader agent (see setLeaderBtwNote). */
|
|
14534
14614
|
_leaderBtwNotes = [];
|
|
14535
|
-
/** Active collab sessions tracked by sessionId (see spawnCollab).
|
|
14615
|
+
/** Active collab sessions tracked by sessionId (see spawnCollab).
|
|
14616
|
+
* The tuple holds the session and its Director-registered listener unsubs.
|
|
14617
|
+
* Calling the unsubs on cancel/premature-cleanup prevents listener accumulation
|
|
14618
|
+
* on CollabSession (EventEmitter) across many spawnCollab() calls. */
|
|
14536
14619
|
_activeCollabSessions = /* @__PURE__ */ new Map();
|
|
14537
14620
|
/** Prevents large `ask_subagent` answers from bloating the leader's context window. */
|
|
14538
14621
|
largeAnswerStore;
|
|
@@ -14862,11 +14945,13 @@ var Director = class _Director {
|
|
|
14862
14945
|
* The CollabDebugReport will reflect 'cancelled' disposition when awaited.
|
|
14863
14946
|
*/
|
|
14864
14947
|
cancelCollabSession(sessionId, reason = "Director cancelled") {
|
|
14865
|
-
const
|
|
14866
|
-
if (!
|
|
14867
|
-
|
|
14868
|
-
|
|
14869
|
-
|
|
14948
|
+
const entry = this._activeCollabSessions.get(sessionId);
|
|
14949
|
+
if (!entry || entry.session.isCancelled()) return;
|
|
14950
|
+
for (const unsub of entry.unsubs) unsub();
|
|
14951
|
+
entry.session.cancel(reason);
|
|
14952
|
+
for (const [_role, subagentId] of entry.session.getSubagentIds()) {
|
|
14953
|
+
this.coordinator.stop(subagentId).catch((err) => {
|
|
14954
|
+
console.debug(`[director] stop subagent ${subagentId} failed (may have already completed): ${err}`);
|
|
14870
14955
|
});
|
|
14871
14956
|
}
|
|
14872
14957
|
}
|
|
@@ -15514,9 +15599,15 @@ var Director = class _Director {
|
|
|
15514
15599
|
return options.onBudgetWarning?.(alert) ?? "ignore";
|
|
15515
15600
|
}
|
|
15516
15601
|
});
|
|
15517
|
-
this._activeCollabSessions.
|
|
15518
|
-
|
|
15519
|
-
session.on("session.
|
|
15602
|
+
const doneHandler = () => this._activeCollabSessions.delete(session.sessionId);
|
|
15603
|
+
const errorHandler = () => this._activeCollabSessions.delete(session.sessionId);
|
|
15604
|
+
session.on("session.done", doneHandler);
|
|
15605
|
+
session.on("session.error", errorHandler);
|
|
15606
|
+
const unsubs = [
|
|
15607
|
+
() => session.off("session.done", doneHandler),
|
|
15608
|
+
() => session.off("session.error", errorHandler)
|
|
15609
|
+
];
|
|
15610
|
+
this._activeCollabSessions.set(session.sessionId, { session, unsubs });
|
|
15520
15611
|
return session.start();
|
|
15521
15612
|
}
|
|
15522
15613
|
/**
|
|
@@ -18938,6 +19029,8 @@ var SpecVersioning = class {
|
|
|
18938
19029
|
switch (type) {
|
|
18939
19030
|
case "functional":
|
|
18940
19031
|
return "feature";
|
|
19032
|
+
case "non-functional":
|
|
19033
|
+
return "feature";
|
|
18941
19034
|
case "security":
|
|
18942
19035
|
return "feature";
|
|
18943
19036
|
case "performance":
|
|
@@ -18945,7 +19038,7 @@ var SpecVersioning = class {
|
|
|
18945
19038
|
case "ux":
|
|
18946
19039
|
return "feature";
|
|
18947
19040
|
default:
|
|
18948
|
-
return
|
|
19041
|
+
return assertNever(type);
|
|
18949
19042
|
}
|
|
18950
19043
|
}
|
|
18951
19044
|
};
|
|
@@ -20096,27 +20189,13 @@ function startOtlpTraceExporter(opts) {
|
|
|
20096
20189
|
// src/infrastructure/context-manager.ts
|
|
20097
20190
|
var CONTEXT_MANAGER_TOOL_NAME = "context_manager";
|
|
20098
20191
|
function roughEstimate(messages) {
|
|
20099
|
-
|
|
20100
|
-
for (const m of messages) {
|
|
20101
|
-
if (typeof m.content === "string") {
|
|
20102
|
-
total += Math.ceil(m.content.length / 4);
|
|
20103
|
-
} else if (Array.isArray(m.content)) {
|
|
20104
|
-
for (const b of m.content) {
|
|
20105
|
-
if (b.type === "text") total += Math.ceil(b.text.length / 4);
|
|
20106
|
-
else if (b.type === "tool_use" || b.type === "tool_result") {
|
|
20107
|
-
total += Math.ceil(JSON.stringify(b).length / 4);
|
|
20108
|
-
}
|
|
20109
|
-
}
|
|
20110
|
-
}
|
|
20111
|
-
}
|
|
20112
|
-
return total;
|
|
20192
|
+
return estimateMessageTokens(messages);
|
|
20113
20193
|
}
|
|
20114
20194
|
function createContextManagerTool(opts = {}) {
|
|
20115
20195
|
const minCompactThreshold = opts.minCompactThreshold ?? 0;
|
|
20116
20196
|
const noopRetryDeltaTokens = opts.noopRetryDeltaTokens ?? 2e3;
|
|
20117
|
-
const
|
|
20197
|
+
const configuredMaxContext = opts.maxContext;
|
|
20118
20198
|
const compactThresholdFraction = opts.compactThresholdFraction ?? 0.5;
|
|
20119
|
-
const effectiveThreshold = minCompactThreshold > 0 ? minCompactThreshold : Math.floor(maxContext * compactThresholdFraction);
|
|
20120
20199
|
let lastNoopTokens = 0;
|
|
20121
20200
|
return {
|
|
20122
20201
|
name: CONTEXT_MANAGER_TOOL_NAME,
|
|
@@ -20210,6 +20289,8 @@ function createContextManagerTool(opts = {}) {
|
|
|
20210
20289
|
}
|
|
20211
20290
|
const fullEstimate = input.systemPrompt != null && Array.isArray(input.tools) ? estimateRequestTokens(messages, input.systemPrompt, input.tools) : { total: beforeTokens};
|
|
20212
20291
|
const currentTokens = fullEstimate.total;
|
|
20292
|
+
const runtimeMaxContext = configuredMaxContext ?? ctx.provider?.capabilities?.maxContext ?? 128e3;
|
|
20293
|
+
const runtimeThreshold = minCompactThreshold > 0 ? minCompactThreshold : Math.floor(runtimeMaxContext * compactThresholdFraction);
|
|
20213
20294
|
if (lastNoopTokens > 0) {
|
|
20214
20295
|
const delta = currentTokens - lastNoopTokens;
|
|
20215
20296
|
if (delta < noopRetryDeltaTokens) {
|
|
@@ -20222,13 +20303,13 @@ function createContextManagerTool(opts = {}) {
|
|
|
20222
20303
|
};
|
|
20223
20304
|
}
|
|
20224
20305
|
}
|
|
20225
|
-
if (currentTokens <
|
|
20306
|
+
if (runtimeThreshold > 0 && currentTokens < runtimeThreshold) {
|
|
20226
20307
|
return {
|
|
20227
20308
|
action: "compact",
|
|
20228
20309
|
beforeTokens,
|
|
20229
20310
|
afterTokens: beforeTokens,
|
|
20230
20311
|
messageCount: messages.length,
|
|
20231
|
-
notes: `Context tokens (${currentTokens}) below compact threshold (${
|
|
20312
|
+
notes: `Context tokens (${currentTokens}) below compact threshold (${runtimeThreshold}, based on provider maxContext ${runtimeMaxContext}). Skipping.`
|
|
20232
20313
|
};
|
|
20233
20314
|
}
|
|
20234
20315
|
const report = await opts.compactor.compact(ctx);
|
|
@@ -20481,6 +20562,6 @@ var allServers = () => ({
|
|
|
20481
20562
|
"minimax-vision": { ...miniMaxVisionServer(), enabled: false }
|
|
20482
20563
|
});
|
|
20483
20564
|
|
|
20484
|
-
export { AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetSpawnBudgetError, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SddParallelRun, SddTaskDecomposer, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolExecutor, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDelegateTool, createMessage, createSessionEventBridge,
|
|
20565
|
+
export { AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetSpawnBudgetError, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SddParallelRun, SddTaskDecomposer, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolExecutor, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDelegateTool, createMessage, createSessionEventBridge, createStrategyCompactor, decryptConfigSecrets, deriveTodosFromPlanItem, dispatchAgent, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, formatPlanTemplates, getAgentDefinition, getPlanTemplate, getTemplate, githubServer, googleMapsServer, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, resolveAuditLevel, resolveSessionLoggingConfig, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, scoreAgents, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, templateToMarkdown, wireMetricsToEvents, zaiVisionServer };
|
|
20485
20566
|
//# sourceMappingURL=index.js.map
|
|
20486
20567
|
//# sourceMappingURL=index.js.map
|