@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
package/dist/index.js CHANGED
@@ -1166,6 +1166,98 @@ function estimateTextTokens(text) {
1166
1166
  return RoughTokenEstimate(text);
1167
1167
  }
1168
1168
 
1169
+ // src/utils/message-invariants.ts
1170
+ function repairToolUseAdjacency(messages) {
1171
+ const removedToolUses = [];
1172
+ const removedToolResults = [];
1173
+ let removedMessages = 0;
1174
+ let changed = false;
1175
+ const out = [];
1176
+ for (let i = 0; i < messages.length; i++) {
1177
+ const original = messages[i];
1178
+ let msg = original;
1179
+ if (hasToolUse(msg)) {
1180
+ const nextIds = toolResultIds(messages[i + 1]);
1181
+ const filtered = mapContent(msg, (blocks) => {
1182
+ const next = [];
1183
+ for (const block of blocks) {
1184
+ if (block.type === "tool_use" && !nextIds.has(block.id)) {
1185
+ removedToolUses.push(block.id);
1186
+ changed = true;
1187
+ continue;
1188
+ }
1189
+ next.push(block);
1190
+ }
1191
+ return next;
1192
+ });
1193
+ msg = filtered ?? msg;
1194
+ }
1195
+ if (hasToolResult(msg)) {
1196
+ const allowed = toolUseIds(out[out.length - 1]);
1197
+ const filtered = mapContent(msg, (blocks) => {
1198
+ const next = [];
1199
+ for (const block of blocks) {
1200
+ if (block.type === "tool_result" && !allowed.has(block.tool_use_id)) {
1201
+ removedToolResults.push(block.tool_use_id);
1202
+ changed = true;
1203
+ continue;
1204
+ }
1205
+ next.push(block);
1206
+ }
1207
+ return next;
1208
+ });
1209
+ msg = filtered ?? msg;
1210
+ }
1211
+ if (isEmptyMessage(msg)) {
1212
+ removedMessages++;
1213
+ changed = true;
1214
+ continue;
1215
+ }
1216
+ out.push(msg);
1217
+ }
1218
+ return {
1219
+ messages: changed ? out : messages,
1220
+ report: { changed, removedToolUses, removedToolResults, removedMessages }
1221
+ };
1222
+ }
1223
+ function hasToolUse(msg) {
1224
+ return contentBlocks(msg).some((b) => b.type === "tool_use");
1225
+ }
1226
+ function hasToolResult(msg) {
1227
+ return contentBlocks(msg).some((b) => b.type === "tool_result");
1228
+ }
1229
+ function toolUseIds(msg) {
1230
+ const ids = /* @__PURE__ */ new Set();
1231
+ if (!msg || msg.role !== "assistant") return ids;
1232
+ for (const block of contentBlocks(msg)) {
1233
+ if (block.type === "tool_use") ids.add(block.id);
1234
+ }
1235
+ return ids;
1236
+ }
1237
+ function toolResultIds(msg) {
1238
+ const ids = /* @__PURE__ */ new Set();
1239
+ if (!msg || msg.role !== "user") return ids;
1240
+ for (const block of contentBlocks(msg)) {
1241
+ if (block.type === "tool_result") ids.add(block.tool_use_id);
1242
+ }
1243
+ return ids;
1244
+ }
1245
+ function contentBlocks(msg) {
1246
+ return msg && Array.isArray(msg.content) ? msg.content : [];
1247
+ }
1248
+ function mapContent(msg, fn) {
1249
+ if (!Array.isArray(msg.content)) return msg;
1250
+ const next = fn(msg.content);
1251
+ if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {
1252
+ return msg;
1253
+ }
1254
+ return { ...msg, content: next };
1255
+ }
1256
+ function isEmptyMessage(msg) {
1257
+ if (typeof msg.content === "string") return msg.content.trim().length === 0;
1258
+ return msg.content.length === 0;
1259
+ }
1260
+
1169
1261
  // src/execution/compactor.ts
