@wrongstack/core 0.2.0 → 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 (57) hide show
  1. package/dist/{agent-bridge-DmBiCipY.d.ts → agent-bridge-C3DUGjSb.d.ts} +1 -1
  2. package/dist/{compactor-DSl2FK7a.d.ts → compactor-DpJBI1YH.d.ts} +8 -2
  3. package/dist/{config-DXrqb41m.d.ts → config-D2qvAxVd.d.ts} +39 -2
  4. package/dist/{context-u0bryklF.d.ts → context-IovtuTf8.d.ts} +2 -0
  5. package/dist/coordination/index.d.ts +11 -11
  6. package/dist/coordination/index.js +307 -245
  7. package/dist/coordination/index.js.map +1 -1
  8. package/dist/defaults/index.d.ts +30 -15
  9. package/dist/defaults/index.js +1077 -479
  10. package/dist/defaults/index.js.map +1 -1
  11. package/dist/{events-B6Q03pTu.d.ts → events-BHIQs4o1.d.ts} +34 -1
  12. package/dist/execution/index.d.ts +17 -14
  13. package/dist/execution/index.js +166 -18
  14. package/dist/execution/index.js.map +1 -1
  15. package/dist/extension/index.d.ts +9 -0
  16. package/dist/extension/index.js +241 -0
  17. package/dist/extension/index.js.map +1 -0
  18. package/dist/{plugin-CoYYZKdn.d.ts → index-hWNybrNZ.d.ts} +368 -11
  19. package/dist/index.d.ts +76 -26
  20. package/dist/index.js +1595 -748
  21. package/dist/index.js.map +1 -1
  22. package/dist/infrastructure/index.d.ts +6 -6
  23. package/dist/infrastructure/index.js +191 -20
  24. package/dist/infrastructure/index.js.map +1 -1
  25. package/dist/kernel/index.d.ts +12 -9
  26. package/dist/kernel/index.js +73 -7
  27. package/dist/kernel/index.js.map +1 -1
  28. package/dist/{mcp-servers-BA1Ofmfj.d.ts → mcp-servers-C2OopXOn.d.ts} +21 -5
  29. package/dist/models/index.d.ts +2 -2
  30. package/dist/models/index.js +24 -1
  31. package/dist/models/index.js.map +1 -1
  32. package/dist/{multi-agent-BDfkxL5C.d.ts → multi-agent-B9a6sflH.d.ts} +2 -2
  33. package/dist/observability/index.d.ts +2 -2
  34. package/dist/{path-resolver-Crkt8wTQ.d.ts → path-resolver--59rCou3.d.ts} +2 -2
  35. package/dist/provider-runner-B39miKRw.d.ts +36 -0
  36. package/dist/sdd/index.d.ts +3 -3
  37. package/dist/{secret-scrubber-3TLUkiCV.d.ts → secret-scrubber-CgG2tV2B.d.ts} +1 -1
  38. package/dist/{secret-scrubber-CwYliRWd.d.ts → secret-scrubber-Cuy5afaQ.d.ts} +1 -1
  39. package/dist/security/index.d.ts +3 -3
  40. package/dist/security/index.js +24 -1
  41. package/dist/security/index.js.map +1 -1
  42. package/dist/{selector-BRqzvugb.d.ts → selector-wT2fv9Fg.d.ts} +1 -1
  43. package/dist/{session-reader-C3x96CDR.d.ts → session-reader-CcPi4BQ8.d.ts} +1 -1
  44. package/dist/{skill-Bx8jxznf.d.ts → skill-C_7znCIC.d.ts} +2 -2
  45. package/dist/storage/index.d.ts +7 -6
  46. package/dist/storage/index.js +204 -14
  47. package/dist/storage/index.js.map +1 -1
  48. package/dist/{renderer-0A2ZEtca.d.ts → system-prompt-Dk1qm8ey.d.ts} +30 -2
  49. package/dist/{tool-executor-CYdZdtno.d.ts → tool-executor-HsBLGRaA.d.ts} +5 -5
  50. package/dist/types/index.d.ts +16 -16
  51. package/dist/types/index.js +230 -10
  52. package/dist/types/index.js.map +1 -1
  53. package/dist/utils/index.d.ts +23 -2
  54. package/dist/utils/index.js +117 -2
  55. package/dist/utils/index.js.map +1 -1
  56. package/package.json +5 -1
  57. package/dist/system-prompt-CG9jU5-5.d.ts +0 -31
@@ -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
  }
