@wrongstack/core 0.119.1 → 0.148.0

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