1170
1262
  var HybridCompactor = class {
1171
1263
  preserveK;
@@ -1179,20 +1271,36 @@ var HybridCompactor = class {
1179
1271
  async compact(ctx, opts = {}) {
1180
1272
  const beforeTokens = this.estimateMessages(ctx.messages);
1181
1273
  const reductions = [];
1182
- const phase1Saved = this.eliseOldToolResults(ctx);
1274
+ const policy = readContextWindowPolicy(ctx);
1275
+ const preserveK = policy?.preserveK ?? this.preserveK;
1276
+ const eliseThreshold = policy?.eliseThreshold ?? this.eliseThreshold;
1277
+ const phase1Saved = this.eliseOldToolResults(ctx, preserveK, eliseThreshold);
1183
1278
  if (phase1Saved > 0) reductions.push({ phase: "elision", saved: phase1Saved });
1184
1279
  if (opts.aggressive) {
1185
- const phase2Saved = this.collapseAncientTurns(ctx);
1280
+ const phase2Saved = this.collapseAncientTurns(ctx, preserveK);
1186
1281
  if (phase2Saved > 0) reductions.push({ phase: "summary", saved: phase2Saved });
1187
1282
  }
1283
+ const repaired = repairToolUseAdjacency(ctx.messages);
1284
+ if (repaired.report.changed) {
1285
+ ctx.state.replaceMessages(repaired.messages);
1286
+ }
1188
1287
  const afterTokens = this.estimateMessages(ctx.messages);
1189
- return { before: beforeTokens, after: afterTokens, reductions };
1288
+ return {
1289
+ before: beforeTokens,
1290
+ after: afterTokens,
1291
+ reductions,
1292
+ repaired: repaired.report.changed ? {
1293
+ removedToolUses: repaired.report.removedToolUses,
1294
+ removedToolResults: repaired.report.removedToolResults,
1295
+ removedMessages: repaired.report.removedMessages
1296
+ } : void 0
1297
+ };
1190
1298
  }
1191
- eliseOldToolResults(ctx) {
1299
+ eliseOldToolResults(ctx, preserveK = this.preserveK, eliseThreshold = this.eliseThreshold) {
1192
1300
  const messages = ctx.messages;
1193
1301
  let pairCount = 0;
1194
1302
  let preserveStart = messages.length;
1195
- for (let i = messages.length - 1; i >= 0 && pairCount < this.preserveK; i--) {
1303
+ for (let i = messages.length - 1; i >= 0 && pairCount < preserveK; i--) {
1196
1304
  const m = messages[i];
1197
1305
  if (!m) continue;
1198
1306
  if (m.role === "user" || m.role === "assistant") {
@@ -1216,7 +1324,7 @@ var HybridCompactor = class {
1216
1324
  const newContent = msg.content.map((b) => {
1217
1325
  if (b.type !== "tool_result") return b;
1218
1326
  const tokens = estimateToolResultTokens(b.content);
1219
- if (tokens < this.eliseThreshold) return b;
1327
+ if (tokens < eliseThreshold) return b;
1220
1328
  saved += tokens;
1221
1329
  const elided = {
1222
1330
  type: "tool_result",
@@ -1236,9 +1344,9 @@ var HybridCompactor = class {
1236
1344
  if (changed) ctx.state.replaceMessages(nextMessages);
1237
1345
  return saved;
1238
1346
  }
1239
- collapseAncientTurns(ctx) {
1347
+ collapseAncientTurns(ctx, preserveK = this.preserveK) {
1240
1348
  const messages = ctx.messages;
1241
- const cutTarget = Math.max(0, messages.length - this.preserveK * 2);
1349
+ const cutTarget = Math.max(0, messages.length - preserveK * 2);
1242
1350
  if (cutTarget <= 0) return 0;
1243
1351
  let boundary = -1;
1244
1352
  for (let i = cutTarget; i < messages.length; i++) {
@@ -1279,6 +1387,15 @@ var HybridCompactor = class {
1279
1387
  return total;
1280
1388
  }
1281
1389
  };
1390
+ function readContextWindowPolicy(ctx) {
1391
+ const policy = ctx.meta?.["contextWindowPolicy"];
1392
+ if (!policy || typeof policy !== "object") return null;
1393
+ const candidate = policy;
1394
+ if (typeof candidate.preserveK !== "number" || typeof candidate.eliseThreshold !== "number") {
1395
+ return null;
1396
+ }
1397
+ return candidate;
1398
+ }
1282
1399
  function hasTextContent(m) {
1283
1400
  if (typeof m.content === "string") return m.content.trim().length > 0;
1284
1401
  return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
@@ -1944,6 +2061,85 @@ When refactoring code:
1944
2061
  }
1945
2062
  ];
1946
2063
 
2064
+ // src/types/context-window.ts
2065
+ var DEFAULT_CONTEXT_WINDOW_MODE_ID = "balanced";
2066
+ var CONTEXT_WINDOW_MODES = Object.freeze([
2067
+ {
2068
+ id: "balanced",
2069
+ name: "Balanced",
2070
+ description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed.",
2071
+ thresholds: { warn: 0.6, soft: 0.75, hard: 0.9 },
2072
+ aggressiveOn: "soft",
2073
+ preserveK: 10,
2074
+ eliseThreshold: 2e3,
2075
+ targetLoad: 0.65
2076
+ },
2077
+ {
2078
+ id: "frugal",
2079
+ name: "Frugal",
2080
+ description: "Token-saver mode: compacts early and keeps a tighter verbatim tail.",
2081
+ thresholds: { warn: 0.45, soft: 0.6, hard: 0.75 },
2082
+ aggressiveOn: "warn",
2083
+ preserveK: 6,
2084
+ eliseThreshold: 700,
2085
+ targetLoad: 0.5
2086
+ },
2087
+ {
2088
+ id: "deep",
2089
+ name: "Deep",
2090
+ description: "Long-reasoning mode: delays compaction and keeps more recent turns intact.",
2091
+ thresholds: { warn: 0.72, soft: 0.86, hard: 0.96 },
2092
+ aggressiveOn: "hard",
2093
+ preserveK: 18,
2094
+ eliseThreshold: 5e3,
2095
+ targetLoad: 0.78
2096
+ },
2097
+ {
2098
+ id: "archival",
2099
+ name: "Archival",
2100
+ description: "Decision-preserving mode: compacts steadily while keeping summaries prominent.",
2101
+ thresholds: { warn: 0.55, soft: 0.7, hard: 0.84 },
2102
+ aggressiveOn: "soft",
2103
+ preserveK: 8,
2104
+ eliseThreshold: 1200,
2105
+ targetLoad: 0.58
2106
+ }
2107
+ ]);
2108
+ function listContextWindowModes() {
2109
+ return CONTEXT_WINDOW_MODES.map((m) => ({ ...m, thresholds: { ...m.thresholds } }));
2110
+ }
2111
+ function getContextWindowMode(id) {
2112
+ if (!id) return null;
2113
+ const mode = CONTEXT_WINDOW_MODES.find((m) => m.id === id);
2114
+ return mode ? { ...mode, thresholds: { ...mode.thresholds } } : null;
2115
+ }
2116
+ function isContextWindowModeId(id) {
2117
+ return CONTEXT_WINDOW_MODES.some((m) => m.id === id);
2118
+ }
2119
+ function resolveContextWindowPolicy(config = {}, overrideMode) {
2120
+ const requested = overrideMode ?? config.mode ?? DEFAULT_CONTEXT_WINDOW_MODE_ID;
2121
+ const mode = getContextWindowMode(requested) ?? getContextWindowMode(DEFAULT_CONTEXT_WINDOW_MODE_ID);
2122
+ if (mode.id !== DEFAULT_CONTEXT_WINDOW_MODE_ID) {
2123
+ return mode;
2124
+ }
2125
+ return {
2126
+ ...mode,
2127
+ thresholds: {
2128
+ warn: config.warnThreshold ?? mode.thresholds.warn,
2129
+ soft: config.softThreshold ?? mode.thresholds.soft,
2130
+ hard: config.hardThreshold ?? mode.thresholds.hard
2131
+ },
2132
+ preserveK: config.preserveK ?? mode.preserveK,
2133
+ eliseThreshold: config.eliseThreshold ?? mode.eliseThreshold
2134
+ };
2135
+ }
2136
+ function formatContextWindowModeList(activeId) {
2137
+ return CONTEXT_WINDOW_MODES.map((m) => {
2138
+ const marker = m.id === activeId ? "*" : " ";
2139
+ return `${marker} ${m.id.padEnd(9)} ${m.name} - ${m.description}`;
2140
+ }).join("\n");
2141
+ }
2142
+
1947
2143
  // src/coordination/in-memory-transport.ts
1948
2144
  var InMemoryBridgeTransport = class {
1949
2145
  subs = /* @__PURE__ */ new Map();
@@ -2063,6 +2259,7 @@ var InMemoryAgentBridge = class {
2063
2259
  this.stopped = true;
2064
2260
  for (const [, p] of this.pendingRequests) {
2065
2261
  clearTimeout(p.timer);
2262
+ p.reject(new Error("Bridge stopped"));
2066
2263
  }
2067
2264
  this.pendingRequests.clear();
2068
2265
  this.inflightGuards.clear();
@@ -3493,11 +3690,17 @@ var DefaultSessionStore = class {
3493
3690
  if (openToolUses.size > 0) {
3494
3691
  this.events?.emit("session.damaged", {
3495
3692
  sessionId,
3496
- detail: `${openToolUses.size} tool_use blocks without matching results \u2014 replay truncated`
3693
+ detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
3694
+ });
3695
+ }
3696
+ const repaired = repairToolUseAdjacency(messages);
3697
+ if (repaired.report.changed) {
3698
+ this.events?.emit("session.damaged", {
3699
+ sessionId,
3700
+ detail: `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ${repaired.report.removedToolResults.length} tool_result, ${repaired.report.removedMessages} empty messages`
3497
3701
  });
3498
- return { messages, usage };
3499
3702
  }
3500
- return { messages, usage };
3703
+ return { messages: repaired.messages, usage };
3501
3704
  }
3502
3705
  };
3503
3706
  var FileSessionWriter = class {
@@ -3523,6 +3726,7 @@ var FileSessionWriter = class {
3523
3726
  startedAt;
3524
3727
  meta;
3525
3728
  closed = false;
3729
+ closing = false;
3526
3730
  manifestFile;
3527
3731
  summary;
3528
3732
  tokenIn = 0;
@@ -3538,9 +3742,7 @@ var FileSessionWriter = class {
3538
3742
  resumed;
3539
3743
  appendFailCount = 0;
3540
3744
  lastAppendWarnAt = 0;
3541
- async writeSessionStart() {
3542
- if (this.initDone || this.closed) return;
3543
- this.initDone = true;
3745
+ async writeSessionStartLazy() {
3544
3746
  const record = `${JSON.stringify({
3545
3747
  type: this.resumed ? "session_resumed" : "session_start",
3546
3748
  ts: this.startedAt,
@@ -3559,7 +3761,8 @@ var FileSessionWriter = class {
3559
3761
  async append(event) {
3560
3762
  if (this.closed) return;
3561
3763
  if (!this.initDone) {
3562
- await this.writeSessionStart();
3764
+ this.initDone = true;
3765
+ await this.writeSessionStartLazy();
3563
3766
  }
3564
3767
  this.observeForSummary(event);
3565
3768
  try {
@@ -3600,7 +3803,8 @@ var FileSessionWriter = class {
3600
3803
  }
3601
3804
  }
3602
3805
  async close() {
3603
- if (this.closed) return;
3806
+ if (this.closing) return;
3807
+ this.closing = true;
3604
3808
  this.closed = true;
3605
3809
  if (this.manifestFile) {
3606
3810
  try {
@@ -4002,9 +4206,52 @@ function deepFreeze(obj) {
4002
4206
  }
4003
4207
  return Object.freeze(obj);
4004
4208
  }
4209
+
4210
+ // src/security/config-secrets.ts
4211
+ function decryptConfigSecrets2(cfg, vault) {
4212
+ return walk3(cfg, vault, (v, key) => {
4213
+ try {
4214
+ return vault.decrypt(v);
4215
+ } catch (err) {
4216
+ console.warn(
4217
+ `[secret-vault] Failed to decrypt "${key}":`,
4218
+ err instanceof Error ? err.message : err
4219
+ );
4220
+ return "";
4221
+ }
4222
+ });
4223
+ }
4224
+ function walk3(node, vault, transform) {
4225
+ if (node === null || node === void 0) return node;
4226
+ if (typeof node !== "object") return node;
4227
+ if (Array.isArray(node)) {
4228
+ return node.map((item) => walk3(item, vault, transform));
4229
+ }
4230
+ const out = {};
4231
+ for (const [k, v] of Object.entries(node)) {
4232
+ if (typeof v === "string" && isSecretField2(k)) {
4233
+ out[k] = transform(v, k);
4234
+ } else if (typeof v === "object" && v !== null) {
4235
+ out[k] = walk3(v, vault, transform);
4236
+ } else {
4237
+ out[k] = v;
4238
+ }
4239
+ }
4240
+ return out;
4241
+ }
4242
+ 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;
4243
+ var NON_SECRET_OVERRIDES2 = /* @__PURE__ */ new Set(["publickey", "public_key"]);
4244
+ function isSecretField2(name) {
4245
+ const lc = name.toLowerCase();
4246
+ if (NON_SECRET_OVERRIDES2.has(lc)) return false;
4247
+ return SECRET_KEY_PATTERN2.test(lc);
4248
+ }
4249
+
4250
+ // src/storage/config-loader.ts
4005
4251
  var BEHAVIOR_DEFAULTS = {
4006
4252
  version: 1,
4007
4253
  context: {
4254
+ mode: DEFAULT_CONTEXT_WINDOW_MODE_ID,
4008
4255
  warnThreshold: 0.6,
4009
4256
  softThreshold: 0.75,
4010
4257
  hardThreshold: 0.9,
@@ -4119,7 +4366,7 @@ var DefaultConfigLoader = class {
4119
4366
  cfg = deepMerge2(cfg, opts.cliFlags);
4120
4367
  }
4121
4368
  if (this.vault) {
4122
- cfg = decryptConfigSecrets(cfg, this.vault);
4369
+ cfg = decryptConfigSecrets2(cfg, this.vault);
4123
4370
  }
4124
4371
  if (cfg.providers) {
4125
4372
  for (const pcfg of Object.values(cfg.providers)) {
@@ -4179,6 +4426,10 @@ var DefaultConfigLoader = class {
4179
4426
  if (c.warnThreshold >= c.softThreshold || c.softThreshold >= c.hardThreshold) {
4180
4427
  throw new Error("Config: context thresholds must satisfy warn < soft < hard");
4181
4428
  }
4429
+ if (c.mode !== void 0 && !isContextWindowModeId(c.mode)) {
4430
+ const known = listContextWindowModes().map((m) => m.id).join(", ");
4431
+ throw new Error(`Config: context.mode must be one of: ${known}`);
4432
+ }
4182
4433
  }
4183
4434
  validateIdentity(cfg) {
4184
4435
  if (!cfg.provider) {
@@ -4499,24 +4750,35 @@ async function saveTodosCheckpoint(filePath, sessionId, todos) {
4499
4750
  function attachTodosCheckpoint(state, filePath, sessionId) {
4500
4751
  let timer = null;
4501
4752
  let pending = null;
4753
+ let writeChain = Promise.resolve();
4754
+ const enqueueWrite = (todos) => {
4755
+ writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos));
4756
+ return writeChain;
4757
+ };
4502
4758
  const flush = () => {
4503
4759
  timer = null;
4504
4760
  if (pending) {
4505
- void saveTodosCheckpoint(filePath, sessionId, pending);
4761
+ const todos = pending;
4506
4762
  pending = null;
4763
+ return enqueueWrite(todos);
4507
4764
  }
4765
+ return writeChain;
4508
4766
  };
4509
4767
  const unsubscribe = state.onChange((change) => {
4510
4768
  if (change.kind !== "todos_replaced") return;
4511
4769
  pending = change.todos;
4512
4770
  if (timer) clearTimeout(timer);
4513
- timer = setTimeout(flush, 150);
4771
+ timer = setTimeout(() => {
4772
+ void flush();
4773
+ }, 150);
4514
4774
  });
4515
- return () => {
4775
+ return async () => {
4516
4776
  unsubscribe();
4517
4777
  if (timer) {
4518
4778
  clearTimeout(timer);
4519
- flush();
4779
+ await flush();
4780
+ } else {
4781
+ await writeChain;
4520
4782
  }
4521
4783
  };
4522
4784
  }
@@ -5310,8 +5572,19 @@ var IntelligentCompactor = class {
5310
5572
  const saved2 = this.lightweightCompact(ctx);
5311
5573
  if (saved2 > 0) reductions.push({ phase: "elision", saved: saved2 });
5312
5574
  }
5575
+ const repaired = repairToolUseAdjacency(ctx.messages);
5576
+ if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
5313
5577
  const afterTokens = this.estimateTokens(ctx.messages);
5314
- return { before: beforeTokens, after: afterTokens, reductions };
5578
+ return {
5579
+ before: beforeTokens,
5580
+ after: afterTokens,
5581
+ reductions,
5582
+ repaired: repaired.report.changed ? {
5583
+ removedToolUses: repaired.report.removedToolUses,
5584
+ removedToolResults: repaired.report.removedToolResults,
5585
+ removedMessages: repaired.report.removedMessages
5586
+ } : void 0
5587
+ };
5315
5588
  }
5316
5589
  async summarizeAncientTurns(ctx) {
5317
5590
  const messages = ctx.messages;
@@ -5351,8 +5624,8 @@ var IntelligentCompactor = class {
5351
5624
  const m = messages[i];
5352
5625
  if (!m) continue;
5353
5626
  if (m.role === "assistant") {
5354
- const hasToolUse = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
5355
- if (!hasToolUse) {
5627
+ const hasToolUse2 = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
5628
+ if (!hasToolUse2) {
5356
5629
  return i + 1;
5357
5630
  }
5358
5631
  } else if (m.role !== "user") ; else {
@@ -5658,8 +5931,9 @@ var SelectiveCompactor = class {
5658
5931
  if (!shouldCompact) {
5659
5932
  const saved = this.eliseOldToolResults(ctx);
5660
5933
  if (saved > 0) reductions.push({ phase: "elision", saved });
5934
+ const repair2 = this.repairProtocolAdjacency(ctx);
5661
5935
  const afterTokens2 = this.estimateTokens(ctx.messages);
5662
- return { before: beforeTokens, after: afterTokens2, reductions };
5936
+ return { before: beforeTokens, after: afterTokens2, reductions, repaired: repair2 };
5663
5937
  }
5664
5938
  const savedElision = this.eliseOldToolResults(ctx);
5665
5939
  if (savedElision > 0) reductions.push({ phase: "elision", saved: savedElision });
@@ -5669,8 +5943,18 @@ var SelectiveCompactor = class {
5669
5943
  const savedSelective = await this.runSelector(ctx, targetBudget);
5670
5944
  if (savedSelective > 0) reductions.push({ phase: "selective", saved: savedSelective });
5671
5945
  }
5946
+ const repair = this.repairProtocolAdjacency(ctx);
5672
5947
  const afterTokens = this.estimateTokens(ctx.messages);
5673
- return { before: beforeTokens, after: afterTokens, reductions };
5948
+ return { before: beforeTokens, after: afterTokens, reductions, repaired: repair };
5949
+ }
5950
+ repairProtocolAdjacency(ctx) {
5951
+ const repaired = repairToolUseAdjacency(ctx.messages);
5952
+ if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
5953
+ return repaired.report.changed ? {
5954
+ removedToolUses: repaired.report.removedToolUses,
5955
+ removedToolResults: repaired.report.removedToolResults,
5956
+ removedMessages: repaired.report.removedMessages
5957
+ } : void 0;
5674
5958
  }
5675
5959
  /**
5676
5960
  * Run the LLM selector to decide what to keep vs collapse.
@@ -5858,6 +6142,7 @@ var AutoCompactionMiddleware = class {
5858
6142
  aggressiveOn;
5859
6143
  events;
5860
6144
  failureMode;
6145
+ policyProvider;
5861
6146
  /**
5862
6147
  * @param compactor Compactor to use for compaction.
5863
6148
  * @param maxContext Provider's max context window in tokens.
@@ -5880,17 +6165,25 @@ var AutoCompactionMiddleware = class {
5880
6165
  this.aggressiveOn = opts.aggressiveOn ?? "soft";
5881
6166
  this.events = opts.events;
5882
6167
  this.failureMode = opts.failureMode ?? "throw_on_hard";
6168
+ this.policyProvider = opts.policyProvider;
5883
6169
  }
5884
6170
  handler() {
5885
6171
  return async (ctx, next) => {
5886
6172
  const tokens = this.estimator(ctx);
5887
6173
  const load = tokens / this.maxContext;
5888
- if (load >= this.hardThreshold) {
6174
+ const policy = this.policyProvider?.(ctx);
6175
+ const thresholds = policy?.thresholds ?? {
6176
+ warn: this.warnThreshold,
6177
+ soft: this.softThreshold,
6178
+ hard: this.hardThreshold
6179
+ };
6180
+ const aggressiveOn = policy?.aggressiveOn ?? this.aggressiveOn;
6181
+ if (load >= thresholds.hard) {
5889
6182
  await this.compact(ctx, true, { level: "hard", tokens, load });
5890
- } else if (load >= this.softThreshold) {
5891
- await this.compact(ctx, this.aggressiveOn !== "hard", { level: "soft", tokens, load });
5892
- } else if (load >= this.warnThreshold) {
5893
- await this.compact(ctx, false, { level: "warn", tokens, load });
6183
+ } else if (load >= thresholds.soft) {
6184
+ await this.compact(ctx, aggressiveOn !== "hard", { level: "soft", tokens, load });
6185
+ } else if (load >= thresholds.warn) {
6186
+ await this.compact(ctx, aggressiveOn === "warn", { level: "warn", tokens, load });
5894
6187
  }
5895
6188
  return next(ctx);
5896
6189
  };
@@ -6858,6 +7151,179 @@ function providerErrorToSubagentError(err, message, cause) {
6858
7151
  }
6859
7152
  return { kind: "unknown", message, retryable: err.retryable, cause };
6860
7153
  }
7154
+ function makeSpawnTool(director, roster) {
7155
+ const inputSchema = {
7156
+ type: "object",
7157
+ properties: {
7158
+ role: { type: "string", description: "Roster role id (preferred). When set, the spawn uses the matching config from the roster and ignores other fields." },
7159
+ name: { type: "string", description: "Display name for the subagent. Required when not using roster." },
7160
+ provider: { type: "string", description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.' },
7161
+ model: { type: "string", description: "Model id within the provider. Defaults to the leader model when omitted." },
7162
+ systemPromptOverride: { type: "string", description: "Extra prompt text appended after the role-base prompt." },
7163
+ maxIterations: { type: "number" },
7164
+ maxToolCalls: { type: "number" },
7165
+ maxCostUsd: { type: "number" }
7166
+ },
7167
+ required: []
7168
+ };
7169
+ return {
7170
+ name: "spawn_subagent",
7171
+ description: "Create a new subagent under this director. Returns the subagent id.",
7172
+ usageHint: "Either pass `role` (matches the roster) OR pass `name` + optional `provider`/`model`. Returns `{ subagentId }`.",
7173
+ permission: "auto",
7174
+ mutating: false,
7175
+ inputSchema,
7176
+ async execute(input) {
7177
+ const i = input ?? {};
7178
+ const role = typeof i.role === "string" ? i.role : void 0;
7179
+ const base = role && roster ? roster[role] : void 0;
7180
+ if (role && !base) {
7181
+ return { error: `unknown role "${role}". roster has: ${roster ? Object.keys(roster).join(", ") : "(empty)"}` };
7182
+ }
7183
+ const cfg = { ...base ?? { name: i.name ?? "subagent" } };
7184
+ if (typeof i.name === "string") cfg.name = i.name;
7185
+ if (typeof i.provider === "string") cfg.provider = i.provider;
7186
+ if (typeof i.model === "string") cfg.model = i.model;
7187
+ if (typeof i.systemPromptOverride === "string") cfg.systemPromptOverride = i.systemPromptOverride;
7188
+ if (typeof i.maxIterations === "number") cfg.maxIterations = i.maxIterations;
7189
+ if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
7190
+ if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
7191
+ try {
7192
+ const subagentId = await director.spawn(cfg);
7193
+ return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name };
7194
+ } catch (err) {
7195
+ if (err instanceof DirectorBudgetError) {
7196
+ return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
7197
+ }
7198
+ return { error: err instanceof Error ? err.message : String(err) };
7199
+ }
7200
+ }
7201
+ };
7202
+ }
7203
+ function makeAssignTool(director) {
7204
+ const inputSchema = {
7205
+ type: "object",
7206
+ properties: {
7207
+ subagentId: { type: "string", description: "Target subagent id. Required." },
7208
+ description: { type: "string", description: "The task in natural language \u2014 what you want this subagent to do." },
7209
+ maxToolCalls: { type: "number", description: "Optional per-task tool-call budget override." },
7210
+ timeoutMs: { type: "number", description: "Optional per-task timeout in ms." }
7211
+ },
7212
+ required: ["subagentId", "description"]
7213
+ };
7214
+ return {
7215
+ name: "assign_task",
7216
+ description: "Hand a task to a previously spawned subagent. Returns the task id.",
7217
+ permission: "auto",
7218
+ mutating: false,
7219
+ inputSchema,
7220
+ async execute(input) {
7221
+ const i = input;
7222
+ const task = { id: randomUUID(), description: i.description, subagentId: i.subagentId, maxToolCalls: i.maxToolCalls, timeoutMs: i.timeoutMs };
7223
+ const taskId = await director.assign(task);
7224
+ return { taskId, subagentId: i.subagentId };
7225
+ }
7226
+ };
7227
+ }
7228
+ function makeAwaitTasksTool(director) {
7229
+ return {
7230
+ name: "await_tasks",
7231
+ description: "Block until every named task completes. Returns the array of TaskResult.",
7232
+ permission: "auto",
7233
+ mutating: false,
7234
+ inputSchema: { type: "object", properties: { taskIds: { type: "array", items: { type: "string" }, description: "One or more task ids returned by `assign_task`." } }, required: ["taskIds"] },
7235
+ async execute(input) {
7236
+ const i = input;
7237
+ const results = await director.awaitTasks(i.taskIds);
7238
+ return { results };
7239
+ }
7240
+ };
7241
+ }
7242
+ function makeAskTool(director) {
7243
+ return {
7244
+ name: "ask_subagent",
7245
+ description: "Synchronously ask a subagent a question. Blocks until the subagent replies via the bridge.",
7246
+ permission: "auto",
7247
+ mutating: false,
7248
+ inputSchema: {
7249
+ type: "object",
7250
+ properties: {
7251
+ subagentId: { type: "string", description: "Subagent to ask. Must be a previously spawned id." },
7252
+ question: { type: "string", description: "The question or instruction." },
7253
+ timeoutMs: { type: "number", description: "Optional timeout in ms (default 30s)." }
7254
+ },
7255
+ required: ["subagentId", "question"]
7256
+ },
7257
+ async execute(input) {
7258
+ const i = input;
7259
+ try {
7260
+ const answer = await director.ask(i.subagentId, { question: i.question }, i.timeoutMs);
7261
+ return { ok: true, answer };
7262
+ } catch (err) {
7263
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
7264
+ }
7265
+ }
7266
+ };
7267
+ }
7268
+ function makeRollUpTool(director) {
7269
+ return {
7270
+ name: "roll_up",
7271
+ description: "Aggregate completed task results into a single formatted summary.",
7272
+ permission: "auto",
7273
+ mutating: false,
7274
+ inputSchema: {
7275
+ type: "object",
7276
+ properties: {
7277
+ taskIds: { type: "array", items: { type: "string" }, description: "Completed task ids to aggregate." },
7278
+ style: { type: "string", enum: ["markdown", "json"], description: "Output flavor \u2014 markdown (default) or json." }
7279
+ },
7280
+ required: ["taskIds"]
7281
+ },
7282
+ async execute(input) {
7283
+ const i = input;
7284
+ const summary = director.rollUp(i.taskIds, i.style ?? "markdown");
7285
+ return { summary, count: i.taskIds.length };
7286
+ }
7287
+ };
7288
+ }
7289
+ function makeTerminateTool(director) {
7290
+ return {
7291
+ name: "terminate_subagent",
7292
+ description: "Forcibly abort a subagent.",
7293
+ permission: "auto",
7294
+ mutating: true,
7295
+ inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
7296
+ async execute(input) {
7297
+ const i = input;
7298
+ await director.terminate(i.subagentId);
7299
+ return { ok: true };
7300
+ }
7301
+ };
7302
+ }
7303
+ function makeFleetStatusTool(director) {
7304
+ return {
7305
+ name: "fleet_status",
7306
+ description: "Snapshot of the fleet \u2014 every subagent's current status, pending vs. completed task counts.",
7307
+ permission: "auto",
7308
+ mutating: false,
7309
+ inputSchema: { type: "object", properties: {}, required: [] },
7310
+ async execute() {
7311
+ return director.status();
7312
+ }
7313
+ };
7314
+ }
7315
+ function makeFleetUsageTool(director) {
7316
+ return {
7317
+ name: "fleet_usage",
7318
+ description: "Token + cost breakdown across the fleet, per-subagent and totals.",
7319
+ permission: "auto",
7320
+ mutating: false,
7321
+ inputSchema: { type: "object", properties: {}, required: [] },
7322
+ async execute() {
7323
+ return director.snapshot();
7324
+ }
7325
+ };
7326
+ }
6861
7327
 
6862
7328
  // src/coordination/director.ts
6863
7329
  var DirectorBudgetError = class extends Error {
@@ -7416,242 +7882,6 @@ var Director = class {
7416
7882
  return t2;
7417
7883
  }
7418
7884
  };
7419
- function makeSpawnTool(director, roster) {
7420
- const inputSchema = {
7421
- type: "object",
7422
- properties: {
7423
- role: {
7424
- type: "string",
7425
- description: "Roster role id (preferred). When set, the spawn uses the matching config from the roster and ignores other fields."
7426
- },
7427
- name: {
7428
- type: "string",
7429
- description: "Display name for the subagent. Required when not using roster."
7430
- },
7431
- provider: {
7432
- type: "string",
7433
- description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.'
7434
- },
7435
- model: {
7436
- type: "string",
7437
- description: "Model id within the provider. Defaults to the leader model when omitted."
7438
- },
7439
- systemPromptOverride: {
7440
- type: "string",
7441
- description: "Extra prompt text appended after the role-base prompt."
7442
- },
7443
- maxIterations: { type: "number" },
7444
- maxToolCalls: { type: "number" },
7445
- maxCostUsd: { type: "number" }
7446
- },
7447
- required: []
7448
- };
7449
- return {
7450
- name: "spawn_subagent",
7451
- 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.",
7452
- usageHint: "Either pass `role` (matches the roster) OR pass `name` + optional `provider`/`model`. Returns `{ subagentId }`.",
7453
- permission: "auto",
7454
- mutating: false,
7455
- inputSchema,
7456
- async execute(input) {
7457
- const i = input ?? {};
7458
- const role = typeof i.role === "string" ? i.role : void 0;
7459
- const base = role && roster ? roster[role] : void 0;
7460
- if (role && !base) {
7461
- return {
7462
- error: `unknown role "${role}". roster has: ${roster ? Object.keys(roster).join(", ") : "(empty)"}`
7463
- };
7464
- }
7465
- const cfg = {
7466
- ...base ?? { name: i.name ?? "subagent" }
7467
- };
7468
- if (typeof i.name === "string") cfg.name = i.name;
7469
- if (typeof i.provider === "string") cfg.provider = i.provider;
7470
- if (typeof i.model === "string") cfg.model = i.model;
7471
- if (typeof i.systemPromptOverride === "string")
7472
- cfg.systemPromptOverride = i.systemPromptOverride;
7473
- if (typeof i.maxIterations === "number") cfg.maxIterations = i.maxIterations;
7474
- if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
7475
- if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
7476
- try {
7477
- const subagentId = await director.spawn(cfg);
7478
- return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name };
7479
- } catch (err) {
7480
- if (err instanceof DirectorBudgetError) {
7481
- return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
7482
- }
7483
- return { error: err instanceof Error ? err.message : String(err) };
7484
- }
7485
- }
7486
- };
7487
- }
7488
- function makeAssignTool(director) {
7489
- const inputSchema = {
7490
- type: "object",
7491
- properties: {
7492
- subagentId: { type: "string", description: "Target subagent id. Required." },
7493
- description: {
7494
- type: "string",
7495
- description: "The task in natural language \u2014 what you want this subagent to do."
7496
- },
7497
- maxToolCalls: { type: "number", description: "Optional per-task tool-call budget override." },
7498
- timeoutMs: { type: "number", description: "Optional per-task timeout in ms." }
7499
- },
7500
- required: ["subagentId", "description"]
7501
- };
7502
- return {
7503
- name: "assign_task",
7504
- description: "Hand a task to a previously spawned subagent. Returns the task id \u2014 pass it to `await_tasks` to block on completion.",
7505
- permission: "auto",
7506
- mutating: false,
7507
- inputSchema,
7508
- async execute(input) {
7509
- const i = input;
7510
- const task = {
7511
- id: randomUUID(),
7512
- description: i.description,
7513
- subagentId: i.subagentId,
7514
- maxToolCalls: i.maxToolCalls,
7515
- timeoutMs: i.timeoutMs
7516
- };
7517
- const taskId = await director.assign(task);
7518
- return { taskId, subagentId: i.subagentId };
7519
- }
7520
- };
7521
- }
7522
- function makeAwaitTasksTool(director) {
7523
- const inputSchema = {
7524
- type: "object",
7525
- properties: {
7526
- taskIds: {
7527
- type: "array",
7528
- items: { type: "string" },
7529
- description: "One or more task ids returned by `assign_task`. The call blocks until every id resolves."
7530
- }
7531
- },
7532
- required: ["taskIds"]
7533
- };
7534
- return {
7535
- name: "await_tasks",
7536
- description: "Block until every named task completes. Returns the array of TaskResult \u2014 use this to gather subagent output before deciding the next step.",
7537
- permission: "auto",
7538
- mutating: false,
7539
- inputSchema,
7540
- async execute(input) {
7541
- const i = input;
7542
- const results = await director.awaitTasks(i.taskIds);
7543
- return { results };
7544
- }
7545
- };
7546
- }
7547
- function makeAskTool(director) {
7548
- const inputSchema = {
7549
- type: "object",
7550
- properties: {
7551
- subagentId: {
7552
- type: "string",
7553
- description: "Subagent to ask. Must be a previously spawned id."
7554
- },
7555
- question: {
7556
- type: "string",
7557
- description: "The question or instruction. Sent as the bridge message payload."
7558
- },
7559
- timeoutMs: { type: "number", description: "Optional timeout in ms (default 30s)." }
7560
- },
7561
- required: ["subagentId", "question"]
7562
- };
7563
- return {
7564
- name: "ask_subagent",
7565
- 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.",
7566
- permission: "auto",
7567
- mutating: false,
7568
- inputSchema,
7569
- async execute(input) {
7570
- const i = input;
7571
- try {
7572
- const answer = await director.ask(i.subagentId, { question: i.question }, i.timeoutMs);
7573
- return { ok: true, answer };
7574
- } catch (err) {
7575
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
7576
- }
7577
- }
7578
- };
7579
- }
7580
- function makeRollUpTool(director) {
7581
- const inputSchema = {
7582
- type: "object",
7583
- properties: {
7584
- taskIds: {
7585
- type: "array",
7586
- items: { type: "string" },
7587
- description: "Completed task ids to aggregate. Pass the ids returned by previous `assign_task` calls."
7588
- },
7589
- style: {
7590
- type: "string",
7591
- enum: ["markdown", "json"],
7592
- description: "Output flavor \u2014 markdown (default) for in-prompt summarization, json for structured downstream processing."
7593
- }
7594
- },
7595
- required: ["taskIds"]
7596
- };
7597
- return {
7598
- name: "roll_up",
7599
- 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.",
7600
- permission: "auto",
7601
- mutating: false,
7602
- inputSchema,
7603
- async execute(input) {
7604
- const i = input;
7605
- const summary = director.rollUp(i.taskIds, i.style ?? "markdown");
7606
- return { summary, count: i.taskIds.length };
7607
- }
7608
- };
7609
- }
7610
- function makeTerminateTool(director) {
7611
- const inputSchema = {
7612
- type: "object",
7613
- properties: {
7614
- subagentId: { type: "string", description: "Subagent to abort." }
7615
- },
7616
- required: ["subagentId"]
7617
- };
7618
- return {
7619
- name: "terminate_subagent",
7620
- 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".',
7621
- permission: "auto",
7622
- mutating: true,
7623
- inputSchema,
7624
- async execute(input) {
7625
- const i = input;
7626
- await director.terminate(i.subagentId);
7627
- return { ok: true };
7628
- }
7629
- };
7630
- }
7631
- function makeFleetStatusTool(director) {
7632
- return {
7633
- name: "fleet_status",
7634
- 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.",
7635
- permission: "auto",
7636
- mutating: false,
7637
- inputSchema: { type: "object", properties: {}, required: [] },
7638
- async execute() {
7639
- return director.status();
7640
- }
7641
- };
7642
- }
7643
- function makeFleetUsageTool(director) {
7644
- return {
7645
- name: "fleet_usage",
7646
- 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.",
7647
- permission: "auto",
7648
- mutating: false,
7649
- inputSchema: { type: "object", properties: {}, required: [] },
7650
- async execute() {
7651
- return director.snapshot();
7652
- }
7653
- };
7654
- }
7655
7885
  function createDelegateTool(opts) {
7656
7886
  const defaultTimeoutMs = opts.defaultTimeoutMs ?? 4 * 60 * 60 * 1e3;
7657
7887
  const rosterIds = opts.roster ? Object.keys(opts.roster) : [];
@@ -9837,13 +10067,13 @@ function roughEstimate(messages) {
9837
10067
  function createContextManagerTool(opts = {}) {
9838
10068
  return {
9839
10069
  name: CONTEXT_MANAGER_TOOL_NAME,
9840
- 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.',
10070
+ 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.',
9841
10071
  inputSchema: {
9842
10072
  type: "object",
9843
10073
  properties: {
9844
10074
  action: {
9845
10075
  type: "string",
9846
- enum: ["check", "summary", "prune", "add_note", "compact"],
10076
+ enum: ["check", "summary", "prune", "add_note", "compact", "repair"],
9847
10077
  description: "The context operation to perform."
9848
10078
  },
9849
10079
  from: {
@@ -9871,12 +10101,15 @@ function createContextManagerTool(opts = {}) {
9871
10101
  const messages = ctx.messages;
9872
10102
  const beforeTokens = roughEstimate(messages);
9873
10103
  const applyMessages = (next) => {
10104
+ const repaired = repairToolUseAdjacency(next);
10105
+ const finalMessages = repaired.messages;
9874
10106
  if (ctx.state) {
9875
- ctx.state.replaceMessages(next);
10107
+ ctx.state.replaceMessages(finalMessages);
9876
10108
  } else {
9877
10109
  messages.length = 0;
9878
- messages.splice(0, 0, ...next);
10110
+ messages.splice(0, 0, ...finalMessages);
9879
10111
  }
10112
+ return repaired.report;
9880
10113
  };
9881
10114
  switch (input.action) {
9882
10115
  case "check": {
@@ -9893,6 +10126,22 @@ function createContextManagerTool(opts = {}) {
9893
10126
  })
9894
10127
  };
9895
10128
  }
10129
+ case "repair": {
10130
+ const repair = applyMessages([...messages]);
10131
+ const afterTokens = roughEstimate(ctx.messages);
10132
+ return {
10133
+ action: "repair",
10134
+ beforeTokens,
10135
+ afterTokens,
10136
+ messageCount: ctx.messages.length,
10137
+ repaired: repair.changed ? {
10138
+ removedToolUses: repair.removedToolUses,
10139
+ removedToolResults: repair.removedToolResults,
10140
+ removedMessages: repair.removedMessages
10141
+ } : void 0,
10142
+ notes: repair.changed ? "Context tool-call adjacency repaired." : "Context tool-call adjacency already valid."
10143
+ };
10144
+ }
9896
10145
  case "compact": {
9897
10146
  if (!opts.compactor) {
9898
10147
  return {
@@ -9903,11 +10152,19 @@ function createContextManagerTool(opts = {}) {
9903
10152
  };
9904
10153
  }
9905
10154
  const report = await opts.compactor.compact(ctx);
10155
+ const repair = applyMessages([...ctx.messages]);
10156
+ const afterTokens = repair.changed ? roughEstimate(ctx.messages) : report.after;
10157
+ const repaired = report.repaired ?? (repair.changed ? repair : void 0);
9906
10158
  return {
9907
10159
  action: "compact",
9908
10160
  beforeTokens,
9909
- afterTokens: report.after,
9910
- messageCount: messages.length
10161
+ afterTokens,
10162
+ messageCount: ctx.messages.length,
10163
+ repaired: repaired ? {
10164
+ removedToolUses: repaired.removedToolUses,
10165
+ removedToolResults: repaired.removedToolResults,
10166
+ removedMessages: repaired.removedMessages
10167
+ } : void 0
9911
10168
  };
9912
10169
  }
9913
10170
  case "prune": {
@@ -9923,14 +10180,19 @@ function createContextManagerTool(opts = {}) {
9923
10180
  }
9924
10181
  const copy = [...messages];
9925
10182
  const removed = copy.splice(from, to - from + 1);
9926
- applyMessages(copy);
9927
- const afterTokens = roughEstimate(copy);
10183
+ const repair = applyMessages(copy);
10184
+ const afterTokens = roughEstimate(ctx.messages);
9928
10185
  return {
9929
10186
  action: "prune",
9930
10187
  beforeTokens,
9931
10188
  afterTokens,
9932
- messageCount: copy.length,
9933
- removedCount: removed.length
10189
+ messageCount: ctx.messages.length,
10190
+ removedCount: removed.length,
10191
+ repaired: repair.changed ? {
10192
+ removedToolUses: repair.removedToolUses,
10193
+ removedToolResults: repair.removedToolResults,
10194
+ removedMessages: repair.removedMessages
10195
+ } : void 0
9934
10196
  };
9935
10197
  }
9936
10198
  case "add_note": {
@@ -9942,14 +10204,19 @@ function createContextManagerTool(opts = {}) {
9942
10204
  };
9943
10205
  const copy = [...messages];
9944
10206
  copy.splice(afterIdx, 0, noteMsg);
9945
- applyMessages(copy);
9946
- const afterTokens = roughEstimate(copy);
10207
+ const repair = applyMessages(copy);
10208
+ const afterTokens = roughEstimate(ctx.messages);
9947
10209
  return {
9948
10210
  action: "add_note",
9949
10211
  beforeTokens,
9950
10212
  afterTokens,
9951
- messageCount: copy.length,
9952
- summary: noteText
10213
+ messageCount: ctx.messages.length,
10214
+ summary: noteText,
10215
+ repaired: repair.changed ? {
10216
+ removedToolUses: repair.removedToolUses,
10217
+ removedToolResults: repair.removedToolResults,
10218
+ removedMessages: repair.removedMessages
10219
+ } : void 0
9953
10220
  };
9954
10221
  }
9955
10222
  case "summary": {
@@ -9970,14 +10237,19 @@ function createContextManagerTool(opts = {}) {
9970
10237
  };
9971
10238
  const copy = [...messages];
9972
10239
  copy.splice(from, to - from + 1, summaryMsg);
9973
- applyMessages(copy);
9974
- const afterTokens = roughEstimate(copy);
10240
+ const repair = applyMessages(copy);
10241
+ const afterTokens = roughEstimate(ctx.messages);
9975
10242
  return {
9976
10243
  action: "summary",
9977
10244
  beforeTokens,
9978
10245
  afterTokens,
9979
- messageCount: copy.length,
9980
- summary: summaryText
10246
+ messageCount: ctx.messages.length,
10247
+ summary: summaryText,
10248
+ repaired: repair.changed ? {
10249
+ removedToolUses: repair.removedToolUses,
10250
+ removedToolResults: repair.removedToolResults,
10251
+ removedMessages: repair.removedMessages
10252
+ } : void 0
9981
10253
  };
9982
10254
  }
9983
10255
  default:
@@ -10081,6 +10353,41 @@ var sentinelServer = () => ({
10081
10353
  permission: "deny"
10082
10354
  // security tool — require explicit confirmation
10083
10355
  });
10356
+ var zaiVisionServer = () => ({
10357
+ name: "zai-vision",
10358
+ description: "Z.AI Vision MCP \u2014 image analysis and screenshot understanding",
10359
+ transport: "stdio",
10360
+ command: "npx",
10361
+ args: ["-y", "@z_ai/mcp-server@latest"],
10362
+ env: {
10363
+ Z_AI_API_KEY: process.env.Z_AI_API_KEY ?? "",
10364
+ Z_AI_MODE: process.env.Z_AI_MODE ?? "ZAI"
10365
+ },
10366
+ allowedTools: [
10367
+ "image_analysis",
10368
+ "extract_text_from_screenshot",
10369
+ "diagnose_error_screenshot",
10370
+ "understand_technical_diagram",
10371
+ "analyze_data_visualization",
10372
+ "ui_diff_check"
10373
+ ],
10374
+ permission: "auto"
10375
+ });
10376
+ var miniMaxVisionServer = () => ({
10377
+ name: "minimax-vision",
10378
+ description: "MiniMax MCP \u2014 image understanding via understand_image",
10379
+ transport: "stdio",
10380
+ command: "uvx",
10381
+ args: ["minimax-coding-plan-mcp", "-y"],
10382
+ env: {
10383
+ MINIMAX_API_KEY: process.env.MINIMAX_API_KEY ?? "",
10384
+ MINIMAX_MCP_BASE_PATH: process.env.MINIMAX_MCP_BASE_PATH ?? "./.wrongstack/minimax-output",
10385
+ MINIMAX_API_HOST: process.env.MINIMAX_API_HOST ?? "https://api.minimax.io",
10386
+ MINIMAX_API_RESOURCE_MODE: process.env.MINIMAX_API_RESOURCE_MODE ?? "url"
10387
+ },
10388
+ allowedTools: ["understand_image"],
10389
+ permission: "auto"
10390
+ });
10084
10391
  var allServers = () => ({
10085
10392
  filesystem: { ...filesystemServer(), enabled: false },
10086
10393
  github: { ...githubServer(), enabled: false },
@@ -10091,7 +10398,9 @@ var allServers = () => ({
10091
10398
  slack: { ...slackServer(), enabled: false },
10092
10399
  aws: { ...awsServer(), enabled: false },
10093
10400
  "google-maps": { ...googleMapsServer(), enabled: false },
10094
- sentinel: { ...sentinelServer(), enabled: false }
10401
+ sentinel: { ...sentinelServer(), enabled: false },
10402
+ "zai-vision": { ...zaiVisionServer(), enabled: false },
10403
+ "minimax-vision": { ...miniMaxVisionServer(), enabled: false }
10095
10404
  });
10096
10405
 
10097
10406
  // src/extension/registry.ts
@@ -10194,7 +10503,8 @@ var ExtensionRegistry = class {
10194
10503
  }
10195
10504
  // ── Hook runners ─────────────────────────────────────────────────
10196
10505
  async runBeforeRun(...args) {
10197
- for (const ext of this.extensions) {
10506
+ const snapshot = [...this.extensions];
10507
+ for (const ext of snapshot) {
10198
10508
  if (!ext.beforeRun) continue;
10199
10509
  try {
10200
10510
  await ext.beforeRun(...args);
@@ -10204,7 +10514,8 @@ var ExtensionRegistry = class {
10204
10514
  }
10205
10515
  }
10206
10516
  async runAfterRun(...args) {
10207
- for (const ext of this.extensions) {
10517
+ const snapshot = [...this.extensions];
10518
+ for (const ext of snapshot) {
10208
10519
  if (!ext.afterRun) continue;
10209
10520
  try {
10210
10521
  await ext.afterRun(...args);
@@ -10214,7 +10525,8 @@ var ExtensionRegistry = class {
10214
10525
  }
10215
10526
  }
10216
10527
  async runBeforeIteration(...args) {
10217
- for (const ext of this.extensions) {
10528
+ const snapshot = [...this.extensions];
10529
+ for (const ext of snapshot) {
10218
10530
  if (!ext.beforeIteration) continue;
10219
10531
  try {
10220
10532
  await ext.beforeIteration(...args);
@@ -10224,7 +10536,8 @@ var ExtensionRegistry = class {
10224
10536
  }
10225
10537
  }
10226
10538
  async runAfterIteration(...args) {
10227
- for (const ext of this.extensions) {
10539
+ const snapshot = [...this.extensions];
10540
+ for (const ext of snapshot) {
10228
10541
  if (!ext.afterIteration) continue;
10229
10542
  try {
10230
10543
  await ext.afterIteration(...args);
@@ -10238,7 +10551,8 @@ var ExtensionRegistry = class {
10238
10551
  * result wins; subsequent hooks are skipped.
10239
10552
  */
10240
10553
  async runOnError(...args) {
10241
- for (const ext of this.extensions) {
10554
+ const snapshot = [...this.extensions];
10555
+ for (const ext of snapshot) {
10242
10556
  if (!ext.onError) continue;
10243
10557
  try {
10244
10558
  const result = await ext.onError(...args);
@@ -10273,7 +10587,8 @@ var ExtensionRegistry = class {
10273
10587
  }
10274
10588
  async runBeforeToolExecution(...args) {
10275
10589
  let toolUses = args[1];
10276
- for (const ext of this.extensions) {
10590
+ const snapshot = [...this.extensions];
10591
+ for (const ext of snapshot) {
10277
10592
  if (!ext.beforeToolExecution) continue;
10278
10593
  try {
10279
10594
  toolUses = await ext.beforeToolExecution(args[0], toolUses);
@@ -10284,7 +10599,8 @@ var ExtensionRegistry = class {
10284
10599
  return toolUses;
10285
10600
  }
10286
10601
  async runAfterToolExecution(...args) {
10287
- for (const ext of this.extensions) {
10602
+ const snapshot = [...this.extensions];
10603
+ for (const ext of snapshot) {
10288
10604
  if (!ext.afterToolExecution) continue;
10289
10605
  try {
10290
10606
  await ext.afterToolExecution(...args);
@@ -10609,6 +10925,17 @@ var Agent = class {
10609
10925
  * Build request and run through request pipeline.
10610
10926
  */
10611
10927
  async buildAndRunRequestPipeline(opts) {
10928
+ const repaired = repairToolUseAdjacency(this.ctx.messages);
10929
+ if (repaired.report.changed) {
10930
+ this.ctx.state.replaceMessages(repaired.messages);
10931
+ this.events.emit("context.repaired", {
10932
+ ctx: this.ctx,
10933
+ ...repaired.report
10934
+ });
10935
+ this.logger.warn(
10936
+ `Repaired context tool adjacency: removed ${repaired.report.removedToolUses.length} tool_use block(s), ${repaired.report.removedToolResults.length} tool_result block(s), ${repaired.report.removedMessages} empty message(s)`
10937
+ );
10938
+ }
10612
10939
  const baseReq = {
10613
10940
  model: opts.model ?? this.ctx.model,
10614
10941
  system: this.ctx.systemPrompt,
@@ -10665,13 +10992,13 @@ var Agent = class {
10665
10992
  * single tool.
10666
10993
  */
10667
10994
  async executeTools(toolUses) {
10668
- toolUses = await this.extensions.runBeforeToolExecution(this.ctx, toolUses);
10995
+ const selectedToolUses = await this.extensions.runBeforeToolExecution(this.ctx, toolUses);
10669
10996
  const { outputs } = await this.toolExecutor.executeBatch(
10670
- toolUses,
10997
+ selectedToolUses,
10671
10998
  this.ctx,
10672
10999
  this.executionStrategy
10673
11000
  );
10674
- const useById = new Map(toolUses.map((u) => [u.id, u]));
11001
+ const useById = new Map(selectedToolUses.map((u) => [u.id, u]));
10675
11002
  for (const { result, tool, durationMs } of outputs) {
10676
11003
  if (result.type === "tool_confirm_pending") {
10677
11004
  const decision = await this.waitForConfirm({
@@ -11870,7 +12197,7 @@ var DefaultPluginAPI = class {
11870
12197
  } : noopSlashCommands;
11871
12198
  }
11872
12199
  onEvent(event, handler) {
11873
- const off = this.events.once(event, handler);
12200
+ const off = this.events.on(event, handler);
11874
12201
  this.pluginCleanupFns.push(off);
11875
12202
  return off;
11876
12203
  }
@@ -11947,6 +12274,7 @@ function scopedMetrics(sink, pluginName) {
11947
12274
  }
11948
12275
 
11949
12276
  // src/plugin/loader.ts
12277
+ var pluginApiMap = /* @__PURE__ */ new WeakMap();
11950
12278
  var KERNEL_API_VERSION = "0.1.10";
11951
12279
  function parseSemver(v) {
11952
12280
  const parts = v.replace(/^[^0-9]*/, "").split(".").map((s) => Number.parseInt(s, 10) || 0);
@@ -12103,6 +12431,7 @@ async function loadPlugins(plugins, opts) {
12103
12431
  const rawApi = opts.apiFactory(plugin);
12104
12432
  const api = plugin.capabilities ? wrapApiForCapabilityCheck(plugin, rawApi, opts.log, opts.enforceCapabilities) : rawApi;
12105
12433
  await plugin.setup(api);
12434
+ pluginApiMap.set(plugin, api);
12106
12435
  loaded.push(plugin);
12107
12436
  opts.log.info(`Plugin "${plugin.name}" loaded`);
12108
12437
  } catch (err) {
@@ -12117,8 +12446,9 @@ async function unloadPlugins(loadedPlugins, opts) {
12117
12446
  for (const plugin of ordered) {
12118
12447
  if (typeof plugin.teardown !== "function") continue;
12119
12448
  try {
12120
- const api = opts.apiFactory(plugin);
12449
+ const api = pluginApiMap.get(plugin) ?? opts.apiFactory(plugin);
12121
12450
  await plugin.teardown(api);
12451
+ pluginApiMap.delete(plugin);
12122
12452
  opts.log.info(`Plugin "${plugin.name}" torn down`);
12123
12453
  } catch (err) {
12124
12454
  opts.log.error(`Plugin "${plugin.name}" teardown failed`, err);
@@ -12205,6 +12535,6 @@ function wrapApiForCapabilityCheck(plugin, api, log, enforce = false) {
12205
12535
  });
12206
12536
  }
12207
12537
 
12208
- export { ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, Agent, AgentError, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorBudgetError, DirectorStateCheckpoint, DoneConditionChecker, EventBus, ExtensionRegistry, FLEET_ROSTER, FleetBus, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, RunController, SECURITY_SCANNER_AGENT, SelectiveCompactor, SessionAnalyzer, SessionError, SlashCommandRegistry, SpecDrivenDev, SpecParser, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskTracker, ToolError, ToolExecutor, ToolRegistry, WrongStackError, addPlanItem, allServers, asBlocks, asText, atomicWrite, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildChildEnv, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, context7Server, contextManagerTool, createContextManagerTool, createDefaultPipelines, createDelegateTool, createMessage, createToolOutputSerializer, decryptConfigSecrets, detectNewlineStyle, emptyPlan, encryptConfigSecrets, ensureDir, estimateTextTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatPlan, formatTodosList, githubServer, googleMapsServer, isAgentError, isConfigError, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, loadDirectorState, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, matchAny, matchGlob, migratePlaintextSecrets, normalizeToLf, projectHash, removePlanItem, renderPrometheus, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, savePlan, saveTodosCheckpoint, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState };
12538
+ export { ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, Agent, AgentError, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorBudgetError, DirectorStateCheckpoint, DoneConditionChecker, EventBus, ExtensionRegistry, FLEET_ROSTER, FleetBus, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, RunController, SECURITY_SCANNER_AGENT, SelectiveCompactor, SessionAnalyzer, SessionError, SlashCommandRegistry, SpecDrivenDev, SpecParser, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskTracker, ToolError, ToolExecutor, ToolRegistry, WrongStackError, addPlanItem, allServers, asBlocks, asText, atomicWrite, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildChildEnv, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, context7Server, contextManagerTool, createContextManagerTool, createDefaultPipelines, createDelegateTool, createMessage, createToolOutputSerializer, decryptConfigSecrets, detectNewlineStyle, emptyPlan, encryptConfigSecrets, ensureDir, estimateTextTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatPlan, formatTodosList, getContextWindowMode, githubServer, googleMapsServer, isAgentError, isConfigError, isContextWindowModeId, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, loadDirectorState, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, matchAny, matchGlob, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, projectHash, removePlanItem, renderPrometheus, repairToolUseAdjacency, resolveContextWindowPolicy, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, savePlan, saveTodosCheckpoint, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, zaiVisionServer };
12209
12539
  //# sourceMappingURL=index.js.map
12210
12540
  //# sourceMappingURL=index.js.map