@wrongstack/core 0.109.1 → 0.141.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-mOxbpFcg.d.ts → agent-bridge-r9y6gdn4.d.ts} +1 -1
- package/dist/{agent-subagent-runner-DukQLUcS.d.ts → agent-subagent-runner-1GeQE_L0.d.ts} +7 -8
- package/dist/{brain-Dfv4Y82E.d.ts → brain-Cp_3GIS2.d.ts} +14 -3
- package/dist/{compactor-DXLxLcmU.d.ts → compactor-BueGt7LG.d.ts} +9 -1
- package/dist/{config-BSU-6vah.d.ts → config-BaVThgnT.d.ts} +104 -3
- package/dist/{context-CNRYfhUv.d.ts → context-C7G_MtLV.d.ts} +38 -1
- package/dist/coordination/index.d.ts +12 -13
- package/dist/coordination/index.js +229 -17
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +26 -27
- package/dist/defaults/index.js +886 -721
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +15 -16
- package/dist/execution/index.js +535 -322
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +6 -7
- package/dist/{goal-preamble-CI8lxeY1.d.ts → goal-preamble-iuIUTQwk.d.ts} +48 -18
- package/dist/{index-BWRN6wOb.d.ts → index-BZdezm3g.d.ts} +9 -10
- package/dist/{index-DIKEcfgC.d.ts → index-CPweVoFM.d.ts} +7 -6
- package/dist/index.d.ts +51 -52
- package/dist/index.js +752 -545
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -7
- package/dist/infrastructure/index.js +64 -19
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +10 -11
- package/dist/kernel/index.js.map +1 -1
- package/dist/llm-selector-CP72f1lC.d.ts +58 -0
- package/dist/{mcp-servers-CXCsANdY.d.ts → mcp-servers-Bl5LTvQg.d.ts} +3 -3
- package/dist/{mode-ARA3HrkY.d.ts → mode-CZlO9iU1.d.ts} +1 -1
- package/dist/models/index.d.ts +157 -53
- package/dist/models/index.js +475 -20
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-DU64QxQa.d.ts → models-registry-D90K9UnM.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-51LvnXkD.d.ts → multi-agent-coordinator-QWEzJDlm.d.ts} +1 -1
- package/dist/{null-fleet-bus-D09hMzFQ.d.ts → null-fleet-bus-BUyfqh23.d.ts} +13 -10
- package/dist/observability/index.d.ts +2 -2
- package/dist/{parallel-eternal-engine-B2CbsKpc.d.ts → parallel-eternal-engine-Dj2SYzha.d.ts} +24 -11
- package/dist/{path-resolver-DDJiMAtX.d.ts → path-resolver-DRjQBkoO.d.ts} +3 -3
- package/dist/{permission-BDv7z0mk.d.ts → permission-B7nKnEvQ.d.ts} +1 -1
- package/dist/{permission-policy-dF74EpDp.d.ts → permission-policy-8-6zBmfA.d.ts} +2 -13
- package/dist/{pipeline-BqiA_UMr.d.ts → pipeline-BG7UgbDc.d.ts} +2 -2
- package/dist/{plan-templates-BdDxl9cI.d.ts → plan-templates-CkKNPU3I.d.ts} +6 -6
- package/dist/{provider-runner-BUunikwY.d.ts → provider-runner-BNpuIyOL.d.ts} +3 -3
- package/dist/{retry-policy-BcmuT_V0.d.ts → retry-policy-rutAfVeR.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -9
- package/dist/sdd/index.js +208 -1
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-DrOhc2i5.d.ts → secret-vault-BTcC_T5v.d.ts} +3 -2
- package/dist/security/index.d.ts +5 -7
- package/dist/security/index.js +4 -35
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-C7wcdqMA.d.ts → selector-4vDFZKt3.d.ts} +1 -1
- package/dist/{session-event-bridge-BpJ5trO9.d.ts → session-event-bridge-DWlvglC2.d.ts} +2 -2
- package/dist/{session-reader-DDz1Ek4V.d.ts → session-reader-BAtCxdaw.d.ts} +1 -1
- package/dist/storage/index.d.ts +14 -15
- package/dist/storage/index.js +140 -134
- package/dist/storage/index.js.map +1 -1
- package/dist/types/index.d.ts +19 -20
- package/dist/types/index.js +186 -102
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +38 -15
- package/dist/utils/index.js +62 -33
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-_lqjzErq.d.ts → wstack-paths-DD50Omgn.d.ts} +3 -0
- package/package.json +2 -2
- package/dist/models-registry-B6_KfS65.d.ts +0 -95
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) {
|
|
@@ -1510,39 +1508,41 @@ ${line}`;
|
|
|
1510
1508
|
}
|
|
1511
1509
|
async forget(scope, query, filePath) {
|
|
1512
1510
|
const file = this.resolveFile(filePath, scope);
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
const
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1511
|
+
return withFileLock(file, async () => {
|
|
1512
|
+
let existing;
|
|
1513
|
+
try {
|
|
1514
|
+
existing = await fsp.readFile(file, "utf8");
|
|
1515
|
+
} catch {
|
|
1516
|
+
return 0;
|
|
1517
|
+
}
|
|
1518
|
+
const needle = query.toLowerCase();
|
|
1519
|
+
const idMatcher = /mem_\d+_\w+/;
|
|
1520
|
+
let removed = 0;
|
|
1521
|
+
const lines = existing.split("\n").filter((line) => {
|
|
1522
|
+
const trimmed = line.trim();
|
|
1523
|
+
if (!trimmed.startsWith("- ")) return true;
|
|
1524
|
+
if (idMatcher.test(query)) {
|
|
1525
|
+
const entryIdMatch = /mem_\d+_\w+/.exec(trimmed);
|
|
1526
|
+
if (entryIdMatch && entryIdMatch[0] === query) {
|
|
1527
|
+
removed++;
|
|
1528
|
+
return false;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
if (trimmed.toLowerCase().includes(needle)) {
|
|
1528
1532
|
removed++;
|
|
1529
1533
|
return false;
|
|
1530
1534
|
}
|
|
1535
|
+
return true;
|
|
1536
|
+
});
|
|
1537
|
+
if (removed > 0) {
|
|
1538
|
+
if (lines.length === 0 || lines.length === 1 && !lines[0]?.trim()) {
|
|
1539
|
+
await atomicWrite(file, "");
|
|
1540
|
+
} else {
|
|
1541
|
+
await atomicWrite(file, lines.join("\n"));
|
|
1542
|
+
}
|
|
1531
1543
|
}
|
|
1532
|
-
|
|
1533
|
-
removed++;
|
|
1534
|
-
return false;
|
|
1535
|
-
}
|
|
1536
|
-
return true;
|
|
1544
|
+
return removed;
|
|
1537
1545
|
});
|
|
1538
|
-
if (removed > 0) {
|
|
1539
|
-
if (lines.length === 0 || lines.length === 1 && !lines[0]?.trim()) {
|
|
1540
|
-
await atomicWrite(file, "");
|
|
1541
|
-
} else {
|
|
1542
|
-
await atomicWrite(file, lines.join("\n"));
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
return removed;
|
|
1546
1546
|
}
|
|
1547
1547
|
async readAll(scope, filePath) {
|
|
1548
1548
|
const file = this.resolveFile(filePath, scope);
|
|
@@ -1983,20 +1983,101 @@ function deepFreeze(obj) {
|
|
|
1983
1983
|
return Object.freeze(obj);
|
|
1984
1984
|
}
|
|
1985
1985
|
|
|
1986
|
-
// src/
|
|
1987
|
-
|
|
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));
|
|
1988
2067
|
return walk(cfg, vault, (v, key) => {
|
|
1989
2068
|
try {
|
|
1990
2069
|
return vault.decrypt(v);
|
|
1991
2070
|
} catch (err) {
|
|
1992
|
-
|
|
1993
|
-
`[secret-vault] Failed to decrypt "${key}"
|
|
1994
|
-
err instanceof Error ? err.message : err
|
|
2071
|
+
warn(
|
|
2072
|
+
`[secret-vault] Failed to decrypt "${key}": ${err instanceof Error ? err.message : err}`
|
|
1995
2073
|
);
|
|
1996
2074
|
return "";
|
|
1997
2075
|
}
|
|
1998
2076
|
});
|
|
1999
2077
|
}
|
|
2078
|
+
function encryptConfigSecrets(cfg, vault, _opts) {
|
|
2079
|
+
return walk(cfg, vault, (v) => vault.encrypt(v));
|
|
2080
|
+
}
|
|
2000
2081
|
function walk(node, vault, transform) {
|
|
2001
2082
|
if (node === null || node === void 0) return node;
|
|
2002
2083
|
if (typeof node !== "object") return node;
|
|
@@ -2022,6 +2103,114 @@ function isSecretField(name) {
|
|
|
2022
2103
|
if (NON_SECRET_OVERRIDES.has(lc)) return false;
|
|
2023
2104
|
return SECRET_KEY_PATTERN.test(lc);
|
|
2024
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
|
+
}
|
|
2025
2214
|
|
|
2026
2215
|
// src/storage/config-loader.ts
|
|
2027
2216
|
init_atomic_write();
|
|
@@ -2207,7 +2396,7 @@ var defaultIndexing = {
|
|
|
2207
2396
|
function isPrimitiveArray(a) {
|
|
2208
2397
|
return a.every((v) => v === null || typeof v !== "object");
|
|
2209
2398
|
}
|
|
2210
|
-
var
|
|
2399
|
+
var FORBIDDEN_PROTO_KEYS2 = /* @__PURE__ */ new Set([
|
|
2211
2400
|
"__proto__",
|
|
2212
2401
|
"constructor",
|
|
2213
2402
|
"prototype",
|
|
@@ -2216,12 +2405,12 @@ var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
|
2216
2405
|
"__lookupGetter__",
|
|
2217
2406
|
"__lookupSetter__"
|
|
2218
2407
|
]);
|
|
2219
|
-
function
|
|
2408
|
+
function deepMerge2(base, patch) {
|
|
2220
2409
|
if (typeof base !== "object" || base === null) return patch ?? base;
|
|
2221
2410
|
if (typeof patch !== "object" || patch === null) return base;
|
|
2222
2411
|
const out = { ...base };
|
|
2223
2412
|
for (const [k, v] of Object.entries(patch)) {
|
|
2224
|
-
if (
|
|
2413
|
+
if (FORBIDDEN_PROTO_KEYS2.has(k)) continue;
|
|
2225
2414
|
const existing = out[k];
|
|
2226
2415
|
if (Array.isArray(v)) {
|
|
2227
2416
|
if (Array.isArray(existing) && isPrimitiveArray(v) && isPrimitiveArray(existing)) {
|
|
@@ -2235,7 +2424,7 @@ function deepMerge(base, patch) {
|
|
|
2235
2424
|
}
|
|
2236
2425
|
}
|
|
2237
2426
|
} else if (typeof v === "object" && v !== null && typeof existing === "object" && existing !== null) {
|
|
2238
|
-
out[k] =
|
|
2427
|
+
out[k] = deepMerge2(existing, v);
|
|
2239
2428
|
} else if (v !== void 0) {
|
|
2240
2429
|
out[k] = v;
|
|
2241
2430
|
}
|
|
@@ -2255,12 +2444,14 @@ var DefaultConfigLoader = class {
|
|
|
2255
2444
|
}
|
|
2256
2445
|
async load(opts = {}) {
|
|
2257
2446
|
let cfg = { ...BEHAVIOR_DEFAULTS };
|
|
2258
|
-
const [global, local] = await Promise.all([
|
|
2447
|
+
const [global, local, inProject] = await Promise.all([
|
|
2259
2448
|
this.readJson(this.paths.globalConfig),
|
|
2260
|
-
this.readJson(this.paths.projectLocalConfig)
|
|
2449
|
+
this.readJson(this.paths.projectLocalConfig),
|
|
2450
|
+
this.readJson(this.paths.inProjectConfig)
|
|
2261
2451
|
]);
|
|
2262
|
-
cfg =
|
|
2263
|
-
cfg =
|
|
2452
|
+
cfg = deepMerge2(cfg, global);
|
|
2453
|
+
cfg = deepMerge2(cfg, local);
|
|
2454
|
+
cfg = deepMerge2(cfg, inProject);
|
|
2264
2455
|
for (const [key, fn] of Object.entries(ENV_MAP)) {
|
|
2265
2456
|
const v = process.env[key];
|
|
2266
2457
|
if (v) fn(cfg, v);
|
|
@@ -2274,14 +2465,14 @@ var DefaultConfigLoader = class {
|
|
|
2274
2465
|
try {
|
|
2275
2466
|
const patch = await src.read();
|
|
2276
2467
|
if (patch && Object.keys(patch).length > 0) {
|
|
2277
|
-
cfg =
|
|
2468
|
+
cfg = deepMerge2(cfg, patch);
|
|
2278
2469
|
}
|
|
2279
2470
|
} catch (err) {
|
|
2280
2471
|
console.warn(`Config source "${src.name}" failed`, err);
|
|
2281
2472
|
}
|
|
2282
2473
|
}
|
|
2283
2474
|
if (opts.cliFlags) {
|
|
2284
|
-
cfg =
|
|
2475
|
+
cfg = deepMerge2(cfg, opts.cliFlags);
|
|
2285
2476
|
}
|
|
2286
2477
|
if (this.vault) {
|
|
2287
2478
|
cfg = decryptConfigSecrets(cfg, this.vault);
|
|
@@ -3693,239 +3884,12 @@ var DefaultSecretScrubber = class {
|
|
|
3693
3884
|
}
|
|
3694
3885
|
};
|
|
3695
3886
|
|
|
3696
|
-
// src/types/secret-vault.ts
|
|
3697
|
-
var ENCRYPTED_PREFIX = "enc:v1:";
|
|
3698
|
-
|
|
3699
|
-
// src/security/secret-vault.ts
|
|
3700
|
-
init_atomic_write();
|
|
3701
|
-
var KEY_BYTES = 32;
|
|
3702
|
-
var IV_BYTES = 12;
|
|
3703
|
-
var TAG_BYTES = 16;
|
|
3704
|
-
var ALGO = "aes-256-gcm";
|
|
3705
|
-
var DefaultSecretVault = class {
|
|
3706
|
-
keyFile;
|
|
3707
|
-
key;
|
|
3708
|
-
constructor(opts) {
|
|
3709
|
-
this.keyFile = opts.keyFile;
|
|
3710
|
-
}
|
|
3711
|
-
isEncrypted(value) {
|
|
3712
|
-
return typeof value === "string" && value.startsWith(ENCRYPTED_PREFIX);
|
|
3713
|
-
}
|
|
3714
|
-
encrypt(plaintext) {
|
|
3715
|
-
if (this.isEncrypted(plaintext)) return plaintext;
|
|
3716
|
-
const key = this.loadOrCreateKey();
|
|
3717
|
-
const iv = randomBytes(IV_BYTES);
|
|
3718
|
-
const cipher = createCipheriv(ALGO, key, iv);
|
|
3719
|
-
const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
3720
|
-
const tag = cipher.getAuthTag();
|
|
3721
|
-
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
|
|
3722
|
-
}
|
|
3723
|
-
decrypt(value) {
|
|
3724
|
-
if (!this.isEncrypted(value)) return value;
|
|
3725
|
-
const rest = value.slice(ENCRYPTED_PREFIX.length);
|
|
3726
|
-
const parts = rest.split(":");
|
|
3727
|
-
if (parts.length !== 3) {
|
|
3728
|
-
throw new Error("SecretVault: malformed encrypted value");
|
|
3729
|
-
}
|
|
3730
|
-
const [ivB64, tagB64, ctB64] = parts;
|
|
3731
|
-
const iv = Buffer.from(ivB64, "base64");
|
|
3732
|
-
const tag = Buffer.from(tagB64, "base64");
|
|
3733
|
-
const ct = Buffer.from(ctB64, "base64");
|
|
3734
|
-
if (iv.length !== IV_BYTES) throw new Error("SecretVault: bad IV length");
|
|
3735
|
-
if (tag.length !== TAG_BYTES) throw new Error("SecretVault: bad tag length");
|
|
3736
|
-
const key = this.loadOrCreateKey();
|
|
3737
|
-
const decipher = createDecipheriv(ALGO, key, iv);
|
|
3738
|
-
decipher.setAuthTag(tag);
|
|
3739
|
-
const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
3740
|
-
return pt.toString("utf8");
|
|
3741
|
-
}
|
|
3742
|
-
loadOrCreateKey() {
|
|
3743
|
-
if (this.key) return this.key;
|
|
3744
|
-
try {
|
|
3745
|
-
const buf = fs5.readFileSync(this.keyFile);
|
|
3746
|
-
if (buf.length !== KEY_BYTES) {
|
|
3747
|
-
throw new Error(
|
|
3748
|
-
`SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES}). Remove it manually to generate a new key.`
|
|
3749
|
-
);
|
|
3750
|
-
}
|
|
3751
|
-
this.key = buf;
|
|
3752
|
-
return this.key;
|
|
3753
|
-
} catch (err) {
|
|
3754
|
-
if (err.code !== "ENOENT") throw err;
|
|
3755
|
-
}
|
|
3756
|
-
fs5.mkdirSync(path11.dirname(this.keyFile), { recursive: true });
|
|
3757
|
-
const key = randomBytes(KEY_BYTES);
|
|
3758
|
-
try {
|
|
3759
|
-
fs5.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
|
|
3760
|
-
} catch (err) {
|
|
3761
|
-
if (err.code !== "EEXIST") throw err;
|
|
3762
|
-
const buf = fs5.readFileSync(this.keyFile);
|
|
3763
|
-
if (buf.length !== KEY_BYTES) {
|
|
3764
|
-
throw new Error(
|
|
3765
|
-
`SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES}). Remove it manually to generate a new key.`
|
|
3766
|
-
);
|
|
3767
|
-
}
|
|
3768
|
-
this.key = buf;
|
|
3769
|
-
return this.key;
|
|
3770
|
-
}
|
|
3771
|
-
this.key = key;
|
|
3772
|
-
return key;
|
|
3773
|
-
}
|
|
3774
|
-
};
|
|
3775
|
-
function decryptConfigSecrets2(cfg, vault, opts) {
|
|
3776
|
-
const warn = opts?.warn ?? ((msg) => console.warn(msg));
|
|
3777
|
-
return walk2(cfg, vault, (v, key) => {
|
|
3778
|
-
try {
|
|
3779
|
-
return vault.decrypt(v);
|
|
3780
|
-
} catch (err) {
|
|
3781
|
-
warn(
|
|
3782
|
-
`[secret-vault] Failed to decrypt "${key}": ${err instanceof Error ? err.message : err}`
|
|
3783
|
-
);
|
|
3784
|
-
return "";
|
|
3785
|
-
}
|
|
3786
|
-
});
|
|
3787
|
-
}
|
|
3788
|
-
function encryptConfigSecrets(cfg, vault, _opts) {
|
|
3789
|
-
return walk2(cfg, vault, (v) => vault.encrypt(v));
|
|
3790
|
-
}
|
|
3791
|
-
function walk2(node, vault, transform) {
|
|
3792
|
-
if (node === null || node === void 0) return node;
|
|
3793
|
-
if (typeof node !== "object") return node;
|
|
3794
|
-
if (Array.isArray(node)) {
|
|
3795
|
-
return node.map((item) => walk2(item, vault, transform));
|
|
3796
|
-
}
|
|
3797
|
-
const out = /* @__PURE__ */ Object.create(null);
|
|
3798
|
-
for (const [k, v] of Object.entries(node)) {
|
|
3799
|
-
if (typeof v === "string" && isSecretField2(k)) {
|
|
3800
|
-
out[k] = transform(v, k);
|
|
3801
|
-
} else if (typeof v === "object" && v !== null) {
|
|
3802
|
-
out[k] = walk2(v, vault, transform);
|
|
3803
|
-
} else {
|
|
3804
|
-
out[k] = v;
|
|
3805
|
-
}
|
|
3806
|
-
}
|
|
3807
|
-
return out;
|
|
3808
|
-
}
|
|
3809
|
-
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;
|
|
3810
|
-
var NON_SECRET_OVERRIDES2 = /* @__PURE__ */ new Set(["publickey", "public_key"]);
|
|
3811
|
-
function isSecretField2(name) {
|
|
3812
|
-
const lc = name.toLowerCase();
|
|
3813
|
-
if (NON_SECRET_OVERRIDES2.has(lc)) return false;
|
|
3814
|
-
return SECRET_KEY_PATTERN2.test(lc);
|
|
3815
|
-
}
|
|
3816
|
-
async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
3817
|
-
let current = {};
|
|
3818
|
-
try {
|
|
3819
|
-
const raw = await fsp.readFile(configPath, "utf8");
|
|
3820
|
-
current = JSON.parse(raw);
|
|
3821
|
-
} catch {
|
|
3822
|
-
}
|
|
3823
|
-
const merged = deepMerge2(current, patch ?? {});
|
|
3824
|
-
const encrypted = encryptConfigSecrets(merged, vault);
|
|
3825
|
-
await fsp.mkdir(path11.dirname(configPath), { recursive: true });
|
|
3826
|
-
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
3827
|
-
await restrictFilePermissions(configPath);
|
|
3828
|
-
}
|
|
3829
|
-
async function migratePlaintextSecrets(configPath, vault) {
|
|
3830
|
-
let raw;
|
|
3831
|
-
try {
|
|
3832
|
-
raw = await fsp.readFile(configPath, "utf8");
|
|
3833
|
-
} catch {
|
|
3834
|
-
return { migrated: 0, file: configPath };
|
|
3835
|
-
}
|
|
3836
|
-
let parsed;
|
|
3837
|
-
try {
|
|
3838
|
-
parsed = JSON.parse(raw);
|
|
3839
|
-
} catch {
|
|
3840
|
-
return { migrated: 0, file: configPath };
|
|
3841
|
-
}
|
|
3842
|
-
const counter = { n: 0 };
|
|
3843
|
-
const migrated = walkCount(parsed, vault, counter);
|
|
3844
|
-
if (counter.n === 0) return { migrated: 0, file: configPath };
|
|
3845
|
-
await atomicWrite(configPath, JSON.stringify(migrated, null, 2), { mode: 384 });
|
|
3846
|
-
await restrictFilePermissions(configPath);
|
|
3847
|
-
return { migrated: counter.n, file: configPath };
|
|
3848
|
-
}
|
|
3849
|
-
async function restrictFilePermissions(filePath, opts) {
|
|
3850
|
-
const warn = ((msg) => console.warn(msg));
|
|
3851
|
-
if (process.platform === "win32") {
|
|
3852
|
-
try {
|
|
3853
|
-
const { execFile: execFile2 } = await import('child_process');
|
|
3854
|
-
const { promisify: promisify2 } = await import('util');
|
|
3855
|
-
const execFileAsync = promisify2(execFile2);
|
|
3856
|
-
const user = windowsAccountName();
|
|
3857
|
-
if (!user) {
|
|
3858
|
-
warn(
|
|
3859
|
-
`[secret-vault] Could not determine the current Windows user for ${filePath}; skipping icacls hardening.`
|
|
3860
|
-
);
|
|
3861
|
-
return;
|
|
3862
|
-
}
|
|
3863
|
-
await execFileAsync("icacls", [filePath, "/inheritance:r", "/grant:r", `${user}:(F)`]);
|
|
3864
|
-
} catch {
|
|
3865
|
-
warn(
|
|
3866
|
-
`[secret-vault] Could not restrict permissions on ${filePath} \u2014 config file may be readable by other users on this system.`
|
|
3867
|
-
);
|
|
3868
|
-
}
|
|
3869
|
-
} else {
|
|
3870
|
-
try {
|
|
3871
|
-
await fsp.chmod(filePath, 384);
|
|
3872
|
-
} catch {
|
|
3873
|
-
}
|
|
3874
|
-
}
|
|
3875
|
-
}
|
|
3876
|
-
function windowsAccountName() {
|
|
3877
|
-
const username = process.env.USERNAME || process.env.USER;
|
|
3878
|
-
if (!username || username.includes("\0")) return void 0;
|
|
3879
|
-
const domain = process.env.USERDOMAIN;
|
|
3880
|
-
if (domain && !domain.includes("\0")) return `${domain}\\${username}`;
|
|
3881
|
-
return username;
|
|
3882
|
-
}
|
|
3883
|
-
function walkCount(node, vault, counter) {
|
|
3884
|
-
if (node === null || node === void 0) return node;
|
|
3885
|
-
if (typeof node !== "object") return node;
|
|
3886
|
-
if (Array.isArray(node)) {
|
|
3887
|
-
return node.map((item) => walkCount(item, vault, counter));
|
|
3888
|
-
}
|
|
3889
|
-
const out = /* @__PURE__ */ Object.create(null);
|
|
3890
|
-
for (const [k, v] of Object.entries(node)) {
|
|
3891
|
-
if (typeof v === "string" && isSecretField2(k) && !vault.isEncrypted(v) && v.length > 0) {
|
|
3892
|
-
out[k] = vault.encrypt(v);
|
|
3893
|
-
counter.n++;
|
|
3894
|
-
} else if (typeof v === "object" && v !== null) {
|
|
3895
|
-
out[k] = walkCount(v, vault, counter);
|
|
3896
|
-
} else {
|
|
3897
|
-
out[k] = v;
|
|
3898
|
-
}
|
|
3899
|
-
}
|
|
3900
|
-
return out;
|
|
3901
|
-
}
|
|
3902
|
-
var FORBIDDEN_PROTO_KEYS2 = /* @__PURE__ */ new Set([
|
|
3903
|
-
"__proto__",
|
|
3904
|
-
"constructor",
|
|
3905
|
-
"prototype",
|
|
3906
|
-
"__defineGetter__",
|
|
3907
|
-
"__defineSetter__",
|
|
3908
|
-
"__lookupGetter__",
|
|
3909
|
-
"__lookupSetter__"
|
|
3910
|
-
]);
|
|
3911
|
-
function deepMerge2(a, b) {
|
|
3912
|
-
const out = { ...a };
|
|
3913
|
-
for (const [k, v] of Object.entries(b)) {
|
|
3914
|
-
if (FORBIDDEN_PROTO_KEYS2.has(k)) continue;
|
|
3915
|
-
const existing = out[k];
|
|
3916
|
-
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
3917
|
-
out[k] = deepMerge2(existing, v);
|
|
3918
|
-
} else {
|
|
3919
|
-
out[k] = v;
|
|
3920
|
-
}
|
|
3921
|
-
}
|
|
3922
|
-
return out;
|
|
3923
|
-
}
|
|
3924
|
-
|
|
3925
3887
|
// src/security/capabilities.ts
|
|
3926
3888
|
var ToolCapabilities = {
|
|
3927
3889
|
/** Can execute arbitrary commands in the user's shell (the `bash` tool). */
|
|
3928
3890
|
SHELL_ARBITRARY: "shell.arbitrary",
|
|
3891
|
+
/** Can execute a restricted set of commands (the `exec` tool). */
|
|
3892
|
+
SHELL_RESTRICTED: "shell.restricted",
|
|
3929
3893
|
/** Can write / modify / delete files inside the project. */
|
|
3930
3894
|
FS_WRITE: "fs.write",
|
|
3931
3895
|
/** Can write files outside the current project root (very high risk). */
|
|
@@ -3941,6 +3905,7 @@ var ToolCapabilities = {
|
|
|
3941
3905
|
};
|
|
3942
3906
|
var DANGEROUS_FOR_SUBAGENTS = [
|
|
3943
3907
|
ToolCapabilities.SHELL_ARBITRARY,
|
|
3908
|
+
ToolCapabilities.SHELL_RESTRICTED,
|
|
3944
3909
|
ToolCapabilities.FS_WRITE,
|
|
3945
3910
|
ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
|
|
3946
3911
|
ToolCapabilities.MCP_PROXY,
|
|
@@ -4384,38 +4349,15 @@ var DefaultPermissionPolicy = class {
|
|
|
4384
4349
|
}
|
|
4385
4350
|
};
|
|
4386
4351
|
var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
4387
|
-
/**
|
|
4388
|
-
* Legacy name-based denylist.
|
|
4389
|
-
* @deprecated Prefer declaring `capabilities` on the Tool and using capability-based checks.
|
|
4390
|
-
*/
|
|
4391
|
-
static LEGACY_NAME_DENY = /* @__PURE__ */ new Set([
|
|
4392
|
-
"bash",
|
|
4393
|
-
"write",
|
|
4394
|
-
"edit",
|
|
4395
|
-
"replace",
|
|
4396
|
-
"scaffold",
|
|
4397
|
-
"patch",
|
|
4398
|
-
"install",
|
|
4399
|
-
"exec"
|
|
4400
|
-
]);
|
|
4401
|
-
// Note: hasDangerousCapabilityForSubagents is now the shared helper from capabilities.ts
|
|
4402
|
-
// The old private method was removed in favor of the centralized utility.
|
|
4403
|
-
/**
|
|
4404
|
-
* Tools from MCP servers (`mcp__<server>__<tool>`) are external code of
|
|
4405
|
-
* unknown capability — they may wrap a shell or filesystem. They are
|
|
4406
|
-
* fail-closed here: not auto-approved for subagents by default, so the
|
|
4407
|
-
* leader must allow them explicitly per-spawn.
|
|
4408
|
-
*/
|
|
4409
4352
|
static isMcpTool(name) {
|
|
4410
4353
|
return name.startsWith("mcp__");
|
|
4411
4354
|
}
|
|
4412
4355
|
async evaluate(tool) {
|
|
4413
4356
|
const hasDangerousCap = hasDangerousCapabilityForSubagents(tool);
|
|
4414
|
-
const legacyNameBlock = _AutoApprovePermissionPolicy.LEGACY_NAME_DENY.has(tool.name);
|
|
4415
4357
|
const isMcp = _AutoApprovePermissionPolicy.isMcpTool(tool.name);
|
|
4416
|
-
const blocked = tool.permission === "deny" || hasDangerousCap ||
|
|
4358
|
+
const blocked = tool.permission === "deny" || hasDangerousCap || isMcp;
|
|
4417
4359
|
if (blocked) {
|
|
4418
|
-
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";
|
|
4419
4361
|
return {
|
|
4420
4362
|
permission: "deny",
|
|
4421
4363
|
source: "subagent_guard",
|
|
@@ -4612,6 +4554,7 @@ var DefaultRetryPolicy = class {
|
|
|
4612
4554
|
if (err instanceof ProviderError) {
|
|
4613
4555
|
if (err.status === 429) return 5;
|
|
4614
4556
|
if (err.status === 529) return 3;
|
|
4557
|
+
if (err.status === 599) return 5;
|
|
4615
4558
|
if (err.status >= 500) return 3;
|
|
4616
4559
|
return 0;
|
|
4617
4560
|
}
|
|
@@ -5083,53 +5026,63 @@ async function streamProviderToResponse(provider, req, signal, ctx, events) {
|
|
|
5083
5026
|
const next = await iter.next();
|
|
5084
5027
|
if (next.done) break;
|
|
5085
5028
|
const ev = next.value;
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
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
|
-
|
|
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;
|
|
5116
5079
|
}
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
break;
|
|
5124
|
-
case "thinking_signature":
|
|
5125
|
-
handleThinkingSignature(state, ev.signature);
|
|
5126
|
-
break;
|
|
5127
|
-
case "thinking_stop":
|
|
5128
|
-
handleThinkingStop(state);
|
|
5129
|
-
break;
|
|
5130
|
-
case "message_stop":
|
|
5131
|
-
handleMessageStop(state, ev);
|
|
5132
|
-
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
|
+
});
|
|
5133
5086
|
}
|
|
5134
5087
|
}
|
|
5135
5088
|
} catch (err) {
|
|
@@ -5142,8 +5095,10 @@ async function streamProviderToResponse(provider, req, signal, ctx, events) {
|
|
|
5142
5095
|
try {
|
|
5143
5096
|
let drainTimer = null;
|
|
5144
5097
|
try {
|
|
5098
|
+
const drainPromise = Promise.resolve(iter.return?.()).catch(() => {
|
|
5099
|
+
});
|
|
5145
5100
|
await Promise.race([
|
|
5146
|
-
|
|
5101
|
+
drainPromise,
|
|
5147
5102
|
new Promise((resolve5) => {
|
|
5148
5103
|
drainTimer = setTimeout(resolve5, STREAM_DRAIN_TIMEOUT_MS);
|
|
5149
5104
|
})
|
|
@@ -5241,6 +5196,17 @@ var DefaultProviderRunner = class {
|
|
|
5241
5196
|
|
|
5242
5197
|
// src/utils/token-estimate.ts
|
|
5243
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;
|
|
5244
5210
|
var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
|
|
5245
5211
|
var ESTIMATE_CACHE_MAX_SIZE = 1e4;
|
|
5246
5212
|
function getCachedEstimate(key, compute) {
|
|
@@ -5272,10 +5238,25 @@ function estimateToolResultTokens(content) {
|
|
|
5272
5238
|
function estimateTextTokens(text) {
|
|
5273
5239
|
return RoughTokenEstimate(text);
|
|
5274
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
|
+
}
|
|
5275
5256
|
function estimateToolDefTokens(tool) {
|
|
5276
5257
|
return RoughTokenEstimate(tool.name) + RoughTokenEstimate(tool.description ?? "") + RoughTokenEstimate(JSON.stringify(tool.inputSchema));
|
|
5277
5258
|
}
|
|
5278
|
-
function estimateRequestTokens(messages, systemPrompt, tools) {
|
|
5259
|
+
function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
5279
5260
|
let messagesTokens = 0;
|
|
5280
5261
|
if (typeof messages === "string") {
|
|
5281
5262
|
messagesTokens = RoughTokenEstimate(messages);
|
|
@@ -5314,6 +5295,7 @@ function estimateRequestTokens(messages, systemPrompt, tools) {
|
|
|
5314
5295
|
toolsTokens += estimateToolDefTokens(t);
|
|
5315
5296
|
}
|
|
5316
5297
|
const total = messagesTokens + systemTokens + toolsTokens;
|
|
5298
|
+
calState(calibrationKey).prevEst = total;
|
|
5317
5299
|
return {
|
|
5318
5300
|
messages: messagesTokens,
|
|
5319
5301
|
systemPrompt: systemTokens,
|
|
@@ -5321,39 +5303,164 @@ function estimateRequestTokens(messages, systemPrompt, tools) {
|
|
|
5321
5303
|
total
|
|
5322
5304
|
};
|
|
5323
5305
|
}
|
|
5324
|
-
function estimateRequestTokensCalibrated(messages, systemPrompt, tools) {
|
|
5325
|
-
const result = estimateRequestTokens(messages, systemPrompt, tools);
|
|
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
|
+
}
|
|
5326
5318
|
return result;
|
|
5327
5319
|
}
|
|
5328
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 hasToolUse2 = m.content.some((b) => b.type === "tool_use");
|
|
5347
|
+
if (hasToolUse2 && 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 findSafeBoundary(messages, from, to) {
|
|
5412
|
+
for (let i = to; i >= from; i--) {
|
|
5413
|
+
const m = messages[i];
|
|
5414
|
+
if (!m) continue;
|
|
5415
|
+
if (m.role === "user" && hasTextContent(m)) {
|
|
5416
|
+
return findExchangeStart(messages, i);
|
|
5417
|
+
}
|
|
5418
|
+
}
|
|
5419
|
+
return -1;
|
|
5420
|
+
}
|
|
5421
|
+
function findExchangeStart(messages, userIndex) {
|
|
5422
|
+
for (let i = userIndex - 1; i >= 0; i--) {
|
|
5423
|
+
const m = messages[i];
|
|
5424
|
+
if (!m) continue;
|
|
5425
|
+
if (m.role === "assistant") {
|
|
5426
|
+
const hasToolUse2 = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
|
|
5427
|
+
if (!hasToolUse2) return i + 1;
|
|
5428
|
+
} else if (m.role === "user") {
|
|
5429
|
+
return i;
|
|
5430
|
+
}
|
|
5431
|
+
}
|
|
5432
|
+
return 0;
|
|
5433
|
+
}
|
|
5434
|
+
|
|
5329
5435
|
// src/execution/compactor.ts
|
|
5330
5436
|
var HybridCompactor = class {
|
|
5331
5437
|
preserveK;
|
|
5332
5438
|
eliseThreshold;
|
|
5333
|
-
estimator;
|
|
5334
5439
|
constructor(opts = {}) {
|
|
5335
5440
|
this.preserveK = opts.preserveK ?? 5;
|
|
5336
5441
|
this.eliseThreshold = opts.eliseThreshold ?? 2e3;
|
|
5337
|
-
this.estimator = opts.estimator ?? estimateTextTokens;
|
|
5338
5442
|
}
|
|
5339
5443
|
async compact(ctx, opts = {}) {
|
|
5340
|
-
const beforeTokens =
|
|
5444
|
+
const beforeTokens = estimateMessages(ctx.messages);
|
|
5341
5445
|
const beforeFull = this.estimateFullRequest(ctx);
|
|
5342
5446
|
const reductions = [];
|
|
5343
5447
|
const policy = readContextWindowPolicy(ctx);
|
|
5344
5448
|
const preserveK = policy?.preserveK ?? this.preserveK;
|
|
5345
5449
|
const eliseThreshold = policy?.eliseThreshold ?? this.eliseThreshold;
|
|
5346
|
-
const
|
|
5347
|
-
if (
|
|
5450
|
+
const elide = eliseOldToolResults(ctx.messages, { preserveK, eliseThreshold });
|
|
5451
|
+
if (elide.changed) ctx.state.replaceMessages(elide.messages);
|
|
5452
|
+
if (elide.saved > 0) reductions.push({ phase: "elision", saved: elide.saved });
|
|
5453
|
+
let collapsedDigest;
|
|
5348
5454
|
if (opts.aggressive) {
|
|
5349
|
-
const
|
|
5350
|
-
if (
|
|
5455
|
+
const phase2 = this.collapseAncientTurns(ctx, preserveK);
|
|
5456
|
+
if (phase2.saved > 0) reductions.push({ phase: "summary", saved: phase2.saved });
|
|
5457
|
+
collapsedDigest = phase2.digest;
|
|
5351
5458
|
}
|
|
5352
5459
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
5353
5460
|
if (repaired.report.changed) {
|
|
5354
5461
|
ctx.state.replaceMessages(repaired.messages);
|
|
5355
5462
|
}
|
|
5356
|
-
const afterTokens =
|
|
5463
|
+
const afterTokens = estimateMessages(ctx.messages);
|
|
5357
5464
|
const afterFull = this.estimateFullRequest(ctx);
|
|
5358
5465
|
return {
|
|
5359
5466
|
before: beforeTokens,
|
|
@@ -5361,6 +5468,7 @@ var HybridCompactor = class {
|
|
|
5361
5468
|
fullRequestTokensBefore: beforeFull,
|
|
5362
5469
|
fullRequestTokensAfter: afterFull,
|
|
5363
5470
|
reductions,
|
|
5471
|
+
collapsedDigest,
|
|
5364
5472
|
repaired: repaired.report.changed ? {
|
|
5365
5473
|
removedToolUses: repaired.report.removedToolUses,
|
|
5366
5474
|
removedToolResults: repaired.report.removedToolResults,
|
|
@@ -5373,72 +5481,23 @@ var HybridCompactor = class {
|
|
|
5373
5481
|
* This is the accurate figure for context-window pressure monitoring.
|
|
5374
5482
|
*/
|
|
5375
5483
|
estimateFullRequest(ctx) {
|
|
5376
|
-
|
|
5377
|
-
return breakdown.total;
|
|
5378
|
-
}
|
|
5379
|
-
eliseOldToolResults(ctx, preserveK = this.preserveK, eliseThreshold = this.eliseThreshold) {
|
|
5380
|
-
const messages = ctx.messages;
|
|
5381
|
-
let pairCount = 0;
|
|
5382
|
-
let preserveStart = messages.length;
|
|
5383
|
-
for (let i = messages.length - 1; i >= 0 && pairCount < preserveK; i--) {
|
|
5384
|
-
const m = messages[i];
|
|
5385
|
-
if (!m) continue;
|
|
5386
|
-
if (m.role === "user" || m.role === "assistant") {
|
|
5387
|
-
pairCount++;
|
|
5388
|
-
preserveStart = i;
|
|
5389
|
-
}
|
|
5390
|
-
}
|
|
5391
|
-
for (let i = preserveStart; i < messages.length; i++) {
|
|
5392
|
-
const m = messages[i];
|
|
5393
|
-
if (!m || typeof m.content === "string" || !Array.isArray(m.content)) continue;
|
|
5394
|
-
const hasToolUse2 = m.content.some((b) => b.type === "tool_use");
|
|
5395
|
-
if (hasToolUse2 && i + 1 < messages.length) {
|
|
5396
|
-
const next = messages[i + 1];
|
|
5397
|
-
if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => b.type === "tool_result")) {
|
|
5398
|
-
preserveStart = i + 1;
|
|
5399
|
-
}
|
|
5400
|
-
}
|
|
5401
|
-
}
|
|
5402
|
-
let saved = 0;
|
|
5403
|
-
let changed = false;
|
|
5404
|
-
const nextMessages = new Array(messages.length);
|
|
5405
|
-
for (let i = 0; i < messages.length; i++) {
|
|
5406
|
-
const msg = messages[i];
|
|
5407
|
-
if (i >= preserveStart) {
|
|
5408
|
-
nextMessages[i] = msg;
|
|
5409
|
-
continue;
|
|
5410
|
-
}
|
|
5411
|
-
if (!msg || !Array.isArray(msg.content)) {
|
|
5412
|
-
nextMessages[i] = msg;
|
|
5413
|
-
continue;
|
|
5414
|
-
}
|
|
5415
|
-
const newContent = msg.content.map((b) => {
|
|
5416
|
-
if (b.type !== "tool_result") return b;
|
|
5417
|
-
const tokens = estimateToolResultTokens(b.content);
|
|
5418
|
-
if (tokens < eliseThreshold) return b;
|
|
5419
|
-
saved += tokens;
|
|
5420
|
-
const elided = {
|
|
5421
|
-
type: "tool_result",
|
|
5422
|
-
tool_use_id: b.tool_use_id,
|
|
5423
|
-
content: `[elided: ~${tokens} tokens]`,
|
|
5424
|
-
is_error: b.is_error
|
|
5425
|
-
};
|
|
5426
|
-
return elided;
|
|
5427
|
-
});
|
|
5428
|
-
if (newContent.length === msg.content.length && newContent.every((b, idx) => b === msg.content[idx])) {
|
|
5429
|
-
nextMessages[i] = msg;
|
|
5430
|
-
} else {
|
|
5431
|
-
nextMessages[i] = { ...msg, content: newContent };
|
|
5432
|
-
changed = true;
|
|
5433
|
-
}
|
|
5434
|
-
}
|
|
5435
|
-
if (changed) ctx.state.replaceMessages(nextMessages);
|
|
5436
|
-
return saved;
|
|
5484
|
+
return estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
|
|
5437
5485
|
}
|
|
5486
|
+
/**
|
|
5487
|
+
* Lossless rule-based collapse of ancient turns into a single digest message.
|
|
5488
|
+
*
|
|
5489
|
+
* Preserves ALL textual content of the collapsed range — user instructions,
|
|
5490
|
+
* assistant decisions/conclusions, and any prior digests (chained forward so
|
|
5491
|
+
* the digest stays lossless across repeated compactions). Only `tool_use` /
|
|
5492
|
+
* `tool_result` protocol blocks are dropped and replaced with a count marker;
|
|
5493
|
+
* their full payload already lives in the session log. No sub-LLM call.
|
|
5494
|
+
*
|
|
5495
|
+
* Returns the token savings and the digest text (for audit logging).
|
|
5496
|
+
*/
|
|
5438
5497
|
collapseAncientTurns(ctx, preserveK = this.preserveK) {
|
|
5439
5498
|
const messages = ctx.messages;
|
|
5440
5499
|
const cutTarget = Math.max(0, messages.length - preserveK * 2);
|
|
5441
|
-
if (cutTarget <= 0) return 0;
|
|
5500
|
+
if (cutTarget <= 0) return { saved: 0 };
|
|
5442
5501
|
let boundary = -1;
|
|
5443
5502
|
for (let i = cutTarget; i < messages.length; i++) {
|
|
5444
5503
|
const m = messages[i];
|
|
@@ -5448,34 +5507,20 @@ var HybridCompactor = class {
|
|
|
5448
5507
|
break;
|
|
5449
5508
|
}
|
|
5450
5509
|
}
|
|
5451
|
-
if (boundary <= 0) return 0;
|
|
5510
|
+
if (boundary <= 0) return { saved: 0 };
|
|
5452
5511
|
const removed = messages.slice(0, boundary);
|
|
5453
|
-
const removedTokens =
|
|
5454
|
-
const
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
{ role: "assistant", content: "Continuing from compacted context." }
|
|
5460
|
-
];
|
|
5512
|
+
const removedTokens = estimateMessages(removed);
|
|
5513
|
+
const digest = buildLosslessDigest(removed) || `${removed.length} earlier turns (no textual content; tool I/O omitted \u2014 see session log)`;
|
|
5514
|
+
const summaryMsg = {
|
|
5515
|
+
role: "system",
|
|
5516
|
+
content: `[prior_turns_digest: ${digest}]`
|
|
5517
|
+
};
|
|
5461
5518
|
const tail = ctx.messages.slice(boundary);
|
|
5462
|
-
ctx.state.replaceMessages([
|
|
5463
|
-
return
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
for (const m of messages) {
|
|
5468
|
-
if (typeof m.content === "string") {
|
|
5469
|
-
total += this.estimator(m.content);
|
|
5470
|
-
} else {
|
|
5471
|
-
for (const b of m.content) {
|
|
5472
|
-
if (b.type === "text") total += this.estimator(b.text);
|
|
5473
|
-
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
5474
|
-
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
5475
|
-
}
|
|
5476
|
-
}
|
|
5477
|
-
}
|
|
5478
|
-
return total;
|
|
5519
|
+
ctx.state.replaceMessages([summaryMsg, ...tail]);
|
|
5520
|
+
return {
|
|
5521
|
+
saved: Math.max(0, removedTokens - estimateMessages([summaryMsg])),
|
|
5522
|
+
digest
|
|
5523
|
+
};
|
|
5479
5524
|
}
|
|
5480
5525
|
};
|
|
5481
5526
|
function readContextWindowPolicy(ctx) {
|
|
@@ -5487,15 +5532,6 @@ function readContextWindowPolicy(ctx) {
|
|
|
5487
5532
|
}
|
|
5488
5533
|
return candidate;
|
|
5489
5534
|
}
|
|
5490
|
-
function hasTextContent(m) {
|
|
5491
|
-
if (typeof m.content === "string") return m.content.trim().length > 0;
|
|
5492
|
-
return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
|
|
5493
|
-
}
|
|
5494
|
-
|
|
5495
|
-
// src/types/blocks.ts
|
|
5496
|
-
function isTextBlock(b) {
|
|
5497
|
-
return b.type === "text";
|
|
5498
|
-
}
|
|
5499
5535
|
|
|
5500
5536
|
// src/execution/intelligent-compactor.ts
|
|
5501
5537
|
var IntelligentCompactor = class {
|
|
@@ -5520,23 +5556,25 @@ var IntelligentCompactor = class {
|
|
|
5520
5556
|
this.summarizerModel = opts.summarizerModel;
|
|
5521
5557
|
}
|
|
5522
5558
|
async compact(ctx, opts = {}) {
|
|
5523
|
-
const beforeTokens =
|
|
5559
|
+
const beforeTokens = estimateMessages(ctx.messages);
|
|
5524
5560
|
const beforeFull = this.estimateFullRequest(ctx);
|
|
5525
5561
|
const reductions = [];
|
|
5526
5562
|
const load = beforeFull / this.maxContext;
|
|
5527
5563
|
const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
|
|
5528
|
-
const saved1 = this.
|
|
5564
|
+
const saved1 = this.elide(ctx);
|
|
5529
5565
|
if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
|
|
5566
|
+
let collapsedDigest;
|
|
5530
5567
|
if (aggressive) {
|
|
5531
|
-
const
|
|
5532
|
-
if (
|
|
5568
|
+
const phase2 = await this.summarizeAncientTurns(ctx);
|
|
5569
|
+
if (phase2.saved > 0) reductions.push({ phase: "summary", saved: phase2.saved });
|
|
5570
|
+
collapsedDigest = phase2.digest;
|
|
5533
5571
|
} else if (load >= this.warnThreshold) {
|
|
5534
|
-
const saved2 = this.
|
|
5572
|
+
const saved2 = this.elide(ctx);
|
|
5535
5573
|
if (saved2 > 0) reductions.push({ phase: "elision", saved: saved2 });
|
|
5536
5574
|
}
|
|
5537
5575
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
5538
5576
|
if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
|
|
5539
|
-
const afterTokens =
|
|
5577
|
+
const afterTokens = estimateMessages(ctx.messages);
|
|
5540
5578
|
const afterFull = this.estimateFullRequest(ctx);
|
|
5541
5579
|
return {
|
|
5542
5580
|
before: beforeTokens,
|
|
@@ -5544,6 +5582,7 @@ var IntelligentCompactor = class {
|
|
|
5544
5582
|
fullRequestTokensBefore: beforeFull,
|
|
5545
5583
|
fullRequestTokensAfter: afterFull,
|
|
5546
5584
|
reductions,
|
|
5585
|
+
collapsedDigest,
|
|
5547
5586
|
repaired: repaired.report.changed ? {
|
|
5548
5587
|
removedToolUses: repaired.report.removedToolUses,
|
|
5549
5588
|
removedToolResults: repaired.report.removedToolResults,
|
|
@@ -5556,75 +5595,39 @@ var IntelligentCompactor = class {
|
|
|
5556
5595
|
* This is the accurate figure for context-window pressure monitoring.
|
|
5557
5596
|
*/
|
|
5558
5597
|
estimateFullRequest(ctx) {
|
|
5559
|
-
|
|
5560
|
-
|
|
5598
|
+
return estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
|
|
5599
|
+
}
|
|
5600
|
+
/** Run shared tool-result elision and commit through ConversationState. */
|
|
5601
|
+
elide(ctx) {
|
|
5602
|
+
const result = eliseOldToolResults(ctx.messages, {
|
|
5603
|
+
preserveK: this.preserveK,
|
|
5604
|
+
eliseThreshold: this.eliseThreshold
|
|
5605
|
+
});
|
|
5606
|
+
if (result.changed) ctx.state.replaceMessages(result.messages);
|
|
5607
|
+
return result.saved;
|
|
5561
5608
|
}
|
|
5562
5609
|
async summarizeAncientTurns(ctx) {
|
|
5563
5610
|
const messages = ctx.messages;
|
|
5564
5611
|
const cutoff = Math.max(0, messages.length - this.preserveK * 2);
|
|
5565
|
-
if (cutoff <= 2) return 0;
|
|
5566
|
-
const boundary =
|
|
5567
|
-
if (boundary <= 1) return 0;
|
|
5612
|
+
if (cutoff <= 2) return { saved: 0 };
|
|
5613
|
+
const boundary = findSafeBoundary(messages, 0, cutoff);
|
|
5614
|
+
if (boundary <= 1) return { saved: 0 };
|
|
5568
5615
|
const toSummarize = messages.slice(0, boundary);
|
|
5569
|
-
const removedTokens =
|
|
5616
|
+
const removedTokens = estimateMessages(toSummarize);
|
|
5570
5617
|
let summaryText;
|
|
5571
5618
|
try {
|
|
5572
5619
|
summaryText = await this.callSummarizer(toSummarize, ctx);
|
|
5573
5620
|
} catch {
|
|
5574
|
-
|
|
5575
|
-
for (const m of toSummarize) {
|
|
5576
|
-
if (m.role === "system") continue;
|
|
5577
|
-
if (typeof m.content === "string") {
|
|
5578
|
-
preservedMessages.push(m);
|
|
5579
|
-
} else if (Array.isArray(m.content)) {
|
|
5580
|
-
const textParts = m.content.filter(isTextBlock);
|
|
5581
|
-
const toolParts = m.content.filter((b) => b.type === "tool_use" || b.type === "tool_result");
|
|
5582
|
-
const next = {
|
|
5583
|
-
role: m.role,
|
|
5584
|
-
content: [
|
|
5585
|
-
...textParts,
|
|
5586
|
-
...toolParts.length > 0 ? [{ type: "text", text: `[${toolParts.length} tool call(s) omitted]` }] : []
|
|
5587
|
-
]
|
|
5588
|
-
};
|
|
5589
|
-
preservedMessages.push(next);
|
|
5590
|
-
}
|
|
5591
|
-
}
|
|
5592
|
-
summaryText = preservedMessages.map((m) => `[${m.role}]: ${typeof m.content === "string" ? m.content : m.content.filter(isTextBlock).map((b) => b.text).join(" ")}`).join("\n");
|
|
5593
|
-
if (!summaryText) summaryText = `${toSummarize.length} earlier turns (semantic content preserved)`;
|
|
5594
|
-
}
|
|
5595
|
-
const summaryMsg = {
|
|
5596
|
-
role: "system",
|
|
5597
|
-
content: `[prior_turns_summary: ${summaryText}]`
|
|
5598
|
-
};
|
|
5599
|
-
const summaryTokens = this.estimateTokens([summaryMsg]);
|
|
5600
|
-
const tail = ctx.messages.slice(boundary);
|
|
5601
|
-
ctx.state.replaceMessages([summaryMsg, ...tail]);
|
|
5602
|
-
return Math.max(0, removedTokens - summaryTokens);
|
|
5603
|
-
}
|
|
5604
|
-
findSafeBoundary(messages, from, to) {
|
|
5605
|
-
for (let i = to; i >= from; i--) {
|
|
5606
|
-
const m = messages[i];
|
|
5607
|
-
if (!m) continue;
|
|
5608
|
-
if (m.role === "user" && this.hasTextContent(m)) {
|
|
5609
|
-
return this.findExchangeStart(messages, i);
|
|
5610
|
-
}
|
|
5611
|
-
}
|
|
5612
|
-
return -1;
|
|
5613
|
-
}
|
|
5614
|
-
findExchangeStart(messages, userIndex) {
|
|
5615
|
-
for (let i = userIndex - 1; i >= 0; i--) {
|
|
5616
|
-
const m = messages[i];
|
|
5617
|
-
if (!m) continue;
|
|
5618
|
-
if (m.role === "assistant") {
|
|
5619
|
-
const hasToolUse2 = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
|
|
5620
|
-
if (!hasToolUse2) {
|
|
5621
|
-
return i + 1;
|
|
5622
|
-
}
|
|
5623
|
-
} else if (m.role !== "user") ; else {
|
|
5624
|
-
return i;
|
|
5625
|
-
}
|
|
5621
|
+
summaryText = buildLosslessDigest(toSummarize) || `${toSummarize.length} earlier turns (semantic content preserved)`;
|
|
5626
5622
|
}
|
|
5627
|
-
|
|
5623
|
+
const summaryMsg = {
|
|
5624
|
+
role: "system",
|
|
5625
|
+
content: `[prior_turns_summary: ${summaryText}]`
|
|
5626
|
+
};
|
|
5627
|
+
const summaryTokens = estimateMessages([summaryMsg]);
|
|
5628
|
+
const tail = ctx.messages.slice(boundary);
|
|
5629
|
+
ctx.state.replaceMessages([summaryMsg, ...tail]);
|
|
5630
|
+
return { saved: Math.max(0, removedTokens - summaryTokens), digest: summaryText };
|
|
5628
5631
|
}
|
|
5629
5632
|
async callSummarizer(messages, ctx) {
|
|
5630
5633
|
const prompt = [
|
|
@@ -5659,86 +5662,6 @@ var IntelligentCompactor = class {
|
|
|
5659
5662
|
}
|
|
5660
5663
|
return [{ type: "text", text: lines.join("\n") }];
|
|
5661
5664
|
}
|
|
5662
|
-
lightweightCompact(ctx) {
|
|
5663
|
-
return this.eliseOldToolResults(ctx);
|
|
5664
|
-
}
|
|
5665
|
-
eliseOldToolResults(ctx) {
|
|
5666
|
-
const messages = ctx.messages;
|
|
5667
|
-
let pairCount = 0;
|
|
5668
|
-
let preserveStart = messages.length;
|
|
5669
|
-
for (let i = messages.length - 1; i >= 0 && pairCount < this.preserveK; i--) {
|
|
5670
|
-
const m = messages[i];
|
|
5671
|
-
if (!m) continue;
|
|
5672
|
-
if (m.role === "user" || m.role === "assistant") {
|
|
5673
|
-
pairCount++;
|
|
5674
|
-
preserveStart = i;
|
|
5675
|
-
}
|
|
5676
|
-
}
|
|
5677
|
-
for (let i = preserveStart; i < messages.length; i++) {
|
|
5678
|
-
const m = messages[i];
|
|
5679
|
-
if (!m || typeof m.content === "string" || !Array.isArray(m.content)) continue;
|
|
5680
|
-
const hasToolUse2 = m.content.some((b) => b.type === "tool_use");
|
|
5681
|
-
if (hasToolUse2 && i + 1 < messages.length) {
|
|
5682
|
-
const next = messages[i + 1];
|
|
5683
|
-
if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => b.type === "tool_result")) {
|
|
5684
|
-
preserveStart = i + 1;
|
|
5685
|
-
}
|
|
5686
|
-
}
|
|
5687
|
-
}
|
|
5688
|
-
let saved = 0;
|
|
5689
|
-
let changed = false;
|
|
5690
|
-
const nextMessages = new Array(messages.length);
|
|
5691
|
-
for (let i = 0; i < messages.length; i++) {
|
|
5692
|
-
const msg = messages[i];
|
|
5693
|
-
if (i >= preserveStart) {
|
|
5694
|
-
nextMessages[i] = msg;
|
|
5695
|
-
continue;
|
|
5696
|
-
}
|
|
5697
|
-
if (!msg || !Array.isArray(msg.content)) {
|
|
5698
|
-
nextMessages[i] = msg;
|
|
5699
|
-
continue;
|
|
5700
|
-
}
|
|
5701
|
-
const newContent = msg.content.map((b) => {
|
|
5702
|
-
if (b.type !== "tool_result") return b;
|
|
5703
|
-
const tokens = estimateToolResultTokens(b.content);
|
|
5704
|
-
if (tokens < this.eliseThreshold) return b;
|
|
5705
|
-
saved += tokens;
|
|
5706
|
-
return {
|
|
5707
|
-
type: "tool_result",
|
|
5708
|
-
tool_use_id: b.tool_use_id,
|
|
5709
|
-
content: `[elided: ~${tokens} tokens]`,
|
|
5710
|
-
is_error: b.is_error
|
|
5711
|
-
};
|
|
5712
|
-
});
|
|
5713
|
-
if (newContent.length === msg.content.length && newContent.every((b, idx) => b === msg.content[idx])) {
|
|
5714
|
-
nextMessages[i] = msg;
|
|
5715
|
-
} else {
|
|
5716
|
-
nextMessages[i] = { ...msg, content: newContent };
|
|
5717
|
-
changed = true;
|
|
5718
|
-
}
|
|
5719
|
-
}
|
|
5720
|
-
if (changed) ctx.state.replaceMessages(nextMessages);
|
|
5721
|
-
return saved;
|
|
5722
|
-
}
|
|
5723
|
-
hasTextContent(m) {
|
|
5724
|
-
if (typeof m.content === "string") return m.content.trim().length > 0;
|
|
5725
|
-
return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
|
|
5726
|
-
}
|
|
5727
|
-
estimateTokens(messages) {
|
|
5728
|
-
let total = 0;
|
|
5729
|
-
for (const m of messages) {
|
|
5730
|
-
if (typeof m.content === "string") {
|
|
5731
|
-
total += estimateTextTokens(m.content);
|
|
5732
|
-
} else {
|
|
5733
|
-
for (const b of m.content) {
|
|
5734
|
-
if (b.type === "text") total += estimateTextTokens(b.text);
|
|
5735
|
-
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
5736
|
-
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
5737
|
-
}
|
|
5738
|
-
}
|
|
5739
|
-
}
|
|
5740
|
-
return total;
|
|
5741
|
-
}
|
|
5742
5665
|
};
|
|
5743
5666
|
|
|
5744
5667
|
// src/models/llm-selector.ts
|
|
@@ -5764,20 +5687,6 @@ Rules:
|
|
|
5764
5687
|
- If unsure, keep rather than collapse (errors are more costly than waste)
|
|
5765
5688
|
|
|
5766
5689
|
Return ONLY the JSON object, no markdown, no explanation outside the JSON.`;
|
|
5767
|
-
function estimateTokens(messages) {
|
|
5768
|
-
let total = 0;
|
|
5769
|
-
for (const m of messages) {
|
|
5770
|
-
if (typeof m.content === "string") {
|
|
5771
|
-
total += Math.ceil(m.content.length / 4);
|
|
5772
|
-
} else if (Array.isArray(m.content)) {
|
|
5773
|
-
for (const b of m.content) {
|
|
5774
|
-
if (b.type === "text") total += Math.ceil(b.text.length / 4);
|
|
5775
|
-
else total += Math.ceil(JSON.stringify(b).length / 4);
|
|
5776
|
-
}
|
|
5777
|
-
}
|
|
5778
|
-
}
|
|
5779
|
-
return total;
|
|
5780
|
-
}
|
|
5781
5690
|
function formatMessages(messages, maxChars = 8e3) {
|
|
5782
5691
|
const lines = [];
|
|
5783
5692
|
let used = 0;
|
|
@@ -5816,7 +5725,7 @@ var LLMSelector = class {
|
|
|
5816
5725
|
async select(messages, maxToKeep) {
|
|
5817
5726
|
const effectiveBudget = Math.min(maxToKeep, this.maxContextTokens);
|
|
5818
5727
|
const historyText = formatMessages(messages);
|
|
5819
|
-
const totalTokens =
|
|
5728
|
+
const totalTokens = estimateMessageTokens(messages);
|
|
5820
5729
|
const systemText = `${this.systemPrompt}
|
|
5821
5730
|
|
|
5822
5731
|
Conversation (${messages.length} messages, ~${totalTokens} tokens, budget: ${effectiveBudget}):
|
|
@@ -5848,10 +5757,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
5848
5757
|
let startIdx = 0;
|
|
5849
5758
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
5850
5759
|
const m = expectDefined(messages[i]);
|
|
5851
|
-
const cost =
|
|
5852
|
-
(acc, b) => acc + (b.type === "text" ? Math.ceil(b.text.length / 4) : Math.ceil(JSON.stringify(b).length / 4)),
|
|
5853
|
-
0
|
|
5854
|
-
);
|
|
5760
|
+
const cost = estimateMessageTokens([m]);
|
|
5855
5761
|
if (tokenCount + cost <= budget) {
|
|
5856
5762
|
tokenCount += cost;
|
|
5857
5763
|
} else {
|
|
@@ -6084,52 +5990,12 @@ Summarize the following message range:`;
|
|
|
6084
5990
|
return Math.floor(this.maxContext * 0.75);
|
|
6085
5991
|
}
|
|
6086
5992
|
eliseOldToolResults(ctx) {
|
|
6087
|
-
const
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
if (m.role === "user" || m.role === "assistant") {
|
|
6094
|
-
pairCount++;
|
|
6095
|
-
preserveStart = i;
|
|
6096
|
-
}
|
|
6097
|
-
}
|
|
6098
|
-
let saved = 0;
|
|
6099
|
-
let changed = false;
|
|
6100
|
-
const nextMessages = new Array(messages.length);
|
|
6101
|
-
for (let i = 0; i < messages.length; i++) {
|
|
6102
|
-
const msg = messages[i];
|
|
6103
|
-
if (i >= preserveStart) {
|
|
6104
|
-
nextMessages[i] = msg;
|
|
6105
|
-
continue;
|
|
6106
|
-
}
|
|
6107
|
-
if (!msg || !Array.isArray(msg.content)) {
|
|
6108
|
-
nextMessages[i] = msg;
|
|
6109
|
-
continue;
|
|
6110
|
-
}
|
|
6111
|
-
const newContent = msg.content.map((b) => {
|
|
6112
|
-
if (b.type !== "tool_result") return b;
|
|
6113
|
-
const text = typeof b.content === "string" ? b.content : JSON.stringify(b.content);
|
|
6114
|
-
const tokens = this.roughTokenEstimate(text);
|
|
6115
|
-
if (tokens < this.eliseThreshold) return b;
|
|
6116
|
-
saved += tokens;
|
|
6117
|
-
return {
|
|
6118
|
-
type: "tool_result",
|
|
6119
|
-
tool_use_id: b.tool_use_id,
|
|
6120
|
-
content: `[elided: ~${tokens} tokens]`,
|
|
6121
|
-
is_error: b.is_error
|
|
6122
|
-
};
|
|
6123
|
-
});
|
|
6124
|
-
if (newContent.every((b, idx) => b === msg.content[idx])) {
|
|
6125
|
-
nextMessages[i] = msg;
|
|
6126
|
-
} else {
|
|
6127
|
-
nextMessages[i] = { ...msg, content: newContent };
|
|
6128
|
-
changed = true;
|
|
6129
|
-
}
|
|
6130
|
-
}
|
|
6131
|
-
if (changed) ctx.state.replaceMessages(nextMessages);
|
|
6132
|
-
return saved;
|
|
5993
|
+
const result = eliseOldToolResults(ctx.messages, {
|
|
5994
|
+
preserveK: this.preserveK,
|
|
5995
|
+
eliseThreshold: this.eliseThreshold
|
|
5996
|
+
});
|
|
5997
|
+
if (result.changed) ctx.state.replaceMessages(result.messages);
|
|
5998
|
+
return result.saved;
|
|
6133
5999
|
}
|
|
6134
6000
|
hasTextContent(m) {
|
|
6135
6001
|
if (typeof m.content === "string") return m.content.trim().length > 0;
|
|
@@ -6159,8 +6025,80 @@ Summarize the following message range:`;
|
|
|
6159
6025
|
}
|
|
6160
6026
|
};
|
|
6161
6027
|
|
|
6028
|
+
// src/execution/strategy-compactor.ts
|
|
6029
|
+
function createStrategyCompactor(opts = {}) {
|
|
6030
|
+
const requested = opts.strategy ?? (opts.llmSelector ? "selective" : "hybrid");
|
|
6031
|
+
const strategy = requested;
|
|
6032
|
+
if (strategy === "intelligent" || strategy === "selective") {
|
|
6033
|
+
return new ProviderBackedCompactor(strategy, opts);
|
|
6034
|
+
}
|
|
6035
|
+
return new HybridCompactor({
|
|
6036
|
+
preserveK: opts.preserveK,
|
|
6037
|
+
eliseThreshold: opts.eliseThreshold
|
|
6038
|
+
});
|
|
6039
|
+
}
|
|
6040
|
+
var ProviderBackedCompactor = class {
|
|
6041
|
+
constructor(strategy, opts) {
|
|
6042
|
+
this.strategy = strategy;
|
|
6043
|
+
this.opts = opts;
|
|
6044
|
+
}
|
|
6045
|
+
strategy;
|
|
6046
|
+
opts;
|
|
6047
|
+
async compact(ctx, compactOpts = {}) {
|
|
6048
|
+
return this.resolveInner(ctx).compact(ctx, compactOpts);
|
|
6049
|
+
}
|
|
6050
|
+
/**
|
|
6051
|
+
* Construct the concrete compactor for this run. Rebuilt per call (cheap, no
|
|
6052
|
+
* I/O) so a model switch — which changes `ctx.provider.capabilities.maxContext`
|
|
6053
|
+
* — is always reflected. Reads the active ContextWindowPolicy from `ctx.meta`
|
|
6054
|
+
* so the LLM compactors honor the same thresholds/preserveK as the policy.
|
|
6055
|
+
*/
|
|
6056
|
+
resolveInner(ctx) {
|
|
6057
|
+
const provider = ctx.provider;
|
|
6058
|
+
if (!provider) {
|
|
6059
|
+
return new HybridCompactor({
|
|
6060
|
+
preserveK: this.opts.preserveK,
|
|
6061
|
+
eliseThreshold: this.opts.eliseThreshold
|
|
6062
|
+
});
|
|
6063
|
+
}
|
|
6064
|
+
const policy = readPolicy(ctx);
|
|
6065
|
+
const maxContext = provider.capabilities?.maxContext || void 0;
|
|
6066
|
+
const thresholds = policy?.thresholds;
|
|
6067
|
+
const common = {
|
|
6068
|
+
provider,
|
|
6069
|
+
maxContext,
|
|
6070
|
+
preserveK: this.opts.preserveK ?? policy?.preserveK,
|
|
6071
|
+
eliseThreshold: this.opts.eliseThreshold ?? policy?.eliseThreshold,
|
|
6072
|
+
...thresholds ? { warnThreshold: thresholds.warn, softThreshold: thresholds.soft, hardThreshold: thresholds.hard } : {}
|
|
6073
|
+
};
|
|
6074
|
+
if (this.strategy === "selective") {
|
|
6075
|
+
return new SelectiveCompactor({
|
|
6076
|
+
...common,
|
|
6077
|
+
selectorModel: this.opts.summarizerModel,
|
|
6078
|
+
summarizerModel: this.opts.summarizerModel
|
|
6079
|
+
});
|
|
6080
|
+
}
|
|
6081
|
+
return new IntelligentCompactor({
|
|
6082
|
+
...common,
|
|
6083
|
+
summarizerModel: this.opts.summarizerModel
|
|
6084
|
+
});
|
|
6085
|
+
}
|
|
6086
|
+
};
|
|
6087
|
+
function readPolicy(ctx) {
|
|
6088
|
+
const policy = ctx.meta?.["contextWindowPolicy"];
|
|
6089
|
+
if (!policy || typeof policy !== "object") return null;
|
|
6090
|
+
const candidate = policy;
|
|
6091
|
+
if (typeof candidate.preserveK !== "number" || !candidate.thresholds) return null;
|
|
6092
|
+
return candidate;
|
|
6093
|
+
}
|
|
6094
|
+
|
|
6162
6095
|
// src/execution/auto-compaction-middleware.ts
|
|
6163
6096
|
var LEVEL_RANK2 = { warn: 0, soft: 1, hard: 2 };
|
|
6097
|
+
var MAX_DIGEST_LOG_CHARS = 4e3;
|
|
6098
|
+
function truncateDigest(digest) {
|
|
6099
|
+
if (digest.length <= MAX_DIGEST_LOG_CHARS) return digest;
|
|
6100
|
+
return `${digest.slice(0, MAX_DIGEST_LOG_CHARS)}\u2026 [+${digest.length - MAX_DIGEST_LOG_CHARS} chars; full turns in session log]`;
|
|
6101
|
+
}
|
|
6164
6102
|
var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
6165
6103
|
name = "AutoCompaction";
|
|
6166
6104
|
compactor;
|
|
@@ -6221,7 +6159,12 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
6221
6159
|
}
|
|
6222
6160
|
handler() {
|
|
6223
6161
|
return async (ctx, next) => {
|
|
6224
|
-
const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokensCalibrated(
|
|
6162
|
+
const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokensCalibrated(
|
|
6163
|
+
ctx.messages,
|
|
6164
|
+
ctx.systemPrompt,
|
|
6165
|
+
ctx.tools ?? [],
|
|
6166
|
+
`${ctx.provider?.id ?? "unknown"}/${ctx.model}`
|
|
6167
|
+
).total;
|
|
6225
6168
|
const load = tokens / this._maxContext;
|
|
6226
6169
|
const policy = this.policyProvider?.(ctx);
|
|
6227
6170
|
const thresholds = policy?.thresholds ?? {
|
|
@@ -6283,7 +6226,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
6283
6226
|
after: report.after,
|
|
6284
6227
|
level: pressure.level,
|
|
6285
6228
|
aggressive,
|
|
6286
|
-
reductions: report.reductions?.map((r) => ({ phase: r.phase, saved: r.saved }))
|
|
6229
|
+
reductions: report.reductions?.map((r) => ({ phase: r.phase, saved: r.saved })),
|
|
6230
|
+
// Record what was collapsed so the audit trail shows the preserved
|
|
6231
|
+
// content, not just token counts. Bounded to keep the log line small;
|
|
6232
|
+
// the full original turns are already in the session JSONL.
|
|
6233
|
+
...report.collapsedDigest ? { digest: truncateDigest(report.collapsedDigest) } : {}
|
|
6287
6234
|
});
|
|
6288
6235
|
ctx.clearFileTracking();
|
|
6289
6236
|
} catch (err) {
|
|
@@ -6318,10 +6265,10 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
6318
6265
|
// src/utils/json-schema-validate.ts
|
|
6319
6266
|
function validateAgainstSchema(value, schema) {
|
|
6320
6267
|
const errors = [];
|
|
6321
|
-
|
|
6268
|
+
walk2(value, schema, "", errors);
|
|
6322
6269
|
return { ok: errors.length === 0, errors };
|
|
6323
6270
|
}
|
|
6324
|
-
function
|
|
6271
|
+
function walk2(value, schema, path19, errors) {
|
|
6325
6272
|
if (schema.enum !== void 0) {
|
|
6326
6273
|
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
6327
6274
|
errors.push({
|
|
@@ -6350,13 +6297,13 @@ function walk3(value, schema, path19, errors) {
|
|
|
6350
6297
|
if (schema.properties) {
|
|
6351
6298
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
6352
6299
|
if (key in obj) {
|
|
6353
|
-
|
|
6300
|
+
walk2(obj[key], subSchema, joinPath(path19, key), errors);
|
|
6354
6301
|
}
|
|
6355
6302
|
}
|
|
6356
6303
|
}
|
|
6357
6304
|
}
|
|
6358
6305
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
6359
|
-
value.forEach((item, i) =>
|
|
6306
|
+
value.forEach((item, i) => walk2(item, schema.items, `${path19}[${i}]`, errors));
|
|
6360
6307
|
}
|
|
6361
6308
|
}
|
|
6362
6309
|
function checkType(value, type) {
|
|
@@ -6870,6 +6817,13 @@ function extractMalformedRaw(input) {
|
|
|
6870
6817
|
}
|
|
6871
6818
|
}
|
|
6872
6819
|
|
|
6820
|
+
// src/utils/assert-never.ts
|
|
6821
|
+
function assertNever(x, message) {
|
|
6822
|
+
throw new Error(
|
|
6823
|
+
`Unhandled case: ${JSON.stringify(x)}`
|
|
6824
|
+
);
|
|
6825
|
+
}
|
|
6826
|
+
|
|
6873
6827
|
// src/execution/autonomous-runner.ts
|
|
6874
6828
|
var DoneConditionChecker = class {
|
|
6875
6829
|
constructor(condition) {
|
|
@@ -6923,6 +6877,12 @@ var DoneConditionChecker = class {
|
|
|
6923
6877
|
return { done: true, reason: `max tool calls (${this.condition.maxToolCalls}) reached`, ...state };
|
|
6924
6878
|
}
|
|
6925
6879
|
break;
|
|
6880
|
+
case "all_tasks_done":
|
|
6881
|
+
break;
|
|
6882
|
+
case "custom":
|
|
6883
|
+
break;
|
|
6884
|
+
default:
|
|
6885
|
+
return assertNever(this.condition.type);
|
|
6926
6886
|
}
|
|
6927
6887
|
return { done: false, iterations: state.iterations, toolCalls: state.toolCalls };
|
|
6928
6888
|
}
|
|
@@ -7074,6 +7034,7 @@ function resolveWstackPaths(opts) {
|
|
|
7074
7034
|
projectTrust: path11.join(projectDir, "trust.json"),
|
|
7075
7035
|
projectMeta: path11.join(projectDir, "meta.json"),
|
|
7076
7036
|
projectLocalConfig: path11.join(projectDir, "config.local.json"),
|
|
7037
|
+
inProjectConfig: path11.join(opts.projectRoot, ".wrongstack", "config.json"),
|
|
7077
7038
|
inProjectAgentsFile: path11.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
|
|
7078
7039
|
inProjectSkills: path11.join(opts.projectRoot, ".wrongstack", "skills"),
|
|
7079
7040
|
inProjectWorktrees: path11.join(opts.projectRoot, ".wrongstack", "worktrees"),
|
|
@@ -7098,16 +7059,20 @@ async function loadGoal(filePath) {
|
|
|
7098
7059
|
let raw;
|
|
7099
7060
|
try {
|
|
7100
7061
|
raw = await fsp.readFile(filePath, "utf8");
|
|
7101
|
-
} catch {
|
|
7102
|
-
|
|
7062
|
+
} catch (err) {
|
|
7063
|
+
const code = err.code;
|
|
7064
|
+
if (code === "ENOENT") return null;
|
|
7065
|
+
throw err;
|
|
7103
7066
|
}
|
|
7104
7067
|
try {
|
|
7105
7068
|
const parsed = JSON.parse(raw);
|
|
7106
7069
|
if (parsed?.version !== 1 || typeof parsed.goal !== "string" || !Array.isArray(parsed.journal)) {
|
|
7070
|
+
console.warn(`[goal-store] Corrupt goal.json at ${filePath} \u2014 invalid schema. Consider deleting it and re-creating.`);
|
|
7107
7071
|
return null;
|
|
7108
7072
|
}
|
|
7109
7073
|
return parsed;
|
|
7110
7074
|
} catch {
|
|
7075
|
+
console.warn(`[goal-store] Corrupt goal.json at ${filePath} \u2014 JSON parse failed. Consider deleting it and re-creating.`);
|
|
7111
7076
|
return null;
|
|
7112
7077
|
}
|
|
7113
7078
|
}
|
|
@@ -7140,9 +7105,9 @@ function parseProgressFromText(text) {
|
|
|
7140
7105
|
const re = /\[progress:\s*(\d{1,3})%\]\s*(?:[—\-]\s*(.+))?/i;
|
|
7141
7106
|
const m = text.match(re);
|
|
7142
7107
|
if (!m) return null;
|
|
7143
|
-
const progress = Math.min(100, Math.max(0, Number.parseInt(m[1], 10)));
|
|
7108
|
+
const progress = Math.min(100, Math.max(0, Number.parseInt(m[1] ?? "0", 10)));
|
|
7144
7109
|
const note = m[2]?.trim() || void 0;
|
|
7145
|
-
return { progress, note };
|
|
7110
|
+
return note === void 0 ? { progress } : { progress, note };
|
|
7146
7111
|
}
|
|
7147
7112
|
function recordProgress(goal, progress, note) {
|
|
7148
7113
|
const clamped = Math.min(100, Math.max(0, progress));
|
|
@@ -7161,7 +7126,7 @@ function computeTrend(history) {
|
|
|
7161
7126
|
const recent = history.slice(-5);
|
|
7162
7127
|
const deltas = [];
|
|
7163
7128
|
for (let i = 1; i < recent.length; i++) {
|
|
7164
|
-
deltas.push(recent[i]
|
|
7129
|
+
deltas.push((recent[i]?.progress ?? 0) - (recent[i - 1]?.progress ?? 0));
|
|
7165
7130
|
}
|
|
7166
7131
|
if (deltas.length < 2) return void 0;
|
|
7167
7132
|
const avgDelta = deltas.reduce((a, b) => a + b, 0) / deltas.length;
|
|
@@ -7740,6 +7705,7 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
7740
7705
|
note: note.slice(0, 240)
|
|
7741
7706
|
});
|
|
7742
7707
|
await saveGoal(this.goalPath, withEntry);
|
|
7708
|
+
this.opts.onEternalStop?.();
|
|
7743
7709
|
}
|
|
7744
7710
|
/**
|
|
7745
7711
|
* Manually clear the goal — equivalent to `/goal clear` typed by the user.
|
|
@@ -7753,8 +7719,8 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
7753
7719
|
await saveGoal(this.goalPath, abandoned);
|
|
7754
7720
|
}
|
|
7755
7721
|
try {
|
|
7756
|
-
const { unlink:
|
|
7757
|
-
await
|
|
7722
|
+
const { unlink: unlink10 } = await import('fs/promises');
|
|
7723
|
+
await unlink10(this.goalPath);
|
|
7758
7724
|
} catch {
|
|
7759
7725
|
}
|
|
7760
7726
|
this.opts.onEternalStop?.();
|
|
@@ -8755,6 +8721,53 @@ Working rules:
|
|
|
8755
8721
|
"flaws"
|
|
8756
8722
|
]
|
|
8757
8723
|
}
|
|
8724
|
+
},
|
|
8725
|
+
{
|
|
8726
|
+
config: {
|
|
8727
|
+
id: "refactor-planner",
|
|
8728
|
+
name: "Refactor Planner",
|
|
8729
|
+
role: "refactor-planner",
|
|
8730
|
+
tools: [...PLAN_TOOLS, "diff"],
|
|
8731
|
+
prompt: `You are the Refactor Planner agent. Your job is to analyze code
|
|
8732
|
+
structure and produce a concrete, phased refactoring plan with risk
|
|
8733
|
+
assessment, dependency ordering, and rollback strategy.
|
|
8734
|
+
|
|
8735
|
+
Scope:
|
|
8736
|
+
- Map module-level dependencies (import graph)
|
|
8737
|
+
- Identify coupling hotspots (high fan-in/out modules)
|
|
8738
|
+
- Assess refactoring risk by complexity and test coverage
|
|
8739
|
+
- Generate phased plans with checkpoint milestones
|
|
8740
|
+
- Produce diff-friendly task lists (one task = one concern)
|
|
8741
|
+
|
|
8742
|
+
Input format you accept:
|
|
8743
|
+
{ "task": "plan | assess | roadmap", "target": "src/core", "constraint": "no-breaking-changes | minimal-downtime | full-rewrite", "focus": "architecture | performance | maintainability" }
|
|
8744
|
+
|
|
8745
|
+
Output: Markdown refactor plan with phases (Low Risk / Medium Risk / High Risk),
|
|
8746
|
+
dependency graph, rollback strategy, and exit criteria.
|
|
8747
|
+
|
|
8748
|
+
Working rules:
|
|
8749
|
+
- Always include rollback strategy \u2014 every refactor can fail
|
|
8750
|
+
- Merge tasks that take <1h into a single phase
|
|
8751
|
+
- Respect team constraints (reviewer availability, parallelization)
|
|
8752
|
+
- Never plan without analyzing the actual code first`
|
|
8753
|
+
},
|
|
8754
|
+
budget: HEAVY_BUDGET,
|
|
8755
|
+
capability: {
|
|
8756
|
+
phase: "planning",
|
|
8757
|
+
summary: "Refactoring planner: analyzes code structure, maps dependencies, produces risk-scored phased plans with rollback strategy.",
|
|
8758
|
+
keywords: [
|
|
8759
|
+
"refactor",
|
|
8760
|
+
"refactoring",
|
|
8761
|
+
"restructure",
|
|
8762
|
+
"debt",
|
|
8763
|
+
"technical debt",
|
|
8764
|
+
"clean up",
|
|
8765
|
+
"modularize",
|
|
8766
|
+
"decouple",
|
|
8767
|
+
"dependency graph",
|
|
8768
|
+
"code structure"
|
|
8769
|
+
]
|
|
8770
|
+
}
|
|
8758
8771
|
}
|
|
8759
8772
|
];
|
|
8760
8773
|
|
|
@@ -9294,6 +9307,157 @@ Working rules:
|
|
|
9294
9307
|
"robustness"
|
|
9295
9308
|
]
|
|
9296
9309
|
}
|
|
9310
|
+
},
|
|
9311
|
+
{
|
|
9312
|
+
config: {
|
|
9313
|
+
id: "security-scanner",
|
|
9314
|
+
name: "Security Scanner",
|
|
9315
|
+
role: "security-scanner",
|
|
9316
|
+
tools: [...TOOLS.inspect],
|
|
9317
|
+
prompt: `You are the Security Scanner agent. Your job is to scan code,
|
|
9318
|
+
configs, and dependencies for security issues from hardcoded secrets to
|
|
9319
|
+
supply chain risks.
|
|
9320
|
+
|
|
9321
|
+
Scope:
|
|
9322
|
+
- Detect hardcoded secrets: API keys, tokens, passwords, private keys
|
|
9323
|
+
- Find injection vectors: eval, innerHTML, SQL concat, shell injection
|
|
9324
|
+
- Identify insecure patterns: weak crypto, hardcoded IVs, disabled TLS
|
|
9325
|
+
- Scan dependencies for known CVEs (via npm/pnpm audit)
|
|
9326
|
+
- Flag supply chain risks: postinstall hooks, unverified scripts
|
|
9327
|
+
|
|
9328
|
+
Input format you accept:
|
|
9329
|
+
{ "task": "scan | audit | secrets | dependencies", "paths": ["src", "config"], "depth": "quick | normal | deep" }
|
|
9330
|
+
|
|
9331
|
+
Output: Markdown security report with severity-ranked findings, injection
|
|
9332
|
+
vectors, dependency issues, and a remediation checklist.
|
|
9333
|
+
|
|
9334
|
+
Working rules:
|
|
9335
|
+
- Never scan node_modules \u2014 use npm audit instead
|
|
9336
|
+
- Always provide remediation steps, not just findings
|
|
9337
|
+
- Verify regex-based secrets before flagging (false positive risk)
|
|
9338
|
+
- When in doubt, flag as medium rather than ignoring potential issues`
|
|
9339
|
+
},
|
|
9340
|
+
budget: HEAVY_BUDGET,
|
|
9341
|
+
capability: {
|
|
9342
|
+
phase: "verify",
|
|
9343
|
+
summary: "Security scanner: detects hardcoded secrets, injection vectors, insecure patterns, and supply-chain risks with remediation.",
|
|
9344
|
+
keywords: [
|
|
9345
|
+
"security",
|
|
9346
|
+
"scan",
|
|
9347
|
+
"vulnerability",
|
|
9348
|
+
"secret",
|
|
9349
|
+
"api key",
|
|
9350
|
+
"hardcoded",
|
|
9351
|
+
"injection",
|
|
9352
|
+
"cve",
|
|
9353
|
+
"audit dependencies",
|
|
9354
|
+
"supply chain",
|
|
9355
|
+
"xss",
|
|
9356
|
+
"sqli",
|
|
9357
|
+
"shell injection",
|
|
9358
|
+
"sensitive data",
|
|
9359
|
+
"credential"
|
|
9360
|
+
]
|
|
9361
|
+
}
|
|
9362
|
+
},
|
|
9363
|
+
{
|
|
9364
|
+
config: {
|
|
9365
|
+
id: "bug-hunter",
|
|
9366
|
+
name: "Bug Hunter",
|
|
9367
|
+
role: "bug-hunter",
|
|
9368
|
+
tools: [...TOOLS.inspect],
|
|
9369
|
+
prompt: `You are the Bug Hunter agent. Your job is to systematically scan
|
|
9370
|
+
source code for bugs, anti-patterns, and code smells using pattern matching
|
|
9371
|
+
and heuristics. Output a prioritized hit list with file:line references.
|
|
9372
|
+
|
|
9373
|
+
Scope:
|
|
9374
|
+
- Detect common bug patterns (uncaught errors, resource leaks, race conditions)
|
|
9375
|
+
- Identify anti-patterns (callback hell, God objects, circular deps)
|
|
9376
|
+
- Find TypeScript-specific issues (unsafe any, missing null checks, branded types)
|
|
9377
|
+
- Flag security-sensitive constructs (eval, innerHTML, hardcoded secrets)
|
|
9378
|
+
- Rank findings: critical > high > medium > low
|
|
9379
|
+
|
|
9380
|
+
Input format you accept:
|
|
9381
|
+
{ "task": "scan | hunt | check", "paths": ["src/**/*.ts"], "focus": "bugs | patterns | security | all", "severityThreshold": "medium" }
|
|
9382
|
+
|
|
9383
|
+
Output: Markdown bug hunt report with critically/high/medium/low sections.
|
|
9384
|
+
Each entry: **[TYPE]** \`file:line\` \u2014 description + suggested fix
|
|
9385
|
+
|
|
9386
|
+
Working rules:
|
|
9387
|
+
- Never scan node_modules \u2014 it's noise
|
|
9388
|
+
- Always include file:line for every finding
|
|
9389
|
+
- If >30% of findings are false positives, note the confidence level
|
|
9390
|
+
- Ask director for clarification if paths are ambiguous`
|
|
9391
|
+
},
|
|
9392
|
+
budget: HEAVY_BUDGET,
|
|
9393
|
+
capability: {
|
|
9394
|
+
phase: "verify",
|
|
9395
|
+
summary: "Bug hunter: scans source code for bugs, anti-patterns, and code smells, producing a file:line-ranked hit list with fixes.",
|
|
9396
|
+
keywords: [
|
|
9397
|
+
"bug",
|
|
9398
|
+
"hunt",
|
|
9399
|
+
"scan",
|
|
9400
|
+
"code smell",
|
|
9401
|
+
"anti-pattern",
|
|
9402
|
+
"race condition",
|
|
9403
|
+
"memory leak",
|
|
9404
|
+
"null deref",
|
|
9405
|
+
"type safety",
|
|
9406
|
+
"unhandled error",
|
|
9407
|
+
"find bugs",
|
|
9408
|
+
"audit code",
|
|
9409
|
+
"code quality"
|
|
9410
|
+
]
|
|
9411
|
+
}
|
|
9412
|
+
},
|
|
9413
|
+
{
|
|
9414
|
+
config: {
|
|
9415
|
+
id: "audit-log",
|
|
9416
|
+
name: "Audit Log",
|
|
9417
|
+
role: "audit-log",
|
|
9418
|
+
tools: [...TOOLS.inspect],
|
|
9419
|
+
prompt: `You are the Audit Log agent. Your job is to analyze structured JSONL
|
|
9420
|
+
session logs and produce actionable markdown reports.
|
|
9421
|
+
|
|
9422
|
+
Scope:
|
|
9423
|
+
- Parse session logs (iteration counts, tool calls, errors, usage)
|
|
9424
|
+
- Detect repeated failure patterns across multiple runs
|
|
9425
|
+
- Identify tool usage anomalies (over-use, failures, unexpected chains)
|
|
9426
|
+
- Track token consumption trends
|
|
9427
|
+
- Generate structured audit reports with severity ratings
|
|
9428
|
+
|
|
9429
|
+
Input format you accept:
|
|
9430
|
+
{ "task": "analyze | report | trends", "sessionPath": "<path>", "focus": "errors | tools | usage | all" }
|
|
9431
|
+
|
|
9432
|
+
Output: Markdown audit report with Summary, Top Errors, Tool Usage table,
|
|
9433
|
+
Anomalies, and Cost Trend sections.
|
|
9434
|
+
|
|
9435
|
+
Working rules:
|
|
9436
|
+
- Never fabricate numbers \u2014 read the actual logs first
|
|
9437
|
+
- Always include file:line references for errors
|
|
9438
|
+
- If sessionPath is missing, ask the director to provide it
|
|
9439
|
+
- Report confidence level: high (>90% accuracy), medium, low`
|
|
9440
|
+
},
|
|
9441
|
+
budget: MEDIUM_BUDGET,
|
|
9442
|
+
capability: {
|
|
9443
|
+
phase: "verify",
|
|
9444
|
+
summary: "Audit log analyzer: parses session JSONL, detects failure patterns, tool anomalies, and cost trends with structured reports.",
|
|
9445
|
+
keywords: [
|
|
9446
|
+
"audit",
|
|
9447
|
+
"log",
|
|
9448
|
+
"logs",
|
|
9449
|
+
"session",
|
|
9450
|
+
"trace",
|
|
9451
|
+
"analyze logs",
|
|
9452
|
+
"error patterns",
|
|
9453
|
+
"cost analysis",
|
|
9454
|
+
"tool usage",
|
|
9455
|
+
"token usage",
|
|
9456
|
+
"post-mortem",
|
|
9457
|
+
"trend",
|
|
9458
|
+
"anomaly"
|
|
9459
|
+
]
|
|
9460
|
+
}
|
|
9297
9461
|
}
|
|
9298
9462
|
];
|
|
9299
9463
|
|
|
@@ -12678,7 +12842,7 @@ async function expandGlob(pattern) {
|
|
|
12678
12842
|
const abs = isAbsolute(pattern);
|
|
12679
12843
|
const base = abs ? baseDir(pattern) : baseDir(pattern);
|
|
12680
12844
|
const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
|
|
12681
|
-
async function
|
|
12845
|
+
async function walk3(dir, pat) {
|
|
12682
12846
|
let entries;
|
|
12683
12847
|
try {
|
|
12684
12848
|
entries = await fsp.readdir(dir);
|
|
@@ -12699,12 +12863,12 @@ async function expandGlob(pattern) {
|
|
|
12699
12863
|
const before = pat.slice(0, firstGlob);
|
|
12700
12864
|
const rest = pat.slice(firstGlob);
|
|
12701
12865
|
if (before.endsWith("**")) {
|
|
12702
|
-
await
|
|
12866
|
+
await walk3(dir, rest);
|
|
12703
12867
|
for (const e of entries) {
|
|
12704
12868
|
const full = `${dir}${SEP}${e}`;
|
|
12705
12869
|
try {
|
|
12706
12870
|
const stat6 = await fsp.stat(full);
|
|
12707
|
-
if (stat6.isDirectory()) await
|
|
12871
|
+
if (stat6.isDirectory()) await walk3(full, rest);
|
|
12708
12872
|
} catch {
|
|
12709
12873
|
}
|
|
12710
12874
|
}
|
|
@@ -12722,13 +12886,13 @@ async function expandGlob(pattern) {
|
|
|
12722
12886
|
const full = `${dir}${SEP}${seg}`;
|
|
12723
12887
|
try {
|
|
12724
12888
|
const stat6 = await fsp.stat(full);
|
|
12725
|
-
if (stat6.isDirectory()) await
|
|
12889
|
+
if (stat6.isDirectory()) await walk3(full, rest);
|
|
12726
12890
|
} catch {
|
|
12727
12891
|
}
|
|
12728
12892
|
}
|
|
12729
12893
|
}
|
|
12730
12894
|
}
|
|
12731
|
-
await
|
|
12895
|
+
await walk3(base === "." ? "." : base, relPat);
|
|
12732
12896
|
return [...results];
|
|
12733
12897
|
}
|
|
12734
12898
|
|
|
@@ -14324,7 +14488,10 @@ var Director = class _Director {
|
|
|
14324
14488
|
workCompleteFlag = false;
|
|
14325
14489
|
/** Pending /btw notes stashed by the leader agent (see setLeaderBtwNote). */
|
|
14326
14490
|
_leaderBtwNotes = [];
|
|
14327
|
-
/** Active collab sessions tracked by sessionId (see spawnCollab).
|
|
14491
|
+
/** Active collab sessions tracked by sessionId (see spawnCollab).
|
|
14492
|
+
* The tuple holds the session and its Director-registered listener unsubs.
|
|
14493
|
+
* Calling the unsubs on cancel/premature-cleanup prevents listener accumulation
|
|
14494
|
+
* on CollabSession (EventEmitter) across many spawnCollab() calls. */
|
|
14328
14495
|
_activeCollabSessions = /* @__PURE__ */ new Map();
|
|
14329
14496
|
/** Prevents large `ask_subagent` answers from bloating the leader's context window. */
|
|
14330
14497
|
largeAnswerStore;
|
|
@@ -14654,11 +14821,13 @@ var Director = class _Director {
|
|
|
14654
14821
|
* The CollabDebugReport will reflect 'cancelled' disposition when awaited.
|
|
14655
14822
|
*/
|
|
14656
14823
|
cancelCollabSession(sessionId, reason = "Director cancelled") {
|
|
14657
|
-
const
|
|
14658
|
-
if (!
|
|
14659
|
-
|
|
14660
|
-
|
|
14661
|
-
|
|
14824
|
+
const entry = this._activeCollabSessions.get(sessionId);
|
|
14825
|
+
if (!entry || entry.session.isCancelled()) return;
|
|
14826
|
+
for (const unsub of entry.unsubs) unsub();
|
|
14827
|
+
entry.session.cancel(reason);
|
|
14828
|
+
for (const [_role, subagentId] of entry.session.getSubagentIds()) {
|
|
14829
|
+
this.coordinator.stop(subagentId).catch((err) => {
|
|
14830
|
+
console.debug(`[director] stop subagent ${subagentId} failed (may have already completed): ${err}`);
|
|
14662
14831
|
});
|
|
14663
14832
|
}
|
|
14664
14833
|
}
|
|
@@ -15306,9 +15475,15 @@ var Director = class _Director {
|
|
|
15306
15475
|
return options.onBudgetWarning?.(alert) ?? "ignore";
|
|
15307
15476
|
}
|
|
15308
15477
|
});
|
|
15309
|
-
this._activeCollabSessions.
|
|
15310
|
-
|
|
15311
|
-
session.on("session.
|
|
15478
|
+
const doneHandler = () => this._activeCollabSessions.delete(session.sessionId);
|
|
15479
|
+
const errorHandler = () => this._activeCollabSessions.delete(session.sessionId);
|
|
15480
|
+
session.on("session.done", doneHandler);
|
|
15481
|
+
session.on("session.error", errorHandler);
|
|
15482
|
+
const unsubs = [
|
|
15483
|
+
() => session.off("session.done", doneHandler),
|
|
15484
|
+
() => session.off("session.error", errorHandler)
|
|
15485
|
+
];
|
|
15486
|
+
this._activeCollabSessions.set(session.sessionId, { session, unsubs });
|
|
15312
15487
|
return session.start();
|
|
15313
15488
|
}
|
|
15314
15489
|
/**
|
|
@@ -18730,6 +18905,8 @@ var SpecVersioning = class {
|
|
|
18730
18905
|
switch (type) {
|
|
18731
18906
|
case "functional":
|
|
18732
18907
|
return "feature";
|
|
18908
|
+
case "non-functional":
|
|
18909
|
+
return "feature";
|
|
18733
18910
|
case "security":
|
|
18734
18911
|
return "feature";
|
|
18735
18912
|
case "performance":
|
|
@@ -18737,7 +18914,7 @@ var SpecVersioning = class {
|
|
|
18737
18914
|
case "ux":
|
|
18738
18915
|
return "feature";
|
|
18739
18916
|
default:
|
|
18740
|
-
return
|
|
18917
|
+
return assertNever(type);
|
|
18741
18918
|
}
|
|
18742
18919
|
}
|
|
18743
18920
|
};
|
|
@@ -19888,27 +20065,13 @@ function startOtlpTraceExporter(opts) {
|
|
|
19888
20065
|
// src/infrastructure/context-manager.ts
|
|
19889
20066
|
var CONTEXT_MANAGER_TOOL_NAME = "context_manager";
|
|
19890
20067
|
function roughEstimate(messages) {
|
|
19891
|
-
|
|
19892
|
-
for (const m of messages) {
|
|
19893
|
-
if (typeof m.content === "string") {
|
|
19894
|
-
total += Math.ceil(m.content.length / 4);
|
|
19895
|
-
} else if (Array.isArray(m.content)) {
|
|
19896
|
-
for (const b of m.content) {
|
|
19897
|
-
if (b.type === "text") total += Math.ceil(b.text.length / 4);
|
|
19898
|
-
else if (b.type === "tool_use" || b.type === "tool_result") {
|
|
19899
|
-
total += Math.ceil(JSON.stringify(b).length / 4);
|
|
19900
|
-
}
|
|
19901
|
-
}
|
|
19902
|
-
}
|
|
19903
|
-
}
|
|
19904
|
-
return total;
|
|
20068
|
+
return estimateMessageTokens(messages);
|
|
19905
20069
|
}
|
|
19906
20070
|
function createContextManagerTool(opts = {}) {
|
|
19907
20071
|
const minCompactThreshold = opts.minCompactThreshold ?? 0;
|
|
19908
20072
|
const noopRetryDeltaTokens = opts.noopRetryDeltaTokens ?? 2e3;
|
|
19909
|
-
const
|
|
20073
|
+
const configuredMaxContext = opts.maxContext;
|
|
19910
20074
|
const compactThresholdFraction = opts.compactThresholdFraction ?? 0.5;
|
|
19911
|
-
const effectiveThreshold = minCompactThreshold > 0 ? minCompactThreshold : Math.floor(maxContext * compactThresholdFraction);
|
|
19912
20075
|
let lastNoopTokens = 0;
|
|
19913
20076
|
return {
|
|
19914
20077
|
name: CONTEXT_MANAGER_TOOL_NAME,
|
|
@@ -20002,6 +20165,8 @@ function createContextManagerTool(opts = {}) {
|
|
|
20002
20165
|
}
|
|
20003
20166
|
const fullEstimate = input.systemPrompt != null && Array.isArray(input.tools) ? estimateRequestTokens(messages, input.systemPrompt, input.tools) : { total: beforeTokens};
|
|
20004
20167
|
const currentTokens = fullEstimate.total;
|
|
20168
|
+
const runtimeMaxContext = configuredMaxContext ?? ctx.provider?.capabilities?.maxContext ?? 128e3;
|
|
20169
|
+
const runtimeThreshold = minCompactThreshold > 0 ? minCompactThreshold : Math.floor(runtimeMaxContext * compactThresholdFraction);
|
|
20005
20170
|
if (lastNoopTokens > 0) {
|
|
20006
20171
|
const delta = currentTokens - lastNoopTokens;
|
|
20007
20172
|
if (delta < noopRetryDeltaTokens) {
|
|
@@ -20014,13 +20179,13 @@ function createContextManagerTool(opts = {}) {
|
|
|
20014
20179
|
};
|
|
20015
20180
|
}
|
|
20016
20181
|
}
|
|
20017
|
-
if (currentTokens <
|
|
20182
|
+
if (runtimeThreshold > 0 && currentTokens < runtimeThreshold) {
|
|
20018
20183
|
return {
|
|
20019
20184
|
action: "compact",
|
|
20020
20185
|
beforeTokens,
|
|
20021
20186
|
afterTokens: beforeTokens,
|
|
20022
20187
|
messageCount: messages.length,
|
|
20023
|
-
notes: `Context tokens (${currentTokens}) below compact threshold (${
|
|
20188
|
+
notes: `Context tokens (${currentTokens}) below compact threshold (${runtimeThreshold}, based on provider maxContext ${runtimeMaxContext}). Skipping.`
|
|
20024
20189
|
};
|
|
20025
20190
|
}
|
|
20026
20191
|
const report = await opts.compactor.compact(ctx);
|
|
@@ -20273,6 +20438,6 @@ var allServers = () => ({
|
|
|
20273
20438
|
"minimax-vision": { ...miniMaxVisionServer(), enabled: false }
|
|
20274
20439
|
});
|
|
20275
20440
|
|
|
20276
|
-
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,
|
|
20441
|
+
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 };
|
|
20277
20442
|
//# sourceMappingURL=index.js.map
|
|
20278
20443
|
//# sourceMappingURL=index.js.map
|