@@ -156,7 +156,7 @@ async function atomicWrite(targetPath, content, opts = {}) {
156
156
  if (mode !== void 0) {
157
157
  await fsp.chmod(tmp, mode);
158
158
  }
159
- await fsp.rename(tmp, targetPath);
159
+ await renameWithRetry(tmp, targetPath);
160
160
  } catch (err) {
161
161
  try {
162
162
  await fsp.unlink(tmp);
@@ -168,6 +168,121 @@ async function atomicWrite(targetPath, content, opts = {}) {
168
168
  async function ensureDir(dir) {
169
169
  await fsp.mkdir(dir, { recursive: true });
170
170
  }
171
+ var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
172
+ async function renameWithRetry(from, to) {
173
+ if (process.platform !== "win32") {
174
+ await fsp.rename(from, to);
175
+ return;
176
+ }
177
+ const delays = [10, 25, 60, 120, 250];
178
+ let lastErr;
179
+ for (let i = 0; i <= delays.length; i++) {
180
+ try {
181
+ await fsp.rename(from, to);
182
+ return;
183
+ } catch (err) {
184
+ lastErr = err;
185
+ const code = err?.code;
186
+ if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
187
+ throw err;
188
+ }
189
+ await new Promise((resolve2) => setTimeout(resolve2, delays[i]));
190
+ }
191
+ }
192
+ throw lastErr;
193
+ }
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
+ }
171
286
 
172
287
  // src/storage/session-store.ts
173
288
  var DefaultSessionStore = class {
@@ -371,11 +486,17 @@ var DefaultSessionStore = class {
371
486
  if (openToolUses.size > 0) {
372
487
  this.events?.emit("session.damaged", {
373
488
  sessionId,
374
- detail: `${openToolUses.size} tool_use blocks without matching results \u2014 replay truncated`
489
+ detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
375
490
  });
376
- return { messages, usage };
377
491
  }
378
- return { messages, usage };
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`
497
+ });
498
+ }
499
+ return { messages: repaired.messages, usage };
379
500
  }
380
501
  };
381
502
  var FileSessionWriter = class {
@@ -401,6 +522,7 @@ var FileSessionWriter = class {
401
522
  startedAt;
402
523
  meta;
403
524
  closed = false;
525
+ closing = false;
404
526
  manifestFile;
405
527
  summary;
406
528
  tokenIn = 0;
@@ -416,9 +538,7 @@ var FileSessionWriter = class {
416
538
  resumed;
417
539
  appendFailCount = 0;
418
540
  lastAppendWarnAt = 0;
419
- async writeSessionStart() {
420
- if (this.initDone || this.closed) return;
421
- this.initDone = true;
541
+ async writeSessionStartLazy() {
422
542
  const record = `${JSON.stringify({
423
543
  type: this.resumed ? "session_resumed" : "session_start",
424
544
  ts: this.startedAt,
@@ -437,7 +557,8 @@ var FileSessionWriter = class {
437
557
  async append(event) {
438
558
  if (this.closed) return;
439
559
  if (!this.initDone) {
440
- await this.writeSessionStart();
560
+ this.initDone = true;
561
+ await this.writeSessionStartLazy();
441
562
  }
442
563
  this.observeForSummary(event);
443
564
  try {
@@ -478,7 +599,8 @@ var FileSessionWriter = class {
478
599
  }
479
600
  }
480
601
  async close() {
481
- if (this.closed) return;
602
+ if (this.closing) return;
603
+ this.closing = true;
482
604
  this.closed = true;
483
605
  if (this.manifestFile) {
484
606
  try {
@@ -881,80 +1003,7 @@ function deepFreeze(obj) {
881
1003
  return Object.freeze(obj);
882
1004
  }
883
1005
 
884
- // src/types/secret-vault.ts
885
- var ENCRYPTED_PREFIX = "enc:v1:";
886
-
887
- // src/security/secret-vault.ts
888
- var KEY_BYTES = 32;
889
- var IV_BYTES = 12;
890
- var TAG_BYTES = 16;
891
- var ALGO = "aes-256-gcm";
892
- var DefaultSecretVault = class {
893
- keyFile;
894
- key;
895
- constructor(opts) {
896
- this.keyFile = opts.keyFile;
897
- }
898
- isEncrypted(value) {
899
- return typeof value === "string" && value.startsWith(ENCRYPTED_PREFIX);
900
- }
901
- encrypt(plaintext) {
902
- if (this.isEncrypted(plaintext)) return plaintext;
903
- const key = this.loadOrCreateKey();
904
- const iv = randomBytes(IV_BYTES);
905
- const cipher = createCipheriv(ALGO, key, iv);
906
- const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
907
- const tag = cipher.getAuthTag();
908
- return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
909
- }
910
- decrypt(value) {
911
- if (!this.isEncrypted(value)) return value;
912
- const rest = value.slice(ENCRYPTED_PREFIX.length);
913
- const parts = rest.split(":");
914
- if (parts.length !== 3) {
915
- throw new Error("SecretVault: malformed encrypted value");
916
- }
917
- const [ivB64, tagB64, ctB64] = parts;
918
- const iv = Buffer.from(ivB64, "base64");
919
- const tag = Buffer.from(tagB64, "base64");
920
- const ct = Buffer.from(ctB64, "base64");
921
- if (iv.length !== IV_BYTES) throw new Error("SecretVault: bad IV length");
922
- if (tag.length !== TAG_BYTES) throw new Error("SecretVault: bad tag length");
923
- const key = this.loadOrCreateKey();
924
- const decipher = createDecipheriv(ALGO, key, iv);
925
- decipher.setAuthTag(tag);
926
- const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
927
- return pt.toString("utf8");
928
- }
929
- loadOrCreateKey() {
930
- if (this.key) return this.key;
931
- try {
932
- const buf = fs4.readFileSync(this.keyFile);
933
- if (buf.length !== KEY_BYTES) {
934
- throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
935
- }
936
- this.key = buf;
937
- return this.key;
938
- } catch (err) {
939
- if (err.code !== "ENOENT") throw err;
940
- }
941
- fs4.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
942
- const key = randomBytes(KEY_BYTES);
943
- try {
944
- fs4.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
945
- } catch (err) {
946
- if (err.code !== "EEXIST") throw err;
947
- const buf = fs4.readFileSync(this.keyFile);
948
- if (buf.length !== KEY_BYTES) {
949
- throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
950
- }
951
- this.key = buf;
952
- return this.key;
953
- }
954
- this.key = key;
955
- return key;
956
- }
957
- };
1006
+ // src/security/config-secrets.ts
958
1007
  function decryptConfigSecrets(cfg, vault) {
959
1008
  return walk(cfg, vault, (v, key) => {
960
1009
  try {
@@ -968,9 +1017,6 @@ function decryptConfigSecrets(cfg, vault) {
968
1017
  }
969
1018
  });
970
1019
  }
971
- function encryptConfigSecrets(cfg, vault) {
972
- return walk(cfg, vault, (v) => vault.encrypt(v));
973
- }
974
1020
  function walk(node, vault, transform) {
975
1021
  if (node === null || node === void 0) return node;
976
1022
  if (typeof node !== "object") return node;
@@ -996,77 +1042,56 @@ function isSecretField(name) {
996
1042
  if (NON_SECRET_OVERRIDES.has(lc)) return false;
997
1043
  return SECRET_KEY_PATTERN.test(lc);
998
1044
  }
999
- async function rewriteConfigEncrypted(configPath, vault, patch) {
1000
- let current = {};
1001
- try {
1002
- const raw = await fsp.readFile(configPath, "utf8");
1003
- current = JSON.parse(raw);
1004
- } catch {
1005
- }
1006
- const merged = deepMerge(current, patch ?? {});
1007
- const encrypted = encryptConfigSecrets(merged, vault);
1008
- await fsp.mkdir(path3.dirname(configPath), { recursive: true });
1009
- await fsp.writeFile(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
1010
- try {
1011
- await fsp.chmod(configPath, 384);
1012
- } catch {
1013
- }
1014
- }
1015
- async function migratePlaintextSecrets(configPath, vault) {
1016
- let raw;
1017
- try {
1018
- raw = await fsp.readFile(configPath, "utf8");
1019
- } catch {
1020
- return { migrated: 0, file: configPath };
1021
- }
1022
- let parsed;
1023
- try {
1024
- parsed = JSON.parse(raw);
1025
- } catch {
1026
- return { migrated: 0, file: configPath };
1027
- }
1028
- const counter = { n: 0 };
1029
- const migrated = walkCount(parsed, vault, counter);
1030
- if (counter.n === 0) return { migrated: 0, file: configPath };
1031
- await fsp.writeFile(configPath, JSON.stringify(migrated, null, 2), { mode: 384 });
1032
- try {
1033
- await fsp.chmod(configPath, 384);
1034
- } catch {
1035
- }
1036
- return { migrated: counter.n, file: configPath };
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 } }));
1037
1092
  }
1038
- function walkCount(node, vault, counter) {
1039
- if (node === null || node === void 0) return node;
1040
- if (typeof node !== "object") return node;
1041
- if (Array.isArray(node)) {
1042
- return node.map((item) => walkCount(item, vault, counter));
1043
- }
1044
- const out = {};
1045
- for (const [k, v] of Object.entries(node)) {
1046
- if (typeof v === "string" && isSecretField(k) && !vault.isEncrypted(v) && v.length > 0) {
1047
- out[k] = vault.encrypt(v);
1048
- counter.n++;
1049
- } else if (typeof v === "object" && v !== null) {
1050
- out[k] = walkCount(v, vault, counter);
1051
- } else {
1052
- out[k] = v;
1053
- }
1054
- }
1055
- return out;
1056
- }
1057
- var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1058
- function deepMerge(a, b) {
1059
- const out = { ...a };
1060
- for (const [k, v] of Object.entries(b)) {
1061
- if (FORBIDDEN_PROTO_KEYS.has(k)) continue;
1062
- const existing = out[k];
1063
- if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
1064
- out[k] = deepMerge(existing, v);
1065
- } else {
1066
- out[k] = v;
1067
- }
1068
- }
1069
- return out;
1093
+ function isContextWindowModeId(id) {
1094
+ return CONTEXT_WINDOW_MODES.some((m) => m.id === id);
1070
1095
  }
1071
1096
 
1072
1097
  // src/utils/safe-json.ts
@@ -1088,6 +1113,7 @@ function safeParse(input, maxBytes = 5e6) {
1088
1113
  var BEHAVIOR_DEFAULTS = {
1089
1114
  version: 1,
1090
1115
  context: {
1116
+ mode: DEFAULT_CONTEXT_WINDOW_MODE_ID,
1091
1117
  warnThreshold: 0.6,
1092
1118
  softThreshold: 0.75,
1093
1119
  hardThreshold: 0.9,
@@ -1133,13 +1159,13 @@ var ENV_MAP = {
1133
1159
  function isPrimitiveArray(a) {
1134
1160
  return a.every((v) => v === null || typeof v !== "object");
1135
1161
  }
1136
- var FORBIDDEN_PROTO_KEYS2 = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1137
- function deepMerge2(base, patch) {
1162
+ var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1163
+ function deepMerge(base, patch) {
1138
1164
  if (typeof base !== "object" || base === null) return patch ?? base;
1139
1165
  if (typeof patch !== "object" || patch === null) return base;
1140
1166
  const out = { ...base };
1141
1167
  for (const [k, v] of Object.entries(patch)) {
1142
- if (FORBIDDEN_PROTO_KEYS2.has(k)) continue;
1168
+ if (FORBIDDEN_PROTO_KEYS.has(k)) continue;
1143
1169
  const existing = out[k];
1144
1170
  if (Array.isArray(v)) {
1145
1171
  if (Array.isArray(existing) && isPrimitiveArray(v) && isPrimitiveArray(existing)) {
@@ -1153,7 +1179,7 @@ function deepMerge2(base, patch) {
1153
1179
  }
1154
1180
  }
1155
1181
  } else if (typeof v === "object" && v !== null && typeof existing === "object" && existing !== null) {
1156
- out[k] = deepMerge2(existing, v);
1182
+ out[k] = deepMerge(existing, v);
1157
1183
  } else if (v !== void 0) {
1158
1184
  out[k] = v;
1159
1185
  }
@@ -1177,8 +1203,8 @@ var DefaultConfigLoader = class {
1177
1203
  this.readJson(this.paths.globalConfig),
1178
1204
  this.readJson(this.paths.projectLocalConfig)
1179
1205
  ]);
1180
- cfg = deepMerge2(cfg, global);
1181
- cfg = deepMerge2(cfg, local);
1206
+ cfg = deepMerge(cfg, global);
1207
+ cfg = deepMerge(cfg, local);
1182
1208
  for (const [key, fn] of Object.entries(ENV_MAP)) {
1183
1209
  const v = process.env[key];
1184
1210
  if (v) fn(cfg, v);
@@ -1192,14 +1218,14 @@ var DefaultConfigLoader = class {
1192
1218
  try {
1193
1219
  const patch = await src.read();
1194
1220
  if (patch && Object.keys(patch).length > 0) {
1195
- cfg = deepMerge2(cfg, patch);
1221
+ cfg = deepMerge(cfg, patch);
1196
1222
  }
1197
1223
  } catch (err) {
1198
1224
  console.warn(`Config source "${src.name}" failed`, err);
1199
1225
  }
1200
1226
  }
1201
1227
  if (opts.cliFlags) {
1202
- cfg = deepMerge2(cfg, opts.cliFlags);
1228
+ cfg = deepMerge(cfg, opts.cliFlags);
1203
1229
  }
1204
1230
  if (this.vault) {
1205
1231
  cfg = decryptConfigSecrets(cfg, this.vault);
@@ -1262,6 +1288,10 @@ var DefaultConfigLoader = class {
1262
1288
  if (c.warnThreshold >= c.softThreshold || c.softThreshold >= c.hardThreshold) {
1263
1289
  throw new Error("Config: context thresholds must satisfy warn < soft < hard");
1264
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
+ }
1265
1295
  }
1266
1296
  validateIdentity(cfg) {
1267
1297
  if (!cfg.provider) {
@@ -1838,24 +1868,35 @@ async function saveTodosCheckpoint(filePath, sessionId, todos) {
1838
1868
  function attachTodosCheckpoint(state, filePath, sessionId) {
1839
1869
  let timer = null;
1840
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
+ };
1841
1876
  const flush = () => {
1842
1877
  timer = null;
1843
1878
  if (pending) {
1844
- void saveTodosCheckpoint(filePath, sessionId, pending);
1879
+ const todos = pending;
1845
1880
  pending = null;
1881
+ return enqueueWrite(todos);
1846
1882
  }
1883
+ return writeChain;
1847
1884
  };
1848
1885
  const unsubscribe = state.onChange((change) => {
1849
1886
  if (change.kind !== "todos_replaced") return;
1850
1887
  pending = change.todos;
1851
1888
  if (timer) clearTimeout(timer);
1852
- timer = setTimeout(flush, 150);
1889
+ timer = setTimeout(() => {
1890
+ void flush();
1891
+ }, 150);
1853
1892
  });
1854
- return () => {
1893
+ return async () => {
1855
1894
  unsubscribe();
1856
1895
  if (timer) {
1857
1896
  clearTimeout(timer);
1858
- flush();
1897
+ await flush();
1898
+ } else {
1899
+ await writeChain;
1859
1900
  }
1860
1901
  };
1861
1902
  }
@@ -2160,7 +2201,195 @@ var DefaultSecretScrubber = class {
2160
2201
  };
2161
2202
  return visit(obj);
2162
2203
  }
2163
- };
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 {
2357
+ }
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;
2375
+ }
2376
+ }
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
+ }
2390
+ }
2391
+ return out;
2392
+ }
2164
2393
 
2165
2394
  // src/utils/glob-match.ts
2166
2395
  function escapeRegex(s) {
@@ -2718,6 +2947,302 @@ function parseDescription(raw) {
2718
2947
  return { trigger, scope };
2719
2948
  }
2720
2949
 
2950
+ // src/core/streaming-response-builder.ts
2951
+ function buildResponse(state) {
2952
+ const content = [];
2953
+ for (const b of state.blockOrder) {
2954
+ if (b.kind === "text") {
2955
+ const txt = state.textBuffers[b.idx] ?? "";
2956
+ if (txt) content.push({ type: "text", text: txt });
2957
+ } else if (b.kind === "thinking") {
2958
+ const t = state.thinking[b.idx];
2959
+ if (!t) continue;
2960
+ if (!t.textBuf && !t.signature) continue;
2961
+ const block = { type: "thinking", thinking: t.textBuf };
2962
+ if (t.signature) block.signature = t.signature;
2963
+ if (t.providerMeta && Object.keys(t.providerMeta).length > 0) {
2964
+ block.providerMeta = t.providerMeta;
2965
+ }
2966
+ content.push(block);
2967
+ } else {
2968
+ const tb = state.tools.get(b.id);
2969
+ if (tb) {
2970
+ const block = {
2971
+ type: "tool_use",
2972
+ id: b.id,
2973
+ name: tb.name,
2974
+ input: tb.input ?? {}
2975
+ };
2976
+ if (tb.providerMeta && Object.keys(tb.providerMeta).length > 0) {
2977
+ block.providerMeta = tb.providerMeta;
2978
+ }
2979
+ content.push(block);
2980
+ }
2981
+ }
2982
+ }
2983
+ if (content.length === 0) content.push({ type: "text", text: "" });
2984
+ return { content, stopReason: state.stopReason, usage: state.usage, model: state.model };
2985
+ }
2986
+ function createStreamingState(model) {
2987
+ return {
2988
+ model,
2989
+ stopReason: "end_turn",
2990
+ usage: { input: 0, output: 0 },
2991
+ textBuffers: [],
2992
+ currentTextIndex: -1,
2993
+ tools: /* @__PURE__ */ new Map(),
2994
+ thinking: [],
2995
+ currentThinkingIndex: -1,
2996
+ blockOrder: []
2997
+ };
2998
+ }
2999
+ function handleMessageStart(state, model) {
3000
+ state.model = model;
3001
+ }
3002
+ function handleContentBlockStart(state, ev) {
3003
+ const kind = ev.kind ?? "text";
3004
+ if (kind === "text") {
3005
+ state.currentTextIndex = state.textBuffers.length;
3006
+ state.textBuffers.push("");
3007
+ state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
3008
+ } else if (kind === "tool_use") {
3009
+ const id = ev.id ?? crypto.randomUUID();
3010
+ state.tools.set(id, { name: ev.name ?? "unknown", partial: "" });
3011
+ state.blockOrder.push({ kind: "tool", id });
3012
+ state.currentTextIndex = -1;
3013
+ } else if (kind === "thinking") {
3014
+ state.currentThinkingIndex = state.thinking.length;
3015
+ state.thinking.push({
3016
+ textBuf: "",
3017
+ ...ev.providerMeta ? { providerMeta: ev.providerMeta } : {}
3018
+ });
3019
+ state.blockOrder.push({ kind: "thinking", idx: state.currentThinkingIndex });
3020
+ state.currentTextIndex = -1;
3021
+ }
3022
+ }
3023
+ function handleContentBlockStop(state, ev) {
3024
+ }
3025
+ function handleTextDelta(state, text) {
3026
+ if (state.currentTextIndex === -1) {
3027
+ state.currentTextIndex = state.textBuffers.length;
3028
+ state.textBuffers.push("");
3029
+ state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
3030
+ }
3031
+ state.textBuffers[state.currentTextIndex] = (state.textBuffers[state.currentTextIndex] ?? "") + text;
3032
+ }
3033
+ function handleToolUseStart(state, ev) {
3034
+ state.currentTextIndex = -1;
3035
+ state.tools.set(ev.id, { name: ev.name, partial: "" });
3036
+ state.blockOrder.push({ kind: "tool", id: ev.id });
3037
+ }
3038
+ function handleToolUseInputDelta(state, ev) {
3039
+ const t = state.tools.get(ev.id);
3040
+ if (t) t.partial += ev.partial;
3041
+ }
3042
+ function safeJsonOrRaw(s) {
3043
+ if (!s) return {};
3044
+ try {
3045
+ return JSON.parse(s);
3046
+ } catch {
3047
+ return { _raw: s };
3048
+ }
3049
+ }
3050
+ function handleToolUseStop(state, ev) {
3051
+ const t = state.tools.get(ev.id);
3052
+ if (t) {
3053
+ t.input = ev.input !== void 0 ? ev.input : safeJsonOrRaw(t.partial);
3054
+ if (ev.providerMeta) t.providerMeta = ev.providerMeta;
3055
+ }
3056
+ state.currentTextIndex = -1;
3057
+ }
3058
+ function handleThinkingStart(state, ev) {
3059
+ state.currentThinkingIndex = state.thinking.length;
3060
+ state.thinking.push({
3061
+ textBuf: "",
3062
+ ...ev.providerMeta ? { providerMeta: ev.providerMeta } : {}
3063
+ });
3064
+ state.blockOrder.push({ kind: "thinking", idx: state.currentThinkingIndex });
3065
+ state.currentTextIndex = -1;
3066
+ }
3067
+ function handleThinkingDelta(state, text) {
3068
+ if (state.currentThinkingIndex === -1) {
3069
+ handleThinkingStart(state, {});
3070
+ }
3071
+ const t = state.thinking[state.currentThinkingIndex];
3072
+ if (t) t.textBuf += text;
3073
+ }
3074
+ function handleThinkingSignature(state, signature) {
3075
+ if (state.currentThinkingIndex === -1) {
3076
+ handleThinkingStart(state, {});
3077
+ }
3078
+ const t = state.thinking[state.currentThinkingIndex];
3079
+ if (t) t.signature = signature;
3080
+ }
3081
+ function handleThinkingStop(state) {
3082
+ state.currentThinkingIndex = -1;
3083
+ }
3084
+ function handleMessageStop(state, ev) {
3085
+ state.stopReason = ev.stopReason ?? "end_turn";
3086
+ state.usage = ev.usage ?? { input: 0, output: 0 };
3087
+ }
3088
+ async function streamProviderToResponse(provider, req, signal, ctx, events) {
3089
+ const state = createStreamingState(req.model);
3090
+ const iter = provider.stream(req, { signal })[Symbol.asyncIterator]();
3091
+ try {
3092
+ for (; ; ) {
3093
+ const next = await iter.next();
3094
+ if (next.done) break;
3095
+ const ev = next.value;
3096
+ switch (ev.type) {
3097
+ case "message_start":
3098
+ handleMessageStart(state, ev.model);
3099
+ break;
3100
+ case "content_block_start":
3101
+ handleContentBlockStart(state, ev);
3102
+ break;
3103
+ case "content_block_stop":
3104
+ handleContentBlockStop(state, ev);
3105
+ break;
3106
+ case "text_delta":
3107
+ handleTextDelta(state, ev.text);
3108
+ events.emit("provider.text_delta", { ctx, text: ev.text });
3109
+ break;
3110
+ case "tool_use_start":
3111
+ handleToolUseStart(state, ev);
3112
+ events.emit("provider.tool_use_start", { ctx, id: ev.id, name: ev.name });
3113
+ break;
3114
+ case "tool_use_input_delta":
3115
+ handleToolUseInputDelta(state, ev);
3116
+ break;
3117
+ case "tool_use_stop":
3118
+ handleToolUseStop(state, ev);
3119
+ events.emit("provider.tool_use_stop", { ctx, id: ev.id });
3120
+ break;
3121
+ case "thinking_start":
3122
+ handleThinkingStart(state, ev);
3123
+ break;
3124
+ case "thinking_delta":
3125
+ handleThinkingDelta(state, ev.text);
3126
+ events.emit("provider.thinking_delta", { ctx, text: ev.text });
3127
+ break;
3128
+ case "thinking_signature":
3129
+ handleThinkingSignature(state, ev.signature);
3130
+ break;
3131
+ case "thinking_stop":
3132
+ handleThinkingStop(state);
3133
+ break;
3134
+ case "message_stop":
3135
+ handleMessageStop(state, ev);
3136
+ break;
3137
+ }
3138
+ }
3139
+ } catch (err) {
3140
+ if (signal.aborted) {
3141
+ state.stopReason = "end_turn";
3142
+ return buildResponse(state);
3143
+ }
3144
+ throw err;
3145
+ } finally {
3146
+ try {
3147
+ let drainTimer = null;
3148
+ try {
3149
+ await Promise.race([
3150
+ Promise.resolve(iter.return?.()),
3151
+ new Promise((resolve2) => {
3152
+ drainTimer = setTimeout(resolve2, 500);
3153
+ })
3154
+ ]);
3155
+ } finally {
3156
+ if (drainTimer) clearTimeout(drainTimer);
3157
+ }
3158
+ } catch {
3159
+ }
3160
+ }
3161
+ return buildResponse(state);
3162
+ }
3163
+
3164
+ // src/core/provider-runner.ts
3165
+ async function runProviderWithRetry(opts) {
3166
+ const { provider, request, signal, ctx, events, retry, logger, tracer } = opts;
3167
+ let attempt = 0;
3168
+ for (; ; ) {
3169
+ const span = tracer?.startSpan("provider.complete", {
3170
+ "provider.id": provider.id,
3171
+ "provider.model": request.model,
3172
+ "provider.streaming": provider.capabilities.streaming,
3173
+ "provider.attempt": attempt
3174
+ });
3175
+ try {
3176
+ const res = provider.capabilities.streaming ? await streamProviderToResponse(provider, request, signal, ctx, events) : await provider.complete(request, { signal });
3177
+ span?.setAttribute("provider.stopReason", res.stopReason);
3178
+ span?.setAttribute("provider.usage_in", res.usage.input);
3179
+ span?.setAttribute("provider.usage_out", res.usage.output);
3180
+ span?.end();
3181
+ return res;
3182
+ } catch (err) {
3183
+ if (err instanceof Error) span?.recordError(err);
3184
+ span?.end();
3185
+ if (signal.aborted) throw err;
3186
+ const isProviderErr = err instanceof ProviderError;
3187
+ const errAsErr = err instanceof Error ? err : new Error(String(err));
3188
+ const canRetry = retry.shouldRetry(isProviderErr ? err : errAsErr, attempt);
3189
+ const description = isProviderErr ? err.describe() : errAsErr.message;
3190
+ if (!canRetry) {
3191
+ if (isProviderErr) {
3192
+ events.emit("provider.error", {
3193
+ providerId: err.providerId,
3194
+ status: err.status,
3195
+ description,
3196
+ retryable: false
3197
+ });
3198
+ }
3199
+ throw err;
3200
+ }
3201
+ const delay = Math.round(retry.delayMs(attempt));
3202
+ const attemptNum = attempt + 1;
3203
+ logger.warn(`Provider retry ${attemptNum} in ${delay}ms \u2014 ${description}`);
3204
+ if (isProviderErr) {
3205
+ events.emit("provider.retry", {
3206
+ providerId: err.providerId,
3207
+ attempt: attemptNum,
3208
+ delayMs: delay,
3209
+ status: err.status,
3210
+ description
3211
+ });
3212
+ }
3213
+ await new Promise((resolve2, reject) => {
3214
+ let settled = false;
3215
+ const onAbort = () => {
3216
+ if (settled) return;
3217
+ settled = true;
3218
+ clearTimeout(t);
3219
+ reject(new Error("aborted"));
3220
+ };
3221
+ const t = setTimeout(() => {
3222
+ if (settled) return;
3223
+ settled = true;
3224
+ clearTimeout(t);
3225
+ signal.removeEventListener("abort", onAbort);
3226
+ resolve2();
3227
+ }, delay);
3228
+ if (signal.aborted) {
3229
+ onAbort();
3230
+ return;
3231
+ }
3232
+ signal.addEventListener("abort", onAbort, { once: true });
3233
+ });
3234
+ attempt++;
3235
+ }
3236
+ }
3237
+ }
3238
+
3239
+ // src/execution/provider-runner-impl.ts
3240
+ var DefaultProviderRunner = class {
3241
+ async run(opts) {
3242
+ return runProviderWithRetry(opts);
3243
+ }
3244
+ };
3245
+
2721
3246
  // src/utils/token-estimate.ts
2722
3247
  var RoughTokenEstimate = (text) => Math.max(1, Math.ceil(text.length / 4));
2723
3248
  var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
@@ -2765,20 +3290,36 @@ var HybridCompactor = class {
2765
3290
  async compact(ctx, opts = {}) {
2766
3291
  const beforeTokens = this.estimateMessages(ctx.messages);
2767
3292
  const reductions = [];
2768
- 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);
2769
3297
  if (phase1Saved > 0) reductions.push({ phase: "elision", saved: phase1Saved });
2770
3298
  if (opts.aggressive) {
2771
- const phase2Saved = this.collapseAncientTurns(ctx);
3299
+ const phase2Saved = this.collapseAncientTurns(ctx, preserveK);
2772
3300
  if (phase2Saved > 0) reductions.push({ phase: "summary", saved: phase2Saved });
2773
3301
  }
3302
+ const repaired = repairToolUseAdjacency(ctx.messages);
3303
+ if (repaired.report.changed) {
3304
+ ctx.state.replaceMessages(repaired.messages);
3305
+ }
2774
3306
  const afterTokens = this.estimateMessages(ctx.messages);
2775
- 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
+ };
2776
3317
  }
2777
- eliseOldToolResults(ctx) {
3318
+ eliseOldToolResults(ctx, preserveK = this.preserveK, eliseThreshold = this.eliseThreshold) {
2778
3319
  const messages = ctx.messages;
2779
3320
  let pairCount = 0;
2780
3321
  let preserveStart = messages.length;
2781
- for (let i = messages.length - 1; i >= 0 && pairCount < this.preserveK; i--) {
3322
+ for (let i = messages.length - 1; i >= 0 && pairCount < preserveK; i--) {
2782
3323
  const m = messages[i];
2783
3324
  if (!m) continue;
2784
3325
  if (m.role === "user" || m.role === "assistant") {
@@ -2802,7 +3343,7 @@ var HybridCompactor = class {
2802
3343
  const newContent = msg.content.map((b) => {
2803
3344
  if (b.type !== "tool_result") return b;
2804
3345
  const tokens = estimateToolResultTokens(b.content);
2805
- if (tokens < this.eliseThreshold) return b;
3346
+ if (tokens < eliseThreshold) return b;
2806
3347
  saved += tokens;
2807
3348
  const elided = {
2808
3349
  type: "tool_result",
@@ -2822,9 +3363,9 @@ var HybridCompactor = class {
2822
3363
  if (changed) ctx.state.replaceMessages(nextMessages);
2823
3364
  return saved;
2824
3365
  }
2825
- collapseAncientTurns(ctx) {
3366
+ collapseAncientTurns(ctx, preserveK = this.preserveK) {
2826
3367
  const messages = ctx.messages;
2827
- const cutTarget = Math.max(0, messages.length - this.preserveK * 2);
3368
+ const cutTarget = Math.max(0, messages.length - preserveK * 2);
2828
3369
  if (cutTarget <= 0) return 0;
2829
3370
  let boundary = -1;
2830
3371
  for (let i = cutTarget; i < messages.length; i++) {
@@ -2865,6 +3406,15 @@ var HybridCompactor = class {
2865
3406
  return total;
2866
3407
  }
2867
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
+ }
2868
3418
  function hasTextContent(m) {
2869
3419
  if (typeof m.content === "string") return m.content.trim().length > 0;
2870
3420
  return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
@@ -2911,8 +3461,19 @@ var IntelligentCompactor = class {
2911
3461
  const saved2 = this.lightweightCompact(ctx);
2912
3462
  if (saved2 > 0) reductions.push({ phase: "elision", saved: saved2 });
2913
3463
  }
3464
+ const repaired = repairToolUseAdjacency(ctx.messages);
3465
+ if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
2914
3466
  const afterTokens = this.estimateTokens(ctx.messages);
2915
- 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
+ };
2916
3477
  }
2917
3478
  async summarizeAncientTurns(ctx) {
2918
3479
  const messages = ctx.messages;
@@ -2952,8 +3513,8 @@ var IntelligentCompactor = class {
2952
3513
  const m = messages[i];
2953
3514
  if (!m) continue;
2954
3515
  if (m.role === "assistant") {
2955
- const hasToolUse = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
2956
- if (!hasToolUse) {
3516
+ const hasToolUse2 = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
3517
+ if (!hasToolUse2) {
2957
3518
  return i + 1;
2958
3519
  }
2959
3520
  } else if (m.role !== "user") ; else {
@@ -3259,8 +3820,9 @@ var SelectiveCompactor = class {
3259
3820
  if (!shouldCompact) {
3260
3821
  const saved = this.eliseOldToolResults(ctx);
3261
3822
  if (saved > 0) reductions.push({ phase: "elision", saved });
3823
+ const repair2 = this.repairProtocolAdjacency(ctx);
3262
3824
  const afterTokens2 = this.estimateTokens(ctx.messages);
3263
- return { before: beforeTokens, after: afterTokens2, reductions };
3825
+ return { before: beforeTokens, after: afterTokens2, reductions, repaired: repair2 };
3264
3826
  }
3265
3827
  const savedElision = this.eliseOldToolResults(ctx);
3266
3828
  if (savedElision > 0) reductions.push({ phase: "elision", saved: savedElision });
@@ -3270,8 +3832,18 @@ var SelectiveCompactor = class {
3270
3832
  const savedSelective = await this.runSelector(ctx, targetBudget);
3271
3833
  if (savedSelective > 0) reductions.push({ phase: "selective", saved: savedSelective });
3272
3834
  }
3835
+ const repair = this.repairProtocolAdjacency(ctx);
3273
3836
  const afterTokens = this.estimateTokens(ctx.messages);
3274
- 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;
3275
3847
  }
3276
3848
  /**
3277
3849
  * Run the LLM selector to decide what to keep vs collapse.
@@ -3459,6 +4031,7 @@ var AutoCompactionMiddleware = class {
3459
4031
  aggressiveOn;
3460
4032
  events;
3461
4033
  failureMode;
4034
+ policyProvider;
3462
4035
  /**
3463
4036
  * @param compactor Compactor to use for compaction.
3464
4037
  * @param maxContext Provider's max context window in tokens.
@@ -3481,17 +4054,25 @@ var AutoCompactionMiddleware = class {
3481
4054
  this.aggressiveOn = opts.aggressiveOn ?? "soft";
3482
4055
  this.events = opts.events;
3483
4056
  this.failureMode = opts.failureMode ?? "throw_on_hard";
4057
+ this.policyProvider = opts.policyProvider;
3484
4058
  }
3485
4059
  handler() {
3486
4060
  return async (ctx, next) => {
3487
4061
  const tokens = this.estimator(ctx);
3488
4062
  const load = tokens / this.maxContext;
3489
- 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) {
3490
4071
  await this.compact(ctx, true, { level: "hard", tokens, load });
3491
- } else if (load >= this.softThreshold) {
3492
- await this.compact(ctx, this.aggressiveOn !== "hard", { level: "soft", tokens, load });
3493
- } else if (load >= this.warnThreshold) {
3494
- 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 });
3495
4076
  }
3496
4077
  return next(ctx);
3497
4078
  };
@@ -4094,6 +4675,7 @@ var InMemoryAgentBridge = class {
4094
4675
  this.stopped = true;
4095
4676
  for (const [, p] of this.pendingRequests) {
4096
4677
  clearTimeout(p.timer);
4678
+ p.reject(new Error("Bridge stopped"));
4097
4679
  }
4098
4680
  this.pendingRequests.clear();
4099
4681
  this.inflightGuards.clear();
@@ -4867,41 +5449,214 @@ function classifySubagentError(err, hints = {}) {
4867
5449
  return { kind: "context_overflow", message: baseMessage, retryable: false, cause };
4868
5450
  }
4869
5451
  return {
4870
- kind: "unknown",
4871
- message: baseMessage,
4872
- retryable: false,
4873
- 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
+ }
4874
5647
  };
4875
5648
  }
4876
- function providerErrorToSubagentError(err, message, cause) {
4877
- const status = err.status;
4878
- if (status === 429 || err.body?.type === "rate_limit_error") {
4879
- return {
4880
- kind: "provider_rate_limit",
4881
- message,
4882
- retryable: true,
4883
- // Conservative default: 5s. Provider-specific code can override
4884
- // by emitting an error whose body carries an explicit hint.
4885
- backoffMs: 5e3,
4886
- cause
4887
- };
4888
- }
4889
- if (status === 401 || status === 403 || err.body?.type === "authentication_error") {
4890
- return { kind: "provider_auth", message, retryable: false, cause };
4891
- }
4892
- if (status === 408 || status === 0) {
4893
- return { kind: "provider_timeout", message, retryable: true, cause };
4894
- }
4895
- if (status >= 500 && status < 600) {
4896
- return {
4897
- kind: "provider_5xx",
4898
- message,
4899
- retryable: true,
4900
- backoffMs: 3e3,
4901
- cause
4902
- };
4903
- }
4904
- 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
+ };
4905
5660
  }
4906
5661
 
4907
5662
  // src/coordination/director.ts
@@ -5461,242 +6216,6 @@ var Director = class {
5461
6216
  return t;
5462
6217
  }
5463
6218
  };
5464
- function makeSpawnTool(director, roster) {
5465
- const inputSchema = {
5466
- type: "object",
5467
- properties: {
5468
- role: {
5469
- type: "string",
5470
- description: "Roster role id (preferred). When set, the spawn uses the matching config from the roster and ignores other fields."
5471
- },
5472
- name: {
5473
- type: "string",
5474
- description: "Display name for the subagent. Required when not using roster."
5475
- },
5476
- provider: {
5477
- type: "string",
5478
- description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.'
5479
- },
5480
- model: {
5481
- type: "string",
5482
- description: "Model id within the provider. Defaults to the leader model when omitted."
5483
- },
5484
- systemPromptOverride: {
5485
- type: "string",
5486
- description: "Extra prompt text appended after the role-base prompt."
5487
- },
5488
- maxIterations: { type: "number" },
5489
- maxToolCalls: { type: "number" },
5490
- maxCostUsd: { type: "number" }
5491
- },
5492
- required: []
5493
- };
5494
- return {
5495
- name: "spawn_subagent",
5496
- 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.",
5497
- usageHint: "Either pass `role` (matches the roster) OR pass `name` + optional `provider`/`model`. Returns `{ subagentId }`.",
5498
- permission: "auto",
5499
- mutating: false,
5500
- inputSchema,
5501
- async execute(input) {
5502
- const i = input ?? {};
5503
- const role = typeof i.role === "string" ? i.role : void 0;
5504
- const base = role && roster ? roster[role] : void 0;
5505
- if (role && !base) {
5506
- return {
5507
- error: `unknown role "${role}". roster has: ${roster ? Object.keys(roster).join(", ") : "(empty)"}`
5508
- };
5509
- }
5510
- const cfg = {
5511
- ...base ?? { name: i.name ?? "subagent" }
5512
- };
5513
- if (typeof i.name === "string") cfg.name = i.name;
5514
- if (typeof i.provider === "string") cfg.provider = i.provider;
5515
- if (typeof i.model === "string") cfg.model = i.model;
5516
- if (typeof i.systemPromptOverride === "string")
5517
- cfg.systemPromptOverride = i.systemPromptOverride;
5518
- if (typeof i.maxIterations === "number") cfg.maxIterations = i.maxIterations;
5519
- if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
5520
- if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
5521
- try {
5522
- const subagentId = await director.spawn(cfg);
5523
- return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name };
5524
- } catch (err) {
5525
- if (err instanceof DirectorBudgetError) {
5526
- return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
5527
- }
5528
- return { error: err instanceof Error ? err.message : String(err) };
5529
- }
5530
- }
5531
- };
5532
- }
5533
- function makeAssignTool(director) {
5534
- const inputSchema = {
5535
- type: "object",
5536
- properties: {
5537
- subagentId: { type: "string", description: "Target subagent id. Required." },
5538
- description: {
5539
- type: "string",
5540
- description: "The task in natural language \u2014 what you want this subagent to do."
5541
- },
5542
- maxToolCalls: { type: "number", description: "Optional per-task tool-call budget override." },
5543
- timeoutMs: { type: "number", description: "Optional per-task timeout in ms." }
5544
- },
5545
- required: ["subagentId", "description"]
5546
- };
5547
- return {
5548
- name: "assign_task",
5549
- description: "Hand a task to a previously spawned subagent. Returns the task id \u2014 pass it to `await_tasks` to block on completion.",
5550
- permission: "auto",
5551
- mutating: false,
5552
- inputSchema,
5553
- async execute(input) {
5554
- const i = input;
5555
- const task = {
5556
- id: randomUUID(),
5557
- description: i.description,
5558
- subagentId: i.subagentId,
5559
- maxToolCalls: i.maxToolCalls,
5560
- timeoutMs: i.timeoutMs
5561
- };
5562
- const taskId = await director.assign(task);
5563
- return { taskId, subagentId: i.subagentId };
5564
- }
5565
- };
5566
- }
5567
- function makeAwaitTasksTool(director) {
5568
- const inputSchema = {
5569
- type: "object",
5570
- properties: {
5571
- taskIds: {
5572
- type: "array",
5573
- items: { type: "string" },
5574
- description: "One or more task ids returned by `assign_task`. The call blocks until every id resolves."
5575
- }
5576
- },
5577
- required: ["taskIds"]
5578
- };
5579
- return {
5580
- name: "await_tasks",
5581
- description: "Block until every named task completes. Returns the array of TaskResult \u2014 use this to gather subagent output before deciding the next step.",
5582
- permission: "auto",
5583
- mutating: false,
5584
- inputSchema,
5585
- async execute(input) {
5586
- const i = input;
5587
- const results = await director.awaitTasks(i.taskIds);
5588
- return { results };
5589
- }
5590
- };
5591
- }
5592
- function makeAskTool(director) {
5593
- const inputSchema = {
5594
- type: "object",
5595
- properties: {
5596
- subagentId: {
5597
- type: "string",
5598
- description: "Subagent to ask. Must be a previously spawned id."
5599
- },
5600
- question: {
5601
- type: "string",
5602
- description: "The question or instruction. Sent as the bridge message payload."
5603
- },
5604
- timeoutMs: { type: "number", description: "Optional timeout in ms (default 30s)." }
5605
- },
5606
- required: ["subagentId", "question"]
5607
- };
5608
- return {
5609
- name: "ask_subagent",
5610
- 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.",
5611
- permission: "auto",
5612
- mutating: false,
5613
- inputSchema,
5614
- async execute(input) {
5615
- const i = input;
5616
- try {
5617
- const answer = await director.ask(i.subagentId, { question: i.question }, i.timeoutMs);
5618
- return { ok: true, answer };
5619
- } catch (err) {
5620
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
5621
- }
5622
- }
5623
- };
5624
- }
5625
- function makeRollUpTool(director) {
5626
- const inputSchema = {
5627
- type: "object",
5628
- properties: {
5629
- taskIds: {
5630
- type: "array",
5631
- items: { type: "string" },
5632
- description: "Completed task ids to aggregate. Pass the ids returned by previous `assign_task` calls."
5633
- },
5634
- style: {
5635
- type: "string",
5636
- enum: ["markdown", "json"],
5637
- description: "Output flavor \u2014 markdown (default) for in-prompt summarization, json for structured downstream processing."
5638
- }
5639
- },
5640
- required: ["taskIds"]
5641
- };
5642
- return {
5643
- name: "roll_up",
5644
- 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.",
5645
- permission: "auto",
5646
- mutating: false,
5647
- inputSchema,
5648
- async execute(input) {
5649
- const i = input;
5650
- const summary = director.rollUp(i.taskIds, i.style ?? "markdown");
5651
- return { summary, count: i.taskIds.length };
5652
- }
5653
- };
5654
- }
5655
- function makeTerminateTool(director) {
5656
- const inputSchema = {
5657
- type: "object",
5658
- properties: {
5659
- subagentId: { type: "string", description: "Subagent to abort." }
5660
- },
5661
- required: ["subagentId"]
5662
- };
5663
- return {
5664
- name: "terminate_subagent",
5665
- 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".',
5666
- permission: "auto",
5667
- mutating: true,
5668
- inputSchema,
5669
- async execute(input) {
5670
- const i = input;
5671
- await director.terminate(i.subagentId);
5672
- return { ok: true };
5673
- }
5674
- };
5675
- }
5676
- function makeFleetStatusTool(director) {
5677
- return {
5678
- name: "fleet_status",
5679
- 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.",
5680
- permission: "auto",
5681
- mutating: false,
5682
- inputSchema: { type: "object", properties: {}, required: [] },
5683
- async execute() {
5684
- return director.status();
5685
- }
5686
- };
5687
- }
5688
- function makeFleetUsageTool(director) {
5689
- return {
5690
- name: "fleet_usage",
5691
- 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.",
5692
- permission: "auto",
5693
- mutating: false,
5694
- inputSchema: { type: "object", properties: {}, required: [] },
5695
- async execute() {
5696
- return director.snapshot();
5697
- }
5698
- };
5699
- }
5700
6219
  function createDelegateTool(opts) {
5701
6220
  const defaultTimeoutMs = opts.defaultTimeoutMs ?? 4 * 60 * 60 * 1e3;
5702
6221
  const rosterIds = opts.roster ? Object.keys(opts.roster) : [];
@@ -8234,13 +8753,13 @@ function roughEstimate(messages) {
8234
8753
  function createContextManagerTool(opts = {}) {
8235
8754
  return {
8236
8755
  name: CONTEXT_MANAGER_TOOL_NAME,
8237
- 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.',
8238
8757
  inputSchema: {
8239
8758
  type: "object",
8240
8759
  properties: {
8241
8760
  action: {
8242
8761
  type: "string",
8243
- enum: ["check", "summary", "prune", "add_note", "compact"],
8762
+ enum: ["check", "summary", "prune", "add_note", "compact", "repair"],
8244
8763
  description: "The context operation to perform."
8245
8764
  },
8246
8765
  from: {
@@ -8268,12 +8787,15 @@ function createContextManagerTool(opts = {}) {
8268
8787
  const messages = ctx.messages;
8269
8788
  const beforeTokens = roughEstimate(messages);
8270
8789
  const applyMessages = (next) => {
8790
+ const repaired = repairToolUseAdjacency(next);
8791
+ const finalMessages = repaired.messages;
8271
8792
  if (ctx.state) {
8272
- ctx.state.replaceMessages(next);
8793
+ ctx.state.replaceMessages(finalMessages);
8273
8794
  } else {
8274
8795
  messages.length = 0;
8275
- messages.splice(0, 0, ...next);
8796
+ messages.splice(0, 0, ...finalMessages);
8276
8797
  }
8798
+ return repaired.report;
8277
8799
  };
8278
8800
  switch (input.action) {
8279
8801
  case "check": {
@@ -8290,6 +8812,22 @@ function createContextManagerTool(opts = {}) {
8290
8812
  })
8291
8813
  };
8292
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
+ }
8293
8831
  case "compact": {
8294
8832
  if (!opts.compactor) {
8295
8833
  return {
@@ -8300,11 +8838,19 @@ function createContextManagerTool(opts = {}) {
8300
8838
  };
8301
8839
  }
8302
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);
8303
8844
  return {
8304
8845
  action: "compact",
8305
8846
  beforeTokens,
8306
- afterTokens: report.after,
8307
- 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
8308
8854
  };
8309
8855
  }
8310
8856
  case "prune": {
@@ -8320,14 +8866,19 @@ function createContextManagerTool(opts = {}) {
8320
8866
  }
8321
8867
  const copy = [...messages];
8322
8868
  const removed = copy.splice(from, to - from + 1);
8323
- applyMessages(copy);
8324
- const afterTokens = roughEstimate(copy);
8869
+ const repair = applyMessages(copy);
8870
+ const afterTokens = roughEstimate(ctx.messages);
8325
8871
  return {
8326
8872
  action: "prune",
8327
8873
  beforeTokens,
8328
8874
  afterTokens,
8329
- messageCount: copy.length,
8330
- 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
8331
8882
  };
8332
8883
  }
8333
8884
  case "add_note": {
@@ -8339,14 +8890,19 @@ function createContextManagerTool(opts = {}) {
8339
8890
  };
8340
8891
  const copy = [...messages];
8341
8892
  copy.splice(afterIdx, 0, noteMsg);
8342
- applyMessages(copy);
8343
- const afterTokens = roughEstimate(copy);
8893
+ const repair = applyMessages(copy);
8894
+ const afterTokens = roughEstimate(ctx.messages);
8344
8895
  return {
8345
8896
  action: "add_note",
8346
8897
  beforeTokens,
8347
8898
  afterTokens,
8348
- messageCount: copy.length,
8349
- 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
8350
8906
  };
8351
8907
  }
8352
8908
  case "summary": {
@@ -8367,14 +8923,19 @@ function createContextManagerTool(opts = {}) {
8367
8923
  };
8368
8924
  const copy = [...messages];
8369
8925
  copy.splice(from, to - from + 1, summaryMsg);
8370
- applyMessages(copy);
8371
- const afterTokens = roughEstimate(copy);
8926
+ const repair = applyMessages(copy);
8927
+ const afterTokens = roughEstimate(ctx.messages);
8372
8928
  return {
8373
8929
  action: "summary",
8374
8930
  beforeTokens,
8375
8931
  afterTokens,
8376
- messageCount: copy.length,
8377
- 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
8378
8939
  };
8379
8940
  }
8380
8941
  default:
@@ -8478,6 +9039,41 @@ var sentinelServer = () => ({
8478
9039
  permission: "deny"
8479
9040
  // security tool — require explicit confirmation
8480
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
+ });
8481
9077
  var allServers = () => ({
8482
9078
  filesystem: { ...filesystemServer(), enabled: false },
8483
9079
  github: { ...githubServer(), enabled: false },
@@ -8488,9 +9084,11 @@ var allServers = () => ({
8488
9084
  slack: { ...slackServer(), enabled: false },
8489
9085
  aws: { ...awsServer(), enabled: false },
8490
9086
  "google-maps": { ...googleMapsServer(), enabled: false },
8491
- sentinel: { ...sentinelServer(), enabled: false }
9087
+ sentinel: { ...sentinelServer(), enabled: false },
9088
+ "zai-vision": { ...zaiVisionServer(), enabled: false },
9089
+ "minimax-vision": { ...miniMaxVisionServer(), enabled: false }
8492
9090
  });
8493
9091
 
8494
- 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, 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 };
8495
9093
  //# sourceMappingURL=index.js.map
8496
9094
  //# sourceMappingURL=index.js.map