@wrongstack/core 0.3.1 → 0.3.2

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 (40) hide show
  1. package/dist/{compactor-BUU6Zm_3.d.ts → compactor-DpJBI1YH.d.ts} +7 -1
  2. package/dist/{config-CKLYPkCi.d.ts → config-D2qvAxVd.d.ts} +38 -1
  3. package/dist/coordination/index.d.ts +3 -3
  4. package/dist/coordination/index.js +283 -244
  5. package/dist/coordination/index.js.map +1 -1
  6. package/dist/defaults/index.d.ts +7 -7
  7. package/dist/defaults/index.js +803 -524
  8. package/dist/defaults/index.js.map +1 -1
  9. package/dist/{events-CNB9PALO.d.ts → events-BHIQs4o1.d.ts} +7 -0
  10. package/dist/execution/index.d.ts +10 -7
  11. package/dist/execution/index.js +166 -18
  12. package/dist/execution/index.js.map +1 -1
  13. package/dist/extension/index.d.ts +3 -3
  14. package/dist/extension/index.js +14 -7
  15. package/dist/extension/index.js.map +1 -1
  16. package/dist/{index-BDb0cAMP.d.ts → index-hWNybrNZ.d.ts} +3 -5
  17. package/dist/index.d.ts +12 -12
  18. package/dist/index.js +629 -299
  19. package/dist/index.js.map +1 -1
  20. package/dist/infrastructure/index.d.ts +5 -5
  21. package/dist/infrastructure/index.js +191 -20
  22. package/dist/infrastructure/index.js.map +1 -1
  23. package/dist/kernel/index.d.ts +4 -4
  24. package/dist/kernel/index.js.map +1 -1
  25. package/dist/{mcp-servers-DR35ojJZ.d.ts → mcp-servers-C2OopXOn.d.ts} +20 -4
  26. package/dist/observability/index.d.ts +1 -1
  27. package/dist/{path-resolver-Cl_q0u-R.d.ts → path-resolver--59rCou3.d.ts} +1 -1
  28. package/dist/{provider-runner-BXuADQqQ.d.ts → provider-runner-B39miKRw.d.ts} +1 -1
  29. package/dist/sdd/index.d.ts +1 -1
  30. package/dist/storage/index.d.ts +4 -3
  31. package/dist/storage/index.js +180 -13
  32. package/dist/storage/index.js.map +1 -1
  33. package/dist/{tool-executor-DKu4A6nB.d.ts → tool-executor-HsBLGRaA.d.ts} +2 -2
  34. package/dist/types/index.d.ts +7 -7
  35. package/dist/types/index.js +206 -9
  36. package/dist/types/index.js.map +1 -1
  37. package/dist/utils/index.d.ts +23 -2
  38. package/dist/utils/index.js +93 -1
  39. package/dist/utils/index.js.map +1 -1
  40. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import * as fs4 from 'fs';
1
+ import * as fs5 from 'fs';
2
2
  import * as path3 from 'path';
3
3
  import * as crypto2 from 'crypto';
4
4
  import { randomBytes, randomUUID, createCipheriv, createDecipheriv } from 'crypto';
@@ -62,7 +62,7 @@ var DefaultLogger = class _DefaultLogger {
62
62
  this.pretty = opts.pretty ?? true;
63
63
  if (this.file) {
64
64
  try {
65
- fs4.mkdirSync(path3.dirname(this.file), { recursive: true });
65
+ fs5.mkdirSync(path3.dirname(this.file), { recursive: true });
66
66
  } catch {
67
67
  }
68
68
  }
@@ -101,7 +101,7 @@ var DefaultLogger = class _DefaultLogger {
101
101
  }
102
102
  if (this.file) {
103
103
  try {
104
- fs4.appendFileSync(this.file, `${JSON.stringify(entry)}
104
+ fs5.appendFileSync(this.file, `${JSON.stringify(entry)}
105
105
  `);
106
106
  } catch {
107
107
  }
@@ -192,6 +192,98 @@ async function renameWithRetry(from, to) {
192
192
  throw lastErr;
193
193
  }
194
194
 
195
+ // src/utils/message-invariants.ts
196
+ function repairToolUseAdjacency(messages) {
197
+ const removedToolUses = [];
198
+ const removedToolResults = [];
199
+ let removedMessages = 0;
200
+ let changed = false;
201
+ const out = [];
202
+ for (let i = 0; i < messages.length; i++) {
203
+ const original = messages[i];
204
+ let msg = original;
205
+ if (hasToolUse(msg)) {
206
+ const nextIds = toolResultIds(messages[i + 1]);
207
+ const filtered = mapContent(msg, (blocks) => {
208
+ const next = [];
209
+ for (const block of blocks) {
210
+ if (block.type === "tool_use" && !nextIds.has(block.id)) {
211
+ removedToolUses.push(block.id);
212
+ changed = true;
213
+ continue;
214
+ }
215
+ next.push(block);
216
+ }
217
+ return next;
218
+ });
219
+ msg = filtered ?? msg;
220
+ }
221
+ if (hasToolResult(msg)) {
222
+ const allowed = toolUseIds(out[out.length - 1]);
223
+ const filtered = mapContent(msg, (blocks) => {
224
+ const next = [];
225
+ for (const block of blocks) {
226
+ if (block.type === "tool_result" && !allowed.has(block.tool_use_id)) {
227
+ removedToolResults.push(block.tool_use_id);
228
+ changed = true;
229
+ continue;
230
+ }
231
+ next.push(block);
232
+ }
233
+ return next;
234
+ });
235
+ msg = filtered ?? msg;
236
+ }
237
+ if (isEmptyMessage(msg)) {
238
+ removedMessages++;
239
+ changed = true;
240
+ continue;
241
+ }
242
+ out.push(msg);
243
+ }
244
+ return {
245
+ messages: changed ? out : messages,
246
+ report: { changed, removedToolUses, removedToolResults, removedMessages }
247
+ };
248
+ }
249
+ function hasToolUse(msg) {
250
+ return contentBlocks(msg).some((b) => b.type === "tool_use");
251
+ }
252
+ function hasToolResult(msg) {
253
+ return contentBlocks(msg).some((b) => b.type === "tool_result");
254
+ }
255
+ function toolUseIds(msg) {
256
+ const ids = /* @__PURE__ */ new Set();
257
+ if (!msg || msg.role !== "assistant") return ids;
258
+ for (const block of contentBlocks(msg)) {
259
+ if (block.type === "tool_use") ids.add(block.id);
260
+ }
261
+ return ids;
262
+ }
263
+ function toolResultIds(msg) {
264
+ const ids = /* @__PURE__ */ new Set();
265
+ if (!msg || msg.role !== "user") return ids;
266
+ for (const block of contentBlocks(msg)) {
267
+ if (block.type === "tool_result") ids.add(block.tool_use_id);
268
+ }
269
+ return ids;
270
+ }
271
+ function contentBlocks(msg) {
272
+ return msg && Array.isArray(msg.content) ? msg.content : [];
273
+ }
274
+ function mapContent(msg, fn) {
275
+ if (!Array.isArray(msg.content)) return msg;
276
+ const next = fn(msg.content);
277
+ if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {
278
+ return msg;
279
+ }
280
+ return { ...msg, content: next };
281
+ }
282
+ function isEmptyMessage(msg) {
283
+ if (typeof msg.content === "string") return msg.content.trim().length === 0;
284
+ return msg.content.length === 0;
285
+ }
286
+
195
287
  // src/storage/session-store.ts
196
288
  var DefaultSessionStore = class {
197
289
  dir;
@@ -394,11 +486,17 @@ var DefaultSessionStore = class {
394
486
  if (openToolUses.size > 0) {
395
487
  this.events?.emit("session.damaged", {
396
488
  sessionId,
397
- detail: `${openToolUses.size} tool_use blocks without matching results \u2014 replay truncated`
489
+ detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
490
+ });
491
+ }
492
+ const repaired = repairToolUseAdjacency(messages);
493
+ if (repaired.report.changed) {
494
+ this.events?.emit("session.damaged", {
495
+ sessionId,
496
+ detail: `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ${repaired.report.removedToolResults.length} tool_result, ${repaired.report.removedMessages} empty messages`
398
497
  });
399
- return { messages, usage };
400
498
  }
401
- return { messages, usage };
499
+ return { messages: repaired.messages, usage };
402
500
  }
403
501
  };
404
502
  var FileSessionWriter = class {
@@ -424,6 +522,7 @@ var FileSessionWriter = class {
424
522
  startedAt;
425
523
  meta;
426
524
  closed = false;
525
+ closing = false;
427
526
  manifestFile;
428
527
  summary;
429
528
  tokenIn = 0;
@@ -439,9 +538,7 @@ var FileSessionWriter = class {
439
538
  resumed;
440
539
  appendFailCount = 0;
441
540
  lastAppendWarnAt = 0;
442
- async writeSessionStart() {
443
- if (this.initDone || this.closed) return;
444
- this.initDone = true;
541
+ async writeSessionStartLazy() {
445
542
  const record = `${JSON.stringify({
446
543
  type: this.resumed ? "session_resumed" : "session_start",
447
544
  ts: this.startedAt,
@@ -460,7 +557,8 @@ var FileSessionWriter = class {
460
557
  async append(event) {
461
558
  if (this.closed) return;
462
559
  if (!this.initDone) {
463
- await this.writeSessionStart();
560
+ this.initDone = true;
561
+ await this.writeSessionStartLazy();
464
562
  }
465
563
  this.observeForSummary(event);
466
564
  try {
@@ -501,7 +599,8 @@ var FileSessionWriter = class {
501
599
  }
502
600
  }
503
601
  async close() {
504
- if (this.closed) return;
602
+ if (this.closing) return;
603
+ this.closing = true;
505
604
  this.closed = true;
506
605
  if (this.manifestFile) {
507
606
  try {
@@ -904,80 +1003,7 @@ function deepFreeze(obj) {
904
1003
  return Object.freeze(obj);
905
1004
  }
906
1005
 
907
- // src/types/secret-vault.ts
908
- var ENCRYPTED_PREFIX = "enc:v1:";
909
-
910
- // src/security/secret-vault.ts
911
- var KEY_BYTES = 32;
912
- var IV_BYTES = 12;
913
- var TAG_BYTES = 16;
914
- var ALGO = "aes-256-gcm";
915
- var DefaultSecretVault = class {
916
- keyFile;
917
- key;
918
- constructor(opts) {
919
- this.keyFile = opts.keyFile;
920
- }
921
- isEncrypted(value) {
922
- return typeof value === "string" && value.startsWith(ENCRYPTED_PREFIX);
923
- }
924
- encrypt(plaintext) {
925
- if (this.isEncrypted(plaintext)) return plaintext;
926
- const key = this.loadOrCreateKey();
927
- const iv = randomBytes(IV_BYTES);
928
- const cipher = createCipheriv(ALGO, key, iv);
929
- const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
930
- const tag = cipher.getAuthTag();
931
- return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
932
- }
933
- decrypt(value) {
934
- if (!this.isEncrypted(value)) return value;
935
- const rest = value.slice(ENCRYPTED_PREFIX.length);
936
- const parts = rest.split(":");
937
- if (parts.length !== 3) {
938
- throw new Error("SecretVault: malformed encrypted value");
939
- }
940
- const [ivB64, tagB64, ctB64] = parts;
941
- const iv = Buffer.from(ivB64, "base64");
942
- const tag = Buffer.from(tagB64, "base64");
943
- const ct = Buffer.from(ctB64, "base64");
944
- if (iv.length !== IV_BYTES) throw new Error("SecretVault: bad IV length");
945
- if (tag.length !== TAG_BYTES) throw new Error("SecretVault: bad tag length");
946
- const key = this.loadOrCreateKey();
947
- const decipher = createDecipheriv(ALGO, key, iv);
948
- decipher.setAuthTag(tag);
949
- const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
950
- return pt.toString("utf8");
951
- }
952
- loadOrCreateKey() {
953
- if (this.key) return this.key;
954
- try {
955
- const buf = fs4.readFileSync(this.keyFile);
956
- if (buf.length !== KEY_BYTES) {
957
- throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
958
- }
959
- this.key = buf;
960
- return this.key;
961
- } catch (err) {
962
- if (err.code !== "ENOENT") throw err;
963
- }
964
- fs4.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
965
- const key = randomBytes(KEY_BYTES);
966
- try {
967
- fs4.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
968
- } catch (err) {
969
- if (err.code !== "EEXIST") throw err;
970
- const buf = fs4.readFileSync(this.keyFile);
971
- if (buf.length !== KEY_BYTES) {
972
- throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
973
- }
974
- this.key = buf;
975
- return this.key;
976
- }
977
- this.key = key;
978
- return key;
979
- }
980
- };
1006
+ // src/security/config-secrets.ts
981
1007
  function decryptConfigSecrets(cfg, vault) {
982
1008
  return walk(cfg, vault, (v, key) => {
983
1009
  try {
@@ -991,9 +1017,6 @@ function decryptConfigSecrets(cfg, vault) {
991
1017
  }
992
1018
  });
993
1019
  }
994
- function encryptConfigSecrets(cfg, vault) {
995
- return walk(cfg, vault, (v) => vault.encrypt(v));
996
- }
997
1020
  function walk(node, vault, transform) {
998
1021
  if (node === null || node === void 0) return node;
999
1022
  if (typeof node !== "object") return node;
@@ -1019,77 +1042,56 @@ function isSecretField(name) {
1019
1042
  if (NON_SECRET_OVERRIDES.has(lc)) return false;
1020
1043
  return SECRET_KEY_PATTERN.test(lc);
1021
1044
  }
1022
- async function rewriteConfigEncrypted(configPath, vault, patch) {
1023
- let current = {};
1024
- try {
1025
- const raw = await fsp.readFile(configPath, "utf8");
1026
- current = JSON.parse(raw);
1027
- } catch {
1028
- }
1029
- const merged = deepMerge(current, patch ?? {});
1030
- const encrypted = encryptConfigSecrets(merged, vault);
1031
- await fsp.mkdir(path3.dirname(configPath), { recursive: true });
1032
- await fsp.writeFile(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
1033
- try {
1034
- await fsp.chmod(configPath, 384);
1035
- } catch {
1036
- }
1037
- }
1038
- async function migratePlaintextSecrets(configPath, vault) {
1039
- let raw;
1040
- try {
1041
- raw = await fsp.readFile(configPath, "utf8");
1042
- } catch {
1043
- return { migrated: 0, file: configPath };
1044
- }
1045
- let parsed;
1046
- try {
1047
- parsed = JSON.parse(raw);
1048
- } catch {
1049
- return { migrated: 0, file: configPath };
1050
- }
1051
- const counter = { n: 0 };
1052
- const migrated = walkCount(parsed, vault, counter);
1053
- if (counter.n === 0) return { migrated: 0, file: configPath };
1054
- await fsp.writeFile(configPath, JSON.stringify(migrated, null, 2), { mode: 384 });
1055
- try {
1056
- await fsp.chmod(configPath, 384);
1057
- } catch {
1058
- }
1059
- return { migrated: counter.n, file: configPath };
1060
- }
1061
- function walkCount(node, vault, counter) {
1062
- if (node === null || node === void 0) return node;
1063
- if (typeof node !== "object") return node;
1064
- if (Array.isArray(node)) {
1065
- return node.map((item) => walkCount(item, vault, counter));
1066
- }
1067
- const out = {};
1068
- for (const [k, v] of Object.entries(node)) {
1069
- if (typeof v === "string" && isSecretField(k) && !vault.isEncrypted(v) && v.length > 0) {
1070
- out[k] = vault.encrypt(v);
1071
- counter.n++;
1072
- } else if (typeof v === "object" && v !== null) {
1073
- out[k] = walkCount(v, vault, counter);
1074
- } else {
1075
- out[k] = v;
1076
- }
1077
- }
1078
- return out;
1079
- }
1080
- var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1081
- function deepMerge(a, b) {
1082
- const out = { ...a };
1083
- for (const [k, v] of Object.entries(b)) {
1084
- if (FORBIDDEN_PROTO_KEYS.has(k)) continue;
1085
- const existing = out[k];
1086
- if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
1087
- out[k] = deepMerge(existing, v);
1088
- } else {
1089
- out[k] = v;
1090
- }
1091
- }
1092
- return out;
1045
+
1046
+ // src/types/context-window.ts
1047
+ var DEFAULT_CONTEXT_WINDOW_MODE_ID = "balanced";
1048
+ var CONTEXT_WINDOW_MODES = Object.freeze([
1049
+ {
1050
+ id: "balanced",
1051
+ name: "Balanced",
1052
+ description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed.",
1053
+ thresholds: { warn: 0.6, soft: 0.75, hard: 0.9 },
1054
+ aggressiveOn: "soft",
1055
+ preserveK: 10,
1056
+ eliseThreshold: 2e3,
1057
+ targetLoad: 0.65
1058
+ },
1059
+ {
1060
+ id: "frugal",
1061
+ name: "Frugal",
1062
+ description: "Token-saver mode: compacts early and keeps a tighter verbatim tail.",
1063
+ thresholds: { warn: 0.45, soft: 0.6, hard: 0.75 },
1064
+ aggressiveOn: "warn",
1065
+ preserveK: 6,
1066
+ eliseThreshold: 700,
1067
+ targetLoad: 0.5
1068
+ },
1069
+ {
1070
+ id: "deep",
1071
+ name: "Deep",
1072
+ description: "Long-reasoning mode: delays compaction and keeps more recent turns intact.",
1073
+ thresholds: { warn: 0.72, soft: 0.86, hard: 0.96 },
1074
+ aggressiveOn: "hard",
1075
+ preserveK: 18,
1076
+ eliseThreshold: 5e3,
1077
+ targetLoad: 0.78
1078
+ },
1079
+ {
1080
+ id: "archival",
1081
+ name: "Archival",
1082
+ description: "Decision-preserving mode: compacts steadily while keeping summaries prominent.",
1083
+ thresholds: { warn: 0.55, soft: 0.7, hard: 0.84 },
1084
+ aggressiveOn: "soft",
1085
+ preserveK: 8,
1086
+ eliseThreshold: 1200,
1087
+ targetLoad: 0.58
1088
+ }
1089
+ ]);
1090
+ function listContextWindowModes() {
1091
+ return CONTEXT_WINDOW_MODES.map((m) => ({ ...m, thresholds: { ...m.thresholds } }));
1092
+ }
1093
+ function isContextWindowModeId(id) {
1094
+ return CONTEXT_WINDOW_MODES.some((m) => m.id === id);
1093
1095
  }
1094
1096
 
1095
1097
  // src/utils/safe-json.ts
@@ -1111,6 +1113,7 @@ function safeParse(input, maxBytes = 5e6) {
1111
1113
  var BEHAVIOR_DEFAULTS = {
1112
1114
  version: 1,
1113
1115
  context: {
1116
+ mode: DEFAULT_CONTEXT_WINDOW_MODE_ID,
1114
1117
  warnThreshold: 0.6,
1115
1118
  softThreshold: 0.75,
1116
1119
  hardThreshold: 0.9,
@@ -1156,13 +1159,13 @@ var ENV_MAP = {
1156
1159
  function isPrimitiveArray(a) {
1157
1160
  return a.every((v) => v === null || typeof v !== "object");
1158
1161
  }
1159
- var FORBIDDEN_PROTO_KEYS2 = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1160
- function deepMerge2(base, patch) {
1162
+ var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1163
+ function deepMerge(base, patch) {
1161
1164
  if (typeof base !== "object" || base === null) return patch ?? base;
1162
1165
  if (typeof patch !== "object" || patch === null) return base;
1163
1166
  const out = { ...base };
1164
1167
  for (const [k, v] of Object.entries(patch)) {
1165
- if (FORBIDDEN_PROTO_KEYS2.has(k)) continue;
1168
+ if (FORBIDDEN_PROTO_KEYS.has(k)) continue;
1166
1169
  const existing = out[k];
1167
1170
  if (Array.isArray(v)) {
1168
1171
  if (Array.isArray(existing) && isPrimitiveArray(v) && isPrimitiveArray(existing)) {
@@ -1176,7 +1179,7 @@ function deepMerge2(base, patch) {
1176
1179
  }
1177
1180
  }
1178
1181
  } else if (typeof v === "object" && v !== null && typeof existing === "object" && existing !== null) {
1179
- out[k] = deepMerge2(existing, v);
1182
+ out[k] = deepMerge(existing, v);
1180
1183
  } else if (v !== void 0) {
1181
1184
  out[k] = v;
1182
1185
  }
@@ -1200,8 +1203,8 @@ var DefaultConfigLoader = class {
1200
1203
  this.readJson(this.paths.globalConfig),
1201
1204
  this.readJson(this.paths.projectLocalConfig)
1202
1205
  ]);
1203
- cfg = deepMerge2(cfg, global);
1204
- cfg = deepMerge2(cfg, local);
1206
+ cfg = deepMerge(cfg, global);
1207
+ cfg = deepMerge(cfg, local);
1205
1208
  for (const [key, fn] of Object.entries(ENV_MAP)) {
1206
1209
  const v = process.env[key];
1207
1210
  if (v) fn(cfg, v);
@@ -1215,14 +1218,14 @@ var DefaultConfigLoader = class {
1215
1218
  try {
1216
1219
  const patch = await src.read();
1217
1220
  if (patch && Object.keys(patch).length > 0) {
1218
- cfg = deepMerge2(cfg, patch);
1221
+ cfg = deepMerge(cfg, patch);
1219
1222
  }
1220
1223
  } catch (err) {
1221
1224
  console.warn(`Config source "${src.name}" failed`, err);
1222
1225
  }
1223
1226
  }
1224
1227
  if (opts.cliFlags) {
1225
- cfg = deepMerge2(cfg, opts.cliFlags);
1228
+ cfg = deepMerge(cfg, opts.cliFlags);
1226
1229
  }
1227
1230
  if (this.vault) {
1228
1231
  cfg = decryptConfigSecrets(cfg, this.vault);
@@ -1285,6 +1288,10 @@ var DefaultConfigLoader = class {
1285
1288
  if (c.warnThreshold >= c.softThreshold || c.softThreshold >= c.hardThreshold) {
1286
1289
  throw new Error("Config: context thresholds must satisfy warn < soft < hard");
1287
1290
  }
1291
+ if (c.mode !== void 0 && !isContextWindowModeId(c.mode)) {
1292
+ const known = listContextWindowModes().map((m) => m.id).join(", ");
1293
+ throw new Error(`Config: context.mode must be one of: ${known}`);
1294
+ }
1288
1295
  }
1289
1296
  validateIdentity(cfg) {
1290
1297
  if (!cfg.provider) {
@@ -1861,24 +1868,35 @@ async function saveTodosCheckpoint(filePath, sessionId, todos) {
1861
1868
  function attachTodosCheckpoint(state, filePath, sessionId) {
1862
1869
  let timer = null;
1863
1870
  let pending = null;
1871
+ let writeChain = Promise.resolve();
1872
+ const enqueueWrite = (todos) => {
1873
+ writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos));
1874
+ return writeChain;
1875
+ };
1864
1876
  const flush = () => {
1865
1877
  timer = null;
1866
1878
  if (pending) {
1867
- void saveTodosCheckpoint(filePath, sessionId, pending);
1879
+ const todos = pending;
1868
1880
  pending = null;
1881
+ return enqueueWrite(todos);
1869
1882
  }
1883
+ return writeChain;
1870
1884
  };
1871
1885
  const unsubscribe = state.onChange((change) => {
1872
1886
  if (change.kind !== "todos_replaced") return;
1873
1887
  pending = change.todos;
1874
1888
  if (timer) clearTimeout(timer);
1875
- timer = setTimeout(flush, 150);
1889
+ timer = setTimeout(() => {
1890
+ void flush();
1891
+ }, 150);
1876
1892
  });
1877
- return () => {
1893
+ return async () => {
1878
1894
  unsubscribe();
1879
1895
  if (timer) {
1880
1896
  clearTimeout(timer);
1881
- flush();
1897
+ await flush();
1898
+ } else {
1899
+ await writeChain;
1882
1900
  }
1883
1901
  };
1884
1902
  }
@@ -2134,56 +2152,244 @@ var PATTERNS = [
2134
2152
  // Value-side word boundary + length gate to avoid matching short random strings
2135
2153
  regex: /\b([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,})['"]?(?!\s*[A-Za-z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))/g
2136
2154
  }
2137
- ];
2138
- var SCRUB_CHUNK_BYTES = 64 * 1024;
2139
- var DefaultSecretScrubber = class {
2140
- scrub(text) {
2141
- if (!text) return text;
2142
- if (text.length <= SCRUB_CHUNK_BYTES) {
2143
- return this.scrubOne(text);
2144
- }
2145
- const out = [];
2146
- let i = 0;
2147
- while (i < text.length) {
2148
- let end = Math.min(i + SCRUB_CHUNK_BYTES, text.length);
2149
- if (end < text.length) {
2150
- const nl = text.lastIndexOf("\n", end);
2151
- if (nl > i + SCRUB_CHUNK_BYTES / 2) end = nl + 1;
2152
- }
2153
- out.push(this.scrubOne(text.slice(i, end)));
2154
- i = end;
2155
- }
2156
- return out.join("");
2155
+ ];
2156
+ var SCRUB_CHUNK_BYTES = 64 * 1024;
2157
+ var DefaultSecretScrubber = class {
2158
+ scrub(text) {
2159
+ if (!text) return text;
2160
+ if (text.length <= SCRUB_CHUNK_BYTES) {
2161
+ return this.scrubOne(text);
2162
+ }
2163
+ const out = [];
2164
+ let i = 0;
2165
+ while (i < text.length) {
2166
+ let end = Math.min(i + SCRUB_CHUNK_BYTES, text.length);
2167
+ if (end < text.length) {
2168
+ const nl = text.lastIndexOf("\n", end);
2169
+ if (nl > i + SCRUB_CHUNK_BYTES / 2) end = nl + 1;
2170
+ }
2171
+ out.push(this.scrubOne(text.slice(i, end)));
2172
+ i = end;
2173
+ }
2174
+ return out.join("");
2175
+ }
2176
+ scrubOne(text) {
2177
+ let out = text;
2178
+ for (const p of PATTERNS) {
2179
+ out = out.replace(p.regex, (_match, group1, group2) => {
2180
+ if (p.type === "high_entropy_env" && group1 && group2) {
2181
+ return `${group1}=[REDACTED:${p.type}]`;
2182
+ }
2183
+ return `[REDACTED:${p.type}]`;
2184
+ });
2185
+ }
2186
+ return out;
2187
+ }
2188
+ scrubObject(obj) {
2189
+ const seen = /* @__PURE__ */ new WeakSet();
2190
+ const visit = (v) => {
2191
+ if (typeof v === "string") return this.scrub(v);
2192
+ if (v === null || typeof v !== "object") return v;
2193
+ if (seen.has(v)) return v;
2194
+ seen.add(v);
2195
+ if (Array.isArray(v)) return v.map(visit);
2196
+ const out = {};
2197
+ for (const [k, val] of Object.entries(v)) {
2198
+ out[k] = visit(val);
2199
+ }
2200
+ return out;
2201
+ };
2202
+ return visit(obj);
2203
+ }
2204
+ };
2205
+
2206
+ // src/types/secret-vault.ts
2207
+ var ENCRYPTED_PREFIX = "enc:v1:";
2208
+
2209
+ // src/security/secret-vault.ts
2210
+ var KEY_BYTES = 32;
2211
+ var IV_BYTES = 12;
2212
+ var TAG_BYTES = 16;
2213
+ var ALGO = "aes-256-gcm";
2214
+ var DefaultSecretVault = class {
2215
+ keyFile;
2216
+ key;
2217
+ constructor(opts) {
2218
+ this.keyFile = opts.keyFile;
2219
+ }
2220
+ isEncrypted(value) {
2221
+ return typeof value === "string" && value.startsWith(ENCRYPTED_PREFIX);
2222
+ }
2223
+ encrypt(plaintext) {
2224
+ if (this.isEncrypted(plaintext)) return plaintext;
2225
+ const key = this.loadOrCreateKey();
2226
+ const iv = randomBytes(IV_BYTES);
2227
+ const cipher = createCipheriv(ALGO, key, iv);
2228
+ const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
2229
+ const tag = cipher.getAuthTag();
2230
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
2231
+ }
2232
+ decrypt(value) {
2233
+ if (!this.isEncrypted(value)) return value;
2234
+ const rest = value.slice(ENCRYPTED_PREFIX.length);
2235
+ const parts = rest.split(":");
2236
+ if (parts.length !== 3) {
2237
+ throw new Error("SecretVault: malformed encrypted value");
2238
+ }
2239
+ const [ivB64, tagB64, ctB64] = parts;
2240
+ const iv = Buffer.from(ivB64, "base64");
2241
+ const tag = Buffer.from(tagB64, "base64");
2242
+ const ct = Buffer.from(ctB64, "base64");
2243
+ if (iv.length !== IV_BYTES) throw new Error("SecretVault: bad IV length");
2244
+ if (tag.length !== TAG_BYTES) throw new Error("SecretVault: bad tag length");
2245
+ const key = this.loadOrCreateKey();
2246
+ const decipher = createDecipheriv(ALGO, key, iv);
2247
+ decipher.setAuthTag(tag);
2248
+ const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
2249
+ return pt.toString("utf8");
2250
+ }
2251
+ loadOrCreateKey() {
2252
+ if (this.key) return this.key;
2253
+ try {
2254
+ const buf = fs5.readFileSync(this.keyFile);
2255
+ if (buf.length !== KEY_BYTES) {
2256
+ throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
2257
+ }
2258
+ this.key = buf;
2259
+ return this.key;
2260
+ } catch (err) {
2261
+ if (err.code !== "ENOENT") throw err;
2262
+ }
2263
+ fs5.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
2264
+ const key = randomBytes(KEY_BYTES);
2265
+ try {
2266
+ fs5.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
2267
+ } catch (err) {
2268
+ if (err.code !== "EEXIST") throw err;
2269
+ const buf = fs5.readFileSync(this.keyFile);
2270
+ if (buf.length !== KEY_BYTES) {
2271
+ throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
2272
+ }
2273
+ this.key = buf;
2274
+ return this.key;
2275
+ }
2276
+ this.key = key;
2277
+ return key;
2278
+ }
2279
+ };
2280
+ function decryptConfigSecrets2(cfg, vault) {
2281
+ return walk2(cfg, vault, (v, key) => {
2282
+ try {
2283
+ return vault.decrypt(v);
2284
+ } catch (err) {
2285
+ console.warn(
2286
+ `[secret-vault] Failed to decrypt "${key}":`,
2287
+ err instanceof Error ? err.message : err
2288
+ );
2289
+ return "";
2290
+ }
2291
+ });
2292
+ }
2293
+ function encryptConfigSecrets(cfg, vault) {
2294
+ return walk2(cfg, vault, (v) => vault.encrypt(v));
2295
+ }
2296
+ function walk2(node, vault, transform) {
2297
+ if (node === null || node === void 0) return node;
2298
+ if (typeof node !== "object") return node;
2299
+ if (Array.isArray(node)) {
2300
+ return node.map((item) => walk2(item, vault, transform));
2301
+ }
2302
+ const out = {};
2303
+ for (const [k, v] of Object.entries(node)) {
2304
+ if (typeof v === "string" && isSecretField2(k)) {
2305
+ out[k] = transform(v, k);
2306
+ } else if (typeof v === "object" && v !== null) {
2307
+ out[k] = walk2(v, vault, transform);
2308
+ } else {
2309
+ out[k] = v;
2310
+ }
2311
+ }
2312
+ return out;
2313
+ }
2314
+ 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;
2315
+ var NON_SECRET_OVERRIDES2 = /* @__PURE__ */ new Set(["publickey", "public_key"]);
2316
+ function isSecretField2(name) {
2317
+ const lc = name.toLowerCase();
2318
+ if (NON_SECRET_OVERRIDES2.has(lc)) return false;
2319
+ return SECRET_KEY_PATTERN2.test(lc);
2320
+ }
2321
+ async function rewriteConfigEncrypted(configPath, vault, patch) {
2322
+ let current = {};
2323
+ try {
2324
+ const raw = await fsp.readFile(configPath, "utf8");
2325
+ current = JSON.parse(raw);
2326
+ } catch {
2327
+ }
2328
+ const merged = deepMerge2(current, patch ?? {});
2329
+ const encrypted = encryptConfigSecrets(merged, vault);
2330
+ await fsp.mkdir(path3.dirname(configPath), { recursive: true });
2331
+ await fsp.writeFile(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
2332
+ try {
2333
+ await fsp.chmod(configPath, 384);
2334
+ } catch {
2335
+ }
2336
+ }
2337
+ async function migratePlaintextSecrets(configPath, vault) {
2338
+ let raw;
2339
+ try {
2340
+ raw = await fsp.readFile(configPath, "utf8");
2341
+ } catch {
2342
+ return { migrated: 0, file: configPath };
2343
+ }
2344
+ let parsed;
2345
+ try {
2346
+ parsed = JSON.parse(raw);
2347
+ } catch {
2348
+ return { migrated: 0, file: configPath };
2349
+ }
2350
+ const counter = { n: 0 };
2351
+ const migrated = walkCount(parsed, vault, counter);
2352
+ if (counter.n === 0) return { migrated: 0, file: configPath };
2353
+ await fsp.writeFile(configPath, JSON.stringify(migrated, null, 2), { mode: 384 });
2354
+ try {
2355
+ await fsp.chmod(configPath, 384);
2356
+ } catch {
2157
2357
  }
2158
- scrubOne(text) {
2159
- let out = text;
2160
- for (const p of PATTERNS) {
2161
- out = out.replace(p.regex, (_match, group1, group2) => {
2162
- if (p.type === "high_entropy_env" && group1 && group2) {
2163
- return `${group1}=[REDACTED:${p.type}]`;
2164
- }
2165
- return `[REDACTED:${p.type}]`;
2166
- });
2358
+ return { migrated: counter.n, file: configPath };
2359
+ }
2360
+ function walkCount(node, vault, counter) {
2361
+ if (node === null || node === void 0) return node;
2362
+ if (typeof node !== "object") return node;
2363
+ if (Array.isArray(node)) {
2364
+ return node.map((item) => walkCount(item, vault, counter));
2365
+ }
2366
+ const out = {};
2367
+ for (const [k, v] of Object.entries(node)) {
2368
+ if (typeof v === "string" && isSecretField2(k) && !vault.isEncrypted(v) && v.length > 0) {
2369
+ out[k] = vault.encrypt(v);
2370
+ counter.n++;
2371
+ } else if (typeof v === "object" && v !== null) {
2372
+ out[k] = walkCount(v, vault, counter);
2373
+ } else {
2374
+ out[k] = v;
2167
2375
  }
2168
- return out;
2169
2376
  }
2170
- scrubObject(obj) {
2171
- const seen = /* @__PURE__ */ new WeakSet();
2172
- const visit = (v) => {
2173
- if (typeof v === "string") return this.scrub(v);
2174
- if (v === null || typeof v !== "object") return v;
2175
- if (seen.has(v)) return v;
2176
- seen.add(v);
2177
- if (Array.isArray(v)) return v.map(visit);
2178
- const out = {};
2179
- for (const [k, val] of Object.entries(v)) {
2180
- out[k] = visit(val);
2181
- }
2182
- return out;
2183
- };
2184
- return visit(obj);
2377
+ return out;
2378
+ }
2379
+ var FORBIDDEN_PROTO_KEYS2 = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
2380
+ function deepMerge2(a, b) {
2381
+ const out = { ...a };
2382
+ for (const [k, v] of Object.entries(b)) {
2383
+ if (FORBIDDEN_PROTO_KEYS2.has(k)) continue;
2384
+ const existing = out[k];
2385
+ if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
2386
+ out[k] = deepMerge2(existing, v);
2387
+ } else {
2388
+ out[k] = v;
2389
+ }
2185
2390
  }
2186
- };
2391
+ return out;
2392
+ }
2187
2393
 
2188
2394
  // src/utils/glob-match.ts
2189
2395
  function escapeRegex(s) {
@@ -3084,20 +3290,36 @@ var HybridCompactor = class {
3084
3290
  async compact(ctx, opts = {}) {
3085
3291
  const beforeTokens = this.estimateMessages(ctx.messages);
3086
3292
  const reductions = [];
3087
- const phase1Saved = this.eliseOldToolResults(ctx);
3293
+ const policy = readContextWindowPolicy(ctx);
3294
+ const preserveK = policy?.preserveK ?? this.preserveK;
3295
+ const eliseThreshold = policy?.eliseThreshold ?? this.eliseThreshold;
3296
+ const phase1Saved = this.eliseOldToolResults(ctx, preserveK, eliseThreshold);
3088
3297
  if (phase1Saved > 0) reductions.push({ phase: "elision", saved: phase1Saved });
3089
3298
  if (opts.aggressive) {
3090
- const phase2Saved = this.collapseAncientTurns(ctx);
3299
+ const phase2Saved = this.collapseAncientTurns(ctx, preserveK);
3091
3300
  if (phase2Saved > 0) reductions.push({ phase: "summary", saved: phase2Saved });
3092
3301
  }
3302
+ const repaired = repairToolUseAdjacency(ctx.messages);
3303
+ if (repaired.report.changed) {
3304
+ ctx.state.replaceMessages(repaired.messages);
3305
+ }
3093
3306
  const afterTokens = this.estimateMessages(ctx.messages);
3094
- return { before: beforeTokens, after: afterTokens, reductions };
3307
+ return {
3308
+ before: beforeTokens,
3309
+ after: afterTokens,
3310
+ reductions,
3311
+ repaired: repaired.report.changed ? {
3312
+ removedToolUses: repaired.report.removedToolUses,
3313
+ removedToolResults: repaired.report.removedToolResults,
3314
+ removedMessages: repaired.report.removedMessages
3315
+ } : void 0
3316
+ };
3095
3317
  }
3096
- eliseOldToolResults(ctx) {
3318
+ eliseOldToolResults(ctx, preserveK = this.preserveK, eliseThreshold = this.eliseThreshold) {
3097
3319
  const messages = ctx.messages;
3098
3320
  let pairCount = 0;
3099
3321
  let preserveStart = messages.length;
3100
- for (let i = messages.length - 1; i >= 0 && pairCount < this.preserveK; i--) {
3322
+ for (let i = messages.length - 1; i >= 0 && pairCount < preserveK; i--) {
3101
3323
  const m = messages[i];
3102
3324
  if (!m) continue;
3103
3325
  if (m.role === "user" || m.role === "assistant") {
@@ -3121,7 +3343,7 @@ var HybridCompactor = class {
3121
3343
  const newContent = msg.content.map((b) => {
3122
3344
  if (b.type !== "tool_result") return b;
3123
3345
  const tokens = estimateToolResultTokens(b.content);
3124
- if (tokens < this.eliseThreshold) return b;
3346
+ if (tokens < eliseThreshold) return b;
3125
3347
  saved += tokens;
3126
3348
  const elided = {
3127
3349
  type: "tool_result",
@@ -3141,9 +3363,9 @@ var HybridCompactor = class {
3141
3363
  if (changed) ctx.state.replaceMessages(nextMessages);
3142
3364
  return saved;
3143
3365
  }
3144
- collapseAncientTurns(ctx) {
3366
+ collapseAncientTurns(ctx, preserveK = this.preserveK) {
3145
3367
  const messages = ctx.messages;
3146
- const cutTarget = Math.max(0, messages.length - this.preserveK * 2);
3368
+ const cutTarget = Math.max(0, messages.length - preserveK * 2);
3147
3369
  if (cutTarget <= 0) return 0;
3148
3370
  let boundary = -1;
3149
3371
  for (let i = cutTarget; i < messages.length; i++) {
@@ -3184,6 +3406,15 @@ var HybridCompactor = class {
3184
3406
  return total;
3185
3407
  }
3186
3408
  };
3409
+ function readContextWindowPolicy(ctx) {
3410
+ const policy = ctx.meta?.["contextWindowPolicy"];
3411
+ if (!policy || typeof policy !== "object") return null;
3412
+ const candidate = policy;
3413
+ if (typeof candidate.preserveK !== "number" || typeof candidate.eliseThreshold !== "number") {
3414
+ return null;
3415
+ }
3416
+ return candidate;
3417
+ }
3187
3418
  function hasTextContent(m) {
3188
3419
  if (typeof m.content === "string") return m.content.trim().length > 0;
3189
3420
  return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
@@ -3230,8 +3461,19 @@ var IntelligentCompactor = class {
3230
3461
  const saved2 = this.lightweightCompact(ctx);
3231
3462
  if (saved2 > 0) reductions.push({ phase: "elision", saved: saved2 });
3232
3463
  }
3464
+ const repaired = repairToolUseAdjacency(ctx.messages);
3465
+ if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
3233
3466
  const afterTokens = this.estimateTokens(ctx.messages);
3234
- return { before: beforeTokens, after: afterTokens, reductions };
3467
+ return {
3468
+ before: beforeTokens,
3469
+ after: afterTokens,
3470
+ reductions,
3471
+ repaired: repaired.report.changed ? {
3472
+ removedToolUses: repaired.report.removedToolUses,
3473
+ removedToolResults: repaired.report.removedToolResults,
3474
+ removedMessages: repaired.report.removedMessages
3475
+ } : void 0
3476
+ };
3235
3477
  }
3236
3478
  async summarizeAncientTurns(ctx) {
3237
3479
  const messages = ctx.messages;
@@ -3271,8 +3513,8 @@ var IntelligentCompactor = class {
3271
3513
  const m = messages[i];
3272
3514
  if (!m) continue;
3273
3515
  if (m.role === "assistant") {
3274
- const hasToolUse = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
3275
- if (!hasToolUse) {
3516
+ const hasToolUse2 = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
3517
+ if (!hasToolUse2) {
3276
3518
  return i + 1;
3277
3519
  }
3278
3520
  } else if (m.role !== "user") ; else {
@@ -3578,8 +3820,9 @@ var SelectiveCompactor = class {
3578
3820
  if (!shouldCompact) {
3579
3821
  const saved = this.eliseOldToolResults(ctx);
3580
3822
  if (saved > 0) reductions.push({ phase: "elision", saved });
3823
+ const repair2 = this.repairProtocolAdjacency(ctx);
3581
3824
  const afterTokens2 = this.estimateTokens(ctx.messages);
3582
- return { before: beforeTokens, after: afterTokens2, reductions };
3825
+ return { before: beforeTokens, after: afterTokens2, reductions, repaired: repair2 };
3583
3826
  }
3584
3827
  const savedElision = this.eliseOldToolResults(ctx);
3585
3828
  if (savedElision > 0) reductions.push({ phase: "elision", saved: savedElision });
@@ -3589,8 +3832,18 @@ var SelectiveCompactor = class {
3589
3832
  const savedSelective = await this.runSelector(ctx, targetBudget);
3590
3833
  if (savedSelective > 0) reductions.push({ phase: "selective", saved: savedSelective });
3591
3834
  }
3835
+ const repair = this.repairProtocolAdjacency(ctx);
3592
3836
  const afterTokens = this.estimateTokens(ctx.messages);
3593
- return { before: beforeTokens, after: afterTokens, reductions };
3837
+ return { before: beforeTokens, after: afterTokens, reductions, repaired: repair };
3838
+ }
3839
+ repairProtocolAdjacency(ctx) {
3840
+ const repaired = repairToolUseAdjacency(ctx.messages);
3841
+ if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
3842
+ return repaired.report.changed ? {
3843
+ removedToolUses: repaired.report.removedToolUses,
3844
+ removedToolResults: repaired.report.removedToolResults,
3845
+ removedMessages: repaired.report.removedMessages
3846
+ } : void 0;
3594
3847
  }
3595
3848
  /**
3596
3849
  * Run the LLM selector to decide what to keep vs collapse.
@@ -3778,6 +4031,7 @@ var AutoCompactionMiddleware = class {
3778
4031
  aggressiveOn;
3779
4032
  events;
3780
4033
  failureMode;
4034
+ policyProvider;
3781
4035
  /**
3782
4036
  * @param compactor Compactor to use for compaction.
3783
4037
  * @param maxContext Provider's max context window in tokens.
@@ -3800,17 +4054,25 @@ var AutoCompactionMiddleware = class {
3800
4054
  this.aggressiveOn = opts.aggressiveOn ?? "soft";
3801
4055
  this.events = opts.events;
3802
4056
  this.failureMode = opts.failureMode ?? "throw_on_hard";
4057
+ this.policyProvider = opts.policyProvider;
3803
4058
  }
3804
4059
  handler() {
3805
4060
  return async (ctx, next) => {
3806
4061
  const tokens = this.estimator(ctx);
3807
4062
  const load = tokens / this.maxContext;
3808
- if (load >= this.hardThreshold) {
4063
+ const policy = this.policyProvider?.(ctx);
4064
+ const thresholds = policy?.thresholds ?? {
4065
+ warn: this.warnThreshold,
4066
+ soft: this.softThreshold,
4067
+ hard: this.hardThreshold
4068
+ };
4069
+ const aggressiveOn = policy?.aggressiveOn ?? this.aggressiveOn;
4070
+ if (load >= thresholds.hard) {
3809
4071
  await this.compact(ctx, true, { level: "hard", tokens, load });
3810
- } else if (load >= this.softThreshold) {
3811
- await this.compact(ctx, this.aggressiveOn !== "hard", { level: "soft", tokens, load });
3812
- } else if (load >= this.warnThreshold) {
3813
- await this.compact(ctx, false, { level: "warn", tokens, load });
4072
+ } else if (load >= thresholds.soft) {
4073
+ await this.compact(ctx, aggressiveOn !== "hard", { level: "soft", tokens, load });
4074
+ } else if (load >= thresholds.warn) {
4075
+ await this.compact(ctx, aggressiveOn === "warn", { level: "warn", tokens, load });
3814
4076
  }
3815
4077
  return next(ctx);
3816
4078
  };
@@ -4413,6 +4675,7 @@ var InMemoryAgentBridge = class {
4413
4675
  this.stopped = true;
4414
4676
  for (const [, p] of this.pendingRequests) {
4415
4677
  clearTimeout(p.timer);
4678
+ p.reject(new Error("Bridge stopped"));
4416
4679
  }
4417
4680
  this.pendingRequests.clear();
4418
4681
  this.inflightGuards.clear();
@@ -5186,41 +5449,214 @@ function classifySubagentError(err, hints = {}) {
5186
5449
  return { kind: "context_overflow", message: baseMessage, retryable: false, cause };
5187
5450
  }
5188
5451
  return {
5189
- kind: "unknown",
5190
- message: baseMessage,
5191
- retryable: false,
5192
- cause
5452
+ kind: "unknown",
5453
+ message: baseMessage,
5454
+ retryable: false,
5455
+ cause
5456
+ };
5457
+ }
5458
+ function providerErrorToSubagentError(err, message, cause) {
5459
+ const status = err.status;
5460
+ if (status === 429 || err.body?.type === "rate_limit_error") {
5461
+ return {
5462
+ kind: "provider_rate_limit",
5463
+ message,
5464
+ retryable: true,
5465
+ // Conservative default: 5s. Provider-specific code can override
5466
+ // by emitting an error whose body carries an explicit hint.
5467
+ backoffMs: 5e3,
5468
+ cause
5469
+ };
5470
+ }
5471
+ if (status === 401 || status === 403 || err.body?.type === "authentication_error") {
5472
+ return { kind: "provider_auth", message, retryable: false, cause };
5473
+ }
5474
+ if (status === 408 || status === 0) {
5475
+ return { kind: "provider_timeout", message, retryable: true, cause };
5476
+ }
5477
+ if (status >= 500 && status < 600) {
5478
+ return {
5479
+ kind: "provider_5xx",
5480
+ message,
5481
+ retryable: true,
5482
+ backoffMs: 3e3,
5483
+ cause
5484
+ };
5485
+ }
5486
+ return { kind: "unknown", message, retryable: err.retryable, cause };
5487
+ }
5488
+ function makeSpawnTool(director, roster) {
5489
+ const inputSchema = {
5490
+ type: "object",
5491
+ properties: {
5492
+ role: { type: "string", description: "Roster role id (preferred). When set, the spawn uses the matching config from the roster and ignores other fields." },
5493
+ name: { type: "string", description: "Display name for the subagent. Required when not using roster." },
5494
+ provider: { type: "string", description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.' },
5495
+ model: { type: "string", description: "Model id within the provider. Defaults to the leader model when omitted." },
5496
+ systemPromptOverride: { type: "string", description: "Extra prompt text appended after the role-base prompt." },
5497
+ maxIterations: { type: "number" },
5498
+ maxToolCalls: { type: "number" },
5499
+ maxCostUsd: { type: "number" }
5500
+ },
5501
+ required: []
5502
+ };
5503
+ return {
5504
+ name: "spawn_subagent",
5505
+ description: "Create a new subagent under this director. Returns the subagent id.",
5506
+ usageHint: "Either pass `role` (matches the roster) OR pass `name` + optional `provider`/`model`. Returns `{ subagentId }`.",
5507
+ permission: "auto",
5508
+ mutating: false,
5509
+ inputSchema,
5510
+ async execute(input) {
5511
+ const i = input ?? {};
5512
+ const role = typeof i.role === "string" ? i.role : void 0;
5513
+ const base = role && roster ? roster[role] : void 0;
5514
+ if (role && !base) {
5515
+ return { error: `unknown role "${role}". roster has: ${roster ? Object.keys(roster).join(", ") : "(empty)"}` };
5516
+ }
5517
+ const cfg = { ...base ?? { name: i.name ?? "subagent" } };
5518
+ if (typeof i.name === "string") cfg.name = i.name;
5519
+ if (typeof i.provider === "string") cfg.provider = i.provider;
5520
+ if (typeof i.model === "string") cfg.model = i.model;
5521
+ if (typeof i.systemPromptOverride === "string") cfg.systemPromptOverride = i.systemPromptOverride;
5522
+ if (typeof i.maxIterations === "number") cfg.maxIterations = i.maxIterations;
5523
+ if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
5524
+ if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
5525
+ try {
5526
+ const subagentId = await director.spawn(cfg);
5527
+ return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name };
5528
+ } catch (err) {
5529
+ if (err instanceof DirectorBudgetError) {
5530
+ return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
5531
+ }
5532
+ return { error: err instanceof Error ? err.message : String(err) };
5533
+ }
5534
+ }
5535
+ };
5536
+ }
5537
+ function makeAssignTool(director) {
5538
+ const inputSchema = {
5539
+ type: "object",
5540
+ properties: {
5541
+ subagentId: { type: "string", description: "Target subagent id. Required." },
5542
+ description: { type: "string", description: "The task in natural language \u2014 what you want this subagent to do." },
5543
+ maxToolCalls: { type: "number", description: "Optional per-task tool-call budget override." },
5544
+ timeoutMs: { type: "number", description: "Optional per-task timeout in ms." }
5545
+ },
5546
+ required: ["subagentId", "description"]
5547
+ };
5548
+ return {
5549
+ name: "assign_task",
5550
+ description: "Hand a task to a previously spawned subagent. Returns the task id.",
5551
+ permission: "auto",
5552
+ mutating: false,
5553
+ inputSchema,
5554
+ async execute(input) {
5555
+ const i = input;
5556
+ const task = { id: randomUUID(), description: i.description, subagentId: i.subagentId, maxToolCalls: i.maxToolCalls, timeoutMs: i.timeoutMs };
5557
+ const taskId = await director.assign(task);
5558
+ return { taskId, subagentId: i.subagentId };
5559
+ }
5560
+ };
5561
+ }
5562
+ function makeAwaitTasksTool(director) {
5563
+ return {
5564
+ name: "await_tasks",
5565
+ description: "Block until every named task completes. Returns the array of TaskResult.",
5566
+ permission: "auto",
5567
+ mutating: false,
5568
+ inputSchema: { type: "object", properties: { taskIds: { type: "array", items: { type: "string" }, description: "One or more task ids returned by `assign_task`." } }, required: ["taskIds"] },
5569
+ async execute(input) {
5570
+ const i = input;
5571
+ const results = await director.awaitTasks(i.taskIds);
5572
+ return { results };
5573
+ }
5574
+ };
5575
+ }
5576
+ function makeAskTool(director) {
5577
+ return {
5578
+ name: "ask_subagent",
5579
+ description: "Synchronously ask a subagent a question. Blocks until the subagent replies via the bridge.",
5580
+ permission: "auto",
5581
+ mutating: false,
5582
+ inputSchema: {
5583
+ type: "object",
5584
+ properties: {
5585
+ subagentId: { type: "string", description: "Subagent to ask. Must be a previously spawned id." },
5586
+ question: { type: "string", description: "The question or instruction." },
5587
+ timeoutMs: { type: "number", description: "Optional timeout in ms (default 30s)." }
5588
+ },
5589
+ required: ["subagentId", "question"]
5590
+ },
5591
+ async execute(input) {
5592
+ const i = input;
5593
+ try {
5594
+ const answer = await director.ask(i.subagentId, { question: i.question }, i.timeoutMs);
5595
+ return { ok: true, answer };
5596
+ } catch (err) {
5597
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
5598
+ }
5599
+ }
5600
+ };
5601
+ }
5602
+ function makeRollUpTool(director) {
5603
+ return {
5604
+ name: "roll_up",
5605
+ description: "Aggregate completed task results into a single formatted summary.",
5606
+ permission: "auto",
5607
+ mutating: false,
5608
+ inputSchema: {
5609
+ type: "object",
5610
+ properties: {
5611
+ taskIds: { type: "array", items: { type: "string" }, description: "Completed task ids to aggregate." },
5612
+ style: { type: "string", enum: ["markdown", "json"], description: "Output flavor \u2014 markdown (default) or json." }
5613
+ },
5614
+ required: ["taskIds"]
5615
+ },
5616
+ async execute(input) {
5617
+ const i = input;
5618
+ const summary = director.rollUp(i.taskIds, i.style ?? "markdown");
5619
+ return { summary, count: i.taskIds.length };
5620
+ }
5621
+ };
5622
+ }
5623
+ function makeTerminateTool(director) {
5624
+ return {
5625
+ name: "terminate_subagent",
5626
+ description: "Forcibly abort a subagent.",
5627
+ permission: "auto",
5628
+ mutating: true,
5629
+ inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
5630
+ async execute(input) {
5631
+ const i = input;
5632
+ await director.terminate(i.subagentId);
5633
+ return { ok: true };
5634
+ }
5635
+ };
5636
+ }
5637
+ function makeFleetStatusTool(director) {
5638
+ return {
5639
+ name: "fleet_status",
5640
+ description: "Snapshot of the fleet \u2014 every subagent's current status, pending vs. completed task counts.",
5641
+ permission: "auto",
5642
+ mutating: false,
5643
+ inputSchema: { type: "object", properties: {}, required: [] },
5644
+ async execute() {
5645
+ return director.status();
5646
+ }
5193
5647
  };
5194
5648
  }
5195
- function providerErrorToSubagentError(err, message, cause) {
5196
- const status = err.status;
5197
- if (status === 429 || err.body?.type === "rate_limit_error") {
5198
- return {
5199
- kind: "provider_rate_limit",
5200
- message,
5201
- retryable: true,
5202
- // Conservative default: 5s. Provider-specific code can override
5203
- // by emitting an error whose body carries an explicit hint.
5204
- backoffMs: 5e3,
5205
- cause
5206
- };
5207
- }
5208
- if (status === 401 || status === 403 || err.body?.type === "authentication_error") {
5209
- return { kind: "provider_auth", message, retryable: false, cause };
5210
- }
5211
- if (status === 408 || status === 0) {
5212
- return { kind: "provider_timeout", message, retryable: true, cause };
5213
- }
5214
- if (status >= 500 && status < 600) {
5215
- return {
5216
- kind: "provider_5xx",
5217
- message,
5218
- retryable: true,
5219
- backoffMs: 3e3,
5220
- cause
5221
- };
5222
- }
5223
- return { kind: "unknown", message, retryable: err.retryable, cause };
5649
+ function makeFleetUsageTool(director) {
5650
+ return {
5651
+ name: "fleet_usage",
5652
+ description: "Token + cost breakdown across the fleet, per-subagent and totals.",
5653
+ permission: "auto",
5654
+ mutating: false,
5655
+ inputSchema: { type: "object", properties: {}, required: [] },
5656
+ async execute() {
5657
+ return director.snapshot();
5658
+ }
5659
+ };
5224
5660
  }
5225
5661
 
5226
5662
  // src/coordination/director.ts
@@ -5780,242 +6216,6 @@ var Director = class {
5780
6216
  return t;
5781
6217
  }
5782
6218
  };
5783
- function makeSpawnTool(director, roster) {
5784
- const inputSchema = {
5785
- type: "object",
5786
- properties: {
5787
- role: {
5788
- type: "string",
5789
- description: "Roster role id (preferred). When set, the spawn uses the matching config from the roster and ignores other fields."
5790
- },
5791
- name: {
5792
- type: "string",
5793
- description: "Display name for the subagent. Required when not using roster."
5794
- },
5795
- provider: {
5796
- type: "string",
5797
- description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.'
5798
- },
5799
- model: {
5800
- type: "string",
5801
- description: "Model id within the provider. Defaults to the leader model when omitted."
5802
- },
5803
- systemPromptOverride: {
5804
- type: "string",
5805
- description: "Extra prompt text appended after the role-base prompt."
5806
- },
5807
- maxIterations: { type: "number" },
5808
- maxToolCalls: { type: "number" },
5809
- maxCostUsd: { type: "number" }
5810
- },
5811
- required: []
5812
- };
5813
- return {
5814
- name: "spawn_subagent",
5815
- description: "Create a new subagent under this director. Returns the subagent id. Use this when you need a worker with a specific provider, model, or role to handle a piece of the plan.",
5816
- usageHint: "Either pass `role` (matches the roster) OR pass `name` + optional `provider`/`model`. Returns `{ subagentId }`.",
5817
- permission: "auto",
5818
- mutating: false,
5819
- inputSchema,
5820
- async execute(input) {
5821
- const i = input ?? {};
5822
- const role = typeof i.role === "string" ? i.role : void 0;
5823
- const base = role && roster ? roster[role] : void 0;
5824
- if (role && !base) {
5825
- return {
5826
- error: `unknown role "${role}". roster has: ${roster ? Object.keys(roster).join(", ") : "(empty)"}`
5827
- };
5828
- }
5829
- const cfg = {
5830
- ...base ?? { name: i.name ?? "subagent" }
5831
- };
5832
- if (typeof i.name === "string") cfg.name = i.name;
5833
- if (typeof i.provider === "string") cfg.provider = i.provider;
5834
- if (typeof i.model === "string") cfg.model = i.model;
5835
- if (typeof i.systemPromptOverride === "string")
5836
- cfg.systemPromptOverride = i.systemPromptOverride;
5837
- if (typeof i.maxIterations === "number") cfg.maxIterations = i.maxIterations;
5838
- if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
5839
- if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
5840
- try {
5841
- const subagentId = await director.spawn(cfg);
5842
- return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name };
5843
- } catch (err) {
5844
- if (err instanceof DirectorBudgetError) {
5845
- return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
5846
- }
5847
- return { error: err instanceof Error ? err.message : String(err) };
5848
- }
5849
- }
5850
- };
5851
- }
5852
- function makeAssignTool(director) {
5853
- const inputSchema = {
5854
- type: "object",
5855
- properties: {
5856
- subagentId: { type: "string", description: "Target subagent id. Required." },
5857
- description: {
5858
- type: "string",
5859
- description: "The task in natural language \u2014 what you want this subagent to do."
5860
- },
5861
- maxToolCalls: { type: "number", description: "Optional per-task tool-call budget override." },
5862
- timeoutMs: { type: "number", description: "Optional per-task timeout in ms." }
5863
- },
5864
- required: ["subagentId", "description"]
5865
- };
5866
- return {
5867
- name: "assign_task",
5868
- description: "Hand a task to a previously spawned subagent. Returns the task id \u2014 pass it to `await_tasks` to block on completion.",
5869
- permission: "auto",
5870
- mutating: false,
5871
- inputSchema,
5872
- async execute(input) {
5873
- const i = input;
5874
- const task = {
5875
- id: randomUUID(),
5876
- description: i.description,
5877
- subagentId: i.subagentId,
5878
- maxToolCalls: i.maxToolCalls,
5879
- timeoutMs: i.timeoutMs
5880
- };
5881
- const taskId = await director.assign(task);
5882
- return { taskId, subagentId: i.subagentId };
5883
- }
5884
- };
5885
- }
5886
- function makeAwaitTasksTool(director) {
5887
- const inputSchema = {
5888
- type: "object",
5889
- properties: {
5890
- taskIds: {
5891
- type: "array",
5892
- items: { type: "string" },
5893
- description: "One or more task ids returned by `assign_task`. The call blocks until every id resolves."
5894
- }
5895
- },
5896
- required: ["taskIds"]
5897
- };
5898
- return {
5899
- name: "await_tasks",
5900
- description: "Block until every named task completes. Returns the array of TaskResult \u2014 use this to gather subagent output before deciding the next step.",
5901
- permission: "auto",
5902
- mutating: false,
5903
- inputSchema,
5904
- async execute(input) {
5905
- const i = input;
5906
- const results = await director.awaitTasks(i.taskIds);
5907
- return { results };
5908
- }
5909
- };
5910
- }
5911
- function makeAskTool(director) {
5912
- const inputSchema = {
5913
- type: "object",
5914
- properties: {
5915
- subagentId: {
5916
- type: "string",
5917
- description: "Subagent to ask. Must be a previously spawned id."
5918
- },
5919
- question: {
5920
- type: "string",
5921
- description: "The question or instruction. Sent as the bridge message payload."
5922
- },
5923
- timeoutMs: { type: "number", description: "Optional timeout in ms (default 30s)." }
5924
- },
5925
- required: ["subagentId", "question"]
5926
- };
5927
- return {
5928
- name: "ask_subagent",
5929
- description: "Synchronously ask a subagent a question. Blocks until the subagent replies via the bridge (or the timeout fires). Use this when you need a one-shot answer without spawning a fresh task.",
5930
- permission: "auto",
5931
- mutating: false,
5932
- inputSchema,
5933
- async execute(input) {
5934
- const i = input;
5935
- try {
5936
- const answer = await director.ask(i.subagentId, { question: i.question }, i.timeoutMs);
5937
- return { ok: true, answer };
5938
- } catch (err) {
5939
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
5940
- }
5941
- }
5942
- };
5943
- }
5944
- function makeRollUpTool(director) {
5945
- const inputSchema = {
5946
- type: "object",
5947
- properties: {
5948
- taskIds: {
5949
- type: "array",
5950
- items: { type: "string" },
5951
- description: "Completed task ids to aggregate. Pass the ids returned by previous `assign_task` calls."
5952
- },
5953
- style: {
5954
- type: "string",
5955
- enum: ["markdown", "json"],
5956
- description: "Output flavor \u2014 markdown (default) for in-prompt summarization, json for structured downstream processing."
5957
- }
5958
- },
5959
- required: ["taskIds"]
5960
- };
5961
- return {
5962
- name: "roll_up",
5963
- description: "Aggregate completed task results into a single formatted summary. Use this after `await_tasks` to fold subagent outputs back into the director's context before deciding the next step.",
5964
- permission: "auto",
5965
- mutating: false,
5966
- inputSchema,
5967
- async execute(input) {
5968
- const i = input;
5969
- const summary = director.rollUp(i.taskIds, i.style ?? "markdown");
5970
- return { summary, count: i.taskIds.length };
5971
- }
5972
- };
5973
- }
5974
- function makeTerminateTool(director) {
5975
- const inputSchema = {
5976
- type: "object",
5977
- properties: {
5978
- subagentId: { type: "string", description: "Subagent to abort." }
5979
- },
5980
- required: ["subagentId"]
5981
- };
5982
- return {
5983
- name: "terminate_subagent",
5984
- description: 'Forcibly abort a subagent. Use sparingly \u2014 prefer waiting on the natural budget to expire. The current task (if any) ends with status "stopped".',
5985
- permission: "auto",
5986
- mutating: true,
5987
- inputSchema,
5988
- async execute(input) {
5989
- const i = input;
5990
- await director.terminate(i.subagentId);
5991
- return { ok: true };
5992
- }
5993
- };
5994
- }
5995
- function makeFleetStatusTool(director) {
5996
- return {
5997
- name: "fleet_status",
5998
- description: "Snapshot of the fleet \u2014 every subagent's current status, pending vs. completed task counts, and the running total iteration count. Cheap; call freely.",
5999
- permission: "auto",
6000
- mutating: false,
6001
- inputSchema: { type: "object", properties: {}, required: [] },
6002
- async execute() {
6003
- return director.status();
6004
- }
6005
- };
6006
- }
6007
- function makeFleetUsageTool(director) {
6008
- return {
6009
- name: "fleet_usage",
6010
- description: "Token + cost breakdown across the fleet, per-subagent and totals. Use this to reason about which workers to assign costly tasks to or when to wrap up to stay within budget.",
6011
- permission: "auto",
6012
- mutating: false,
6013
- inputSchema: { type: "object", properties: {}, required: [] },
6014
- async execute() {
6015
- return director.snapshot();
6016
- }
6017
- };
6018
- }
6019
6219
  function createDelegateTool(opts) {
6020
6220
  const defaultTimeoutMs = opts.defaultTimeoutMs ?? 4 * 60 * 60 * 1e3;
6021
6221
  const rosterIds = opts.roster ? Object.keys(opts.roster) : [];
@@ -8553,13 +8753,13 @@ function roughEstimate(messages) {
8553
8753
  function createContextManagerTool(opts = {}) {
8554
8754
  return {
8555
8755
  name: CONTEXT_MANAGER_TOOL_NAME,
8556
- description: 'Inspect or reorganize the conversation context window. Use "check" to see token budget. Use "summary" to collapse a message range into a concise note (provide "text" for custom summary). Use "prune" to remove specific messages by index. Use "add_note" to inject a summary note. Use "compact" to run aggressive compaction.',
8756
+ description: 'Inspect or reorganize the conversation context window. Use "check" to see token budget. Use "summary" to collapse a message range into a concise note (provide "text" for custom summary). Use "prune" to remove specific messages by index. Use "add_note" to inject a summary note. Use "compact" to run aggressive compaction. Use "repair" to remove orphan tool_use/tool_result blocks after manual context surgery.',
8557
8757
  inputSchema: {
8558
8758
  type: "object",
8559
8759
  properties: {
8560
8760
  action: {
8561
8761
  type: "string",
8562
- enum: ["check", "summary", "prune", "add_note", "compact"],
8762
+ enum: ["check", "summary", "prune", "add_note", "compact", "repair"],
8563
8763
  description: "The context operation to perform."
8564
8764
  },
8565
8765
  from: {
@@ -8587,12 +8787,15 @@ function createContextManagerTool(opts = {}) {
8587
8787
  const messages = ctx.messages;
8588
8788
  const beforeTokens = roughEstimate(messages);
8589
8789
  const applyMessages = (next) => {
8790
+ const repaired = repairToolUseAdjacency(next);
8791
+ const finalMessages = repaired.messages;
8590
8792
  if (ctx.state) {
8591
- ctx.state.replaceMessages(next);
8793
+ ctx.state.replaceMessages(finalMessages);
8592
8794
  } else {
8593
8795
  messages.length = 0;
8594
- messages.splice(0, 0, ...next);
8796
+ messages.splice(0, 0, ...finalMessages);
8595
8797
  }
8798
+ return repaired.report;
8596
8799
  };
8597
8800
  switch (input.action) {
8598
8801
  case "check": {
@@ -8609,6 +8812,22 @@ function createContextManagerTool(opts = {}) {
8609
8812
  })
8610
8813
  };
8611
8814
  }
8815
+ case "repair": {
8816
+ const repair = applyMessages([...messages]);
8817
+ const afterTokens = roughEstimate(ctx.messages);
8818
+ return {
8819
+ action: "repair",
8820
+ beforeTokens,
8821
+ afterTokens,
8822
+ messageCount: ctx.messages.length,
8823
+ repaired: repair.changed ? {
8824
+ removedToolUses: repair.removedToolUses,
8825
+ removedToolResults: repair.removedToolResults,
8826
+ removedMessages: repair.removedMessages
8827
+ } : void 0,
8828
+ notes: repair.changed ? "Context tool-call adjacency repaired." : "Context tool-call adjacency already valid."
8829
+ };
8830
+ }
8612
8831
  case "compact": {
8613
8832
  if (!opts.compactor) {
8614
8833
  return {
@@ -8619,11 +8838,19 @@ function createContextManagerTool(opts = {}) {
8619
8838
  };
8620
8839
  }
8621
8840
  const report = await opts.compactor.compact(ctx);
8841
+ const repair = applyMessages([...ctx.messages]);
8842
+ const afterTokens = repair.changed ? roughEstimate(ctx.messages) : report.after;
8843
+ const repaired = report.repaired ?? (repair.changed ? repair : void 0);
8622
8844
  return {
8623
8845
  action: "compact",
8624
8846
  beforeTokens,
8625
- afterTokens: report.after,
8626
- messageCount: messages.length
8847
+ afterTokens,
8848
+ messageCount: ctx.messages.length,
8849
+ repaired: repaired ? {
8850
+ removedToolUses: repaired.removedToolUses,
8851
+ removedToolResults: repaired.removedToolResults,
8852
+ removedMessages: repaired.removedMessages
8853
+ } : void 0
8627
8854
  };
8628
8855
  }
8629
8856
  case "prune": {
@@ -8639,14 +8866,19 @@ function createContextManagerTool(opts = {}) {
8639
8866
  }
8640
8867
  const copy = [...messages];
8641
8868
  const removed = copy.splice(from, to - from + 1);
8642
- applyMessages(copy);
8643
- const afterTokens = roughEstimate(copy);
8869
+ const repair = applyMessages(copy);
8870
+ const afterTokens = roughEstimate(ctx.messages);
8644
8871
  return {
8645
8872
  action: "prune",
8646
8873
  beforeTokens,
8647
8874
  afterTokens,
8648
- messageCount: copy.length,
8649
- removedCount: removed.length
8875
+ messageCount: ctx.messages.length,
8876
+ removedCount: removed.length,
8877
+ repaired: repair.changed ? {
8878
+ removedToolUses: repair.removedToolUses,
8879
+ removedToolResults: repair.removedToolResults,
8880
+ removedMessages: repair.removedMessages
8881
+ } : void 0
8650
8882
  };
8651
8883
  }
8652
8884
  case "add_note": {
@@ -8658,14 +8890,19 @@ function createContextManagerTool(opts = {}) {
8658
8890
  };
8659
8891
  const copy = [...messages];
8660
8892
  copy.splice(afterIdx, 0, noteMsg);
8661
- applyMessages(copy);
8662
- const afterTokens = roughEstimate(copy);
8893
+ const repair = applyMessages(copy);
8894
+ const afterTokens = roughEstimate(ctx.messages);
8663
8895
  return {
8664
8896
  action: "add_note",
8665
8897
  beforeTokens,
8666
8898
  afterTokens,
8667
- messageCount: copy.length,
8668
- summary: noteText
8899
+ messageCount: ctx.messages.length,
8900
+ summary: noteText,
8901
+ repaired: repair.changed ? {
8902
+ removedToolUses: repair.removedToolUses,
8903
+ removedToolResults: repair.removedToolResults,
8904
+ removedMessages: repair.removedMessages
8905
+ } : void 0
8669
8906
  };
8670
8907
  }
8671
8908
  case "summary": {
@@ -8686,14 +8923,19 @@ function createContextManagerTool(opts = {}) {
8686
8923
  };
8687
8924
  const copy = [...messages];
8688
8925
  copy.splice(from, to - from + 1, summaryMsg);
8689
- applyMessages(copy);
8690
- const afterTokens = roughEstimate(copy);
8926
+ const repair = applyMessages(copy);
8927
+ const afterTokens = roughEstimate(ctx.messages);
8691
8928
  return {
8692
8929
  action: "summary",
8693
8930
  beforeTokens,
8694
8931
  afterTokens,
8695
- messageCount: copy.length,
8696
- summary: summaryText
8932
+ messageCount: ctx.messages.length,
8933
+ summary: summaryText,
8934
+ repaired: repair.changed ? {
8935
+ removedToolUses: repair.removedToolUses,
8936
+ removedToolResults: repair.removedToolResults,
8937
+ removedMessages: repair.removedMessages
8938
+ } : void 0
8697
8939
  };
8698
8940
  }
8699
8941
  default:
@@ -8797,6 +9039,41 @@ var sentinelServer = () => ({
8797
9039
  permission: "deny"
8798
9040
  // security tool — require explicit confirmation
8799
9041
  });
9042
+ var zaiVisionServer = () => ({
9043
+ name: "zai-vision",
9044
+ description: "Z.AI Vision MCP \u2014 image analysis and screenshot understanding",
9045
+ transport: "stdio",
9046
+ command: "npx",
9047
+ args: ["-y", "@z_ai/mcp-server@latest"],
9048
+ env: {
9049
+ Z_AI_API_KEY: process.env.Z_AI_API_KEY ?? "",
9050
+ Z_AI_MODE: process.env.Z_AI_MODE ?? "ZAI"
9051
+ },
9052
+ allowedTools: [
9053
+ "image_analysis",
9054
+ "extract_text_from_screenshot",
9055
+ "diagnose_error_screenshot",
9056
+ "understand_technical_diagram",
9057
+ "analyze_data_visualization",
9058
+ "ui_diff_check"
9059
+ ],
9060
+ permission: "auto"
9061
+ });
9062
+ var miniMaxVisionServer = () => ({
9063
+ name: "minimax-vision",
9064
+ description: "MiniMax MCP \u2014 image understanding via understand_image",
9065
+ transport: "stdio",
9066
+ command: "uvx",
9067
+ args: ["minimax-coding-plan-mcp", "-y"],
9068
+ env: {
9069
+ MINIMAX_API_KEY: process.env.MINIMAX_API_KEY ?? "",
9070
+ MINIMAX_MCP_BASE_PATH: process.env.MINIMAX_MCP_BASE_PATH ?? "./.wrongstack/minimax-output",
9071
+ MINIMAX_API_HOST: process.env.MINIMAX_API_HOST ?? "https://api.minimax.io",
9072
+ MINIMAX_API_RESOURCE_MODE: process.env.MINIMAX_API_RESOURCE_MODE ?? "url"
9073
+ },
9074
+ allowedTools: ["understand_image"],
9075
+ permission: "auto"
9076
+ });
8800
9077
  var allServers = () => ({
8801
9078
  filesystem: { ...filesystemServer(), enabled: false },
8802
9079
  github: { ...githubServer(), enabled: false },
@@ -8807,9 +9084,11 @@ var allServers = () => ({
8807
9084
  slack: { ...slackServer(), enabled: false },
8808
9085
  aws: { ...awsServer(), enabled: false },
8809
9086
  "google-maps": { ...googleMapsServer(), enabled: false },
8810
- sentinel: { ...sentinelServer(), enabled: false }
9087
+ sentinel: { ...sentinelServer(), enabled: false },
9088
+ "zai-vision": { ...zaiVisionServer(), enabled: false },
9089
+ "minimax-vision": { ...miniMaxVisionServer(), enabled: false }
8811
9090
  });
8812
9091
 
8813
- export { ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorBudgetError, DirectorStateCheckpoint, DoneConditionChecker, FLEET_ROSTER, FleetBus, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SubagentBudget, TaskFlow, TaskGenerator, TaskTracker, ToolExecutor, addPlanItem, allServers, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createContextManagerTool, createDelegateTool, createMessage, decryptConfigSecrets, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, githubServer, googleMapsServer, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, migratePlaintextSecrets, removePlanItem, renderPrometheus, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, wireMetricsToEvents };
9092
+ export { ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorBudgetError, DirectorStateCheckpoint, DoneConditionChecker, FLEET_ROSTER, FleetBus, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SubagentBudget, TaskFlow, TaskGenerator, TaskTracker, ToolExecutor, addPlanItem, allServers, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createContextManagerTool, createDelegateTool, createMessage, decryptConfigSecrets2 as decryptConfigSecrets, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, githubServer, googleMapsServer, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderPrometheus, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, wireMetricsToEvents, zaiVisionServer };
8814
9093
  //# sourceMappingURL=index.js.map
8815
9094
  //# sourceMappingURL=index.js.map