@wrongstack/core 0.109.1 → 0.141.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/{agent-bridge-mOxbpFcg.d.ts → agent-bridge-r9y6gdn4.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-DukQLUcS.d.ts → agent-subagent-runner-1GeQE_L0.d.ts} +7 -8
  3. package/dist/{brain-Dfv4Y82E.d.ts → brain-Cp_3GIS2.d.ts} +14 -3
  4. package/dist/{compactor-DXLxLcmU.d.ts → compactor-BueGt7LG.d.ts} +9 -1
  5. package/dist/{config-BSU-6vah.d.ts → config-BaVThgnT.d.ts} +104 -3
  6. package/dist/{context-CNRYfhUv.d.ts → context-C7G_MtLV.d.ts} +38 -1
  7. package/dist/coordination/index.d.ts +12 -13
  8. package/dist/coordination/index.js +229 -17
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +26 -27
  11. package/dist/defaults/index.js +886 -721
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/execution/index.d.ts +15 -16
  14. package/dist/execution/index.js +535 -322
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/extension/index.d.ts +6 -7
  17. package/dist/{goal-preamble-CI8lxeY1.d.ts → goal-preamble-iuIUTQwk.d.ts} +48 -18
  18. package/dist/{index-BWRN6wOb.d.ts → index-BZdezm3g.d.ts} +9 -10
  19. package/dist/{index-DIKEcfgC.d.ts → index-CPweVoFM.d.ts} +7 -6
  20. package/dist/index.d.ts +51 -52
  21. package/dist/index.js +752 -545
  22. package/dist/index.js.map +1 -1
  23. package/dist/infrastructure/index.d.ts +6 -7
  24. package/dist/infrastructure/index.js +64 -19
  25. package/dist/infrastructure/index.js.map +1 -1
  26. package/dist/kernel/index.d.ts +10 -11
  27. package/dist/kernel/index.js.map +1 -1
  28. package/dist/llm-selector-CP72f1lC.d.ts +58 -0
  29. package/dist/{mcp-servers-CXCsANdY.d.ts → mcp-servers-Bl5LTvQg.d.ts} +3 -3
  30. package/dist/{mode-ARA3HrkY.d.ts → mode-CZlO9iU1.d.ts} +1 -1
  31. package/dist/models/index.d.ts +157 -53
  32. package/dist/models/index.js +475 -20
  33. package/dist/models/index.js.map +1 -1
  34. package/dist/{models-registry-DU64QxQa.d.ts → models-registry-D90K9UnM.d.ts} +1 -1
  35. package/dist/{multi-agent-coordinator-51LvnXkD.d.ts → multi-agent-coordinator-QWEzJDlm.d.ts} +1 -1
  36. package/dist/{null-fleet-bus-D09hMzFQ.d.ts → null-fleet-bus-BUyfqh23.d.ts} +13 -10
  37. package/dist/observability/index.d.ts +2 -2
  38. package/dist/{parallel-eternal-engine-B2CbsKpc.d.ts → parallel-eternal-engine-Dj2SYzha.d.ts} +24 -11
  39. package/dist/{path-resolver-DDJiMAtX.d.ts → path-resolver-DRjQBkoO.d.ts} +3 -3
  40. package/dist/{permission-BDv7z0mk.d.ts → permission-B7nKnEvQ.d.ts} +1 -1
  41. package/dist/{permission-policy-dF74EpDp.d.ts → permission-policy-8-6zBmfA.d.ts} +2 -13
  42. package/dist/{pipeline-BqiA_UMr.d.ts → pipeline-BG7UgbDc.d.ts} +2 -2
  43. package/dist/{plan-templates-BdDxl9cI.d.ts → plan-templates-CkKNPU3I.d.ts} +6 -6
  44. package/dist/{provider-runner-BUunikwY.d.ts → provider-runner-BNpuIyOL.d.ts} +3 -3
  45. package/dist/{retry-policy-BcmuT_V0.d.ts → retry-policy-rutAfVeR.d.ts} +1 -1
  46. package/dist/sdd/index.d.ts +8 -9
  47. package/dist/sdd/index.js +208 -1
  48. package/dist/sdd/index.js.map +1 -1
  49. package/dist/{secret-vault-DrOhc2i5.d.ts → secret-vault-BTcC_T5v.d.ts} +3 -2
  50. package/dist/security/index.d.ts +5 -7
  51. package/dist/security/index.js +4 -35
  52. package/dist/security/index.js.map +1 -1
  53. package/dist/{selector-C7wcdqMA.d.ts → selector-4vDFZKt3.d.ts} +1 -1
  54. package/dist/{session-event-bridge-BpJ5trO9.d.ts → session-event-bridge-DWlvglC2.d.ts} +2 -2
  55. package/dist/{session-reader-DDz1Ek4V.d.ts → session-reader-BAtCxdaw.d.ts} +1 -1
  56. package/dist/storage/index.d.ts +14 -15
  57. package/dist/storage/index.js +140 -134
  58. package/dist/storage/index.js.map +1 -1
  59. package/dist/types/index.d.ts +19 -20
  60. package/dist/types/index.js +186 -102
  61. package/dist/types/index.js.map +1 -1
  62. package/dist/utils/index.d.ts +38 -15
  63. package/dist/utils/index.js +62 -33
  64. package/dist/utils/index.js.map +1 -1
  65. package/dist/{wstack-paths-_lqjzErq.d.ts → wstack-paths-DD50Omgn.d.ts} +3 -0
  66. package/package.json +2 -2
  67. package/dist/models-registry-B6_KfS65.d.ts +0 -95
@@ -16,6 +16,17 @@ function expectDefined(value, label) {
16
16
 
17
17
  // src/utils/token-estimate.ts
18
18
  var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
19
+ var CALIBRATION_GLOBAL_KEY = "__global__";
20
+ var _cals = /* @__PURE__ */ new Map();
21
+ function calState(key) {
22
+ let state = _cals.get(key);
23
+ if (!state) {
24
+ state = { ratio: 1, count: 0, prevEst: 0 };
25
+ _cals.set(key, state);
26
+ }
27
+ return state;
28
+ }
29
+ var MIN_SAMPLES_FOR_CALIBRATION = 3;
19
30
  var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
20
31
  var ESTIMATE_CACHE_MAX_SIZE = 1e4;
21
32
  function getCachedEstimate(key, compute) {
@@ -47,10 +58,25 @@ function estimateToolResultTokens(content) {
47
58
  function estimateTextTokens(text) {
48
59
  return RoughTokenEstimate(text);
49
60
  }
61
+ function estimateMessageTokens(messages) {
62
+ let total = 0;
63
+ for (const m of messages) {
64
+ if (typeof m.content === "string") {
65
+ total += estimateTextTokens(m.content);
66
+ } else {
67
+ for (const b of m.content) {
68
+ if (b.type === "text") total += estimateTextTokens(b.text);
69
+ else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
70
+ else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
71
+ }
72
+ }
73
+ }
74
+ return total;
75
+ }
50
76
  function estimateToolDefTokens(tool) {
51
77
  return RoughTokenEstimate(tool.name) + RoughTokenEstimate(tool.description ?? "") + RoughTokenEstimate(JSON.stringify(tool.inputSchema));
52
78
  }
53
- function estimateRequestTokens(messages, systemPrompt, tools) {
79
+ function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
54
80
  let messagesTokens = 0;
55
81
  if (typeof messages === "string") {
56
82
  messagesTokens = RoughTokenEstimate(messages);
@@ -89,6 +115,7 @@ function estimateRequestTokens(messages, systemPrompt, tools) {
89
115
  toolsTokens += estimateToolDefTokens(t);
90
116
  }
91
117
  const total = messagesTokens + systemTokens + toolsTokens;
118
+ calState(calibrationKey).prevEst = total;
92
119
  return {
93
120
  messages: messagesTokens,
94
121
  systemPrompt: systemTokens,
@@ -96,8 +123,18 @@ function estimateRequestTokens(messages, systemPrompt, tools) {
96
123
  total
97
124
  };
98
125
  }
99
- function estimateRequestTokensCalibrated(messages, systemPrompt, tools) {
100
- const result = estimateRequestTokens(messages, systemPrompt, tools);
126
+ function estimateRequestTokensCalibrated(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
127
+ const result = estimateRequestTokens(messages, systemPrompt, tools, calibrationKey);
128
+ const cal = calState(calibrationKey);
129
+ if (cal.count >= MIN_SAMPLES_FOR_CALIBRATION) {
130
+ const safeRatio = Math.min(1.5, Math.max(0.5, cal.ratio));
131
+ return {
132
+ messages: Math.round(result.messages * safeRatio),
133
+ systemPrompt: Math.round(result.systemPrompt * safeRatio),
134
+ tools: Math.round(result.tools * safeRatio),
135
+ total: Math.round(result.total * safeRatio)
136
+ };
137
+ }
101
138
  return result;
102
139
  }
103
140
 
@@ -193,34 +230,149 @@ function isEmptyMessage(msg) {
193
230
  return msg.content.length === 0;
194
231
  }
195
232
 
233
+ // src/types/blocks.ts
234
+ function isTextBlock(b) {
235
+ return b.type === "text";
236
+ }
237
+
238
+ // src/execution/compaction-core.ts
239
+ var estimateMessages = estimateMessageTokens;
240
+ function hasTextContent(m) {
241
+ if (typeof m.content === "string") return m.content.trim().length > 0;
242
+ return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
243
+ }
244
+ function findPreserveStart(messages, preserveK) {
245
+ let pairCount = 0;
246
+ let preserveStart = messages.length;
247
+ for (let i = messages.length - 1; i >= 0 && pairCount < preserveK; i--) {
248
+ const m = messages[i];
249
+ if (!m) continue;
250
+ if (m.role === "user" || m.role === "assistant") {
251
+ pairCount++;
252
+ preserveStart = i;
253
+ }
254
+ }
255
+ for (let i = preserveStart; i < messages.length; i++) {
256
+ const m = messages[i];
257
+ if (!m || typeof m.content === "string" || !Array.isArray(m.content)) continue;
258
+ const hasToolUse2 = m.content.some((b) => b.type === "tool_use");
259
+ if (hasToolUse2 && i + 1 < messages.length) {
260
+ const next = messages[i + 1];
261
+ if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => b.type === "tool_result")) {
262
+ preserveStart = i + 1;
263
+ }
264
+ }
265
+ }
266
+ return preserveStart;
267
+ }
268
+ function eliseOldToolResults(messages, opts) {
269
+ const preserveStart = findPreserveStart(messages, opts.preserveK);
270
+ let saved = 0;
271
+ let changed = false;
272
+ const next = new Array(messages.length);
273
+ for (let i = 0; i < messages.length; i++) {
274
+ const msg = messages[i];
275
+ if (i >= preserveStart || !msg || !Array.isArray(msg.content)) {
276
+ next[i] = msg;
277
+ continue;
278
+ }
279
+ const original = msg.content;
280
+ const newContent = original.map((b) => {
281
+ if (b.type !== "tool_result") return b;
282
+ const tokens = estimateToolResultTokens(b.content);
283
+ if (tokens < opts.eliseThreshold) return b;
284
+ saved += tokens;
285
+ const elided = {
286
+ type: "tool_result",
287
+ tool_use_id: b.tool_use_id,
288
+ content: `[elided: ~${tokens} tokens]`,
289
+ is_error: b.is_error
290
+ };
291
+ return elided;
292
+ });
293
+ if (newContent.every((b, idx) => b === original[idx])) {
294
+ next[i] = msg;
295
+ } else {
296
+ next[i] = { ...msg, content: newContent };
297
+ changed = true;
298
+ }
299
+ }
300
+ return { messages: changed ? next : messages, saved, changed };
301
+ }
302
+ function buildLosslessDigest(messages) {
303
+ const lines = [];
304
+ for (const m of messages) {
305
+ let text;
306
+ let omitted = 0;
307
+ if (typeof m.content === "string") {
308
+ text = m.content;
309
+ } else {
310
+ const parts = [];
311
+ for (const b of m.content) {
312
+ if (isTextBlock(b)) parts.push(b.text);
313
+ else if (b.type === "tool_use" || b.type === "tool_result") omitted++;
314
+ }
315
+ text = parts.join(" ");
316
+ }
317
+ if (text.trim().length === 0 && omitted === 0) continue;
318
+ const marker = omitted > 0 ? ` [${omitted} tool call(s) omitted \u2014 see session log]` : "";
319
+ lines.push(`[${m.role}]: ${text}${marker}`);
320
+ }
321
+ return lines.join("\n");
322
+ }
323
+ function findSafeBoundary(messages, from, to) {
324
+ for (let i = to; i >= from; i--) {
325
+ const m = messages[i];
326
+ if (!m) continue;
327
+ if (m.role === "user" && hasTextContent(m)) {
328
+ return findExchangeStart(messages, i);
329
+ }
330
+ }
331
+ return -1;
332
+ }
333
+ function findExchangeStart(messages, userIndex) {
334
+ for (let i = userIndex - 1; i >= 0; i--) {
335
+ const m = messages[i];
336
+ if (!m) continue;
337
+ if (m.role === "assistant") {
338
+ const hasToolUse2 = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
339
+ if (!hasToolUse2) return i + 1;
340
+ } else if (m.role === "user") {
341
+ return i;
342
+ }
343
+ }
344
+ return 0;
345
+ }
346
+
196
347
  // src/execution/compactor.ts
197
348
  var HybridCompactor = class {
198
349
  preserveK;
199
350
  eliseThreshold;
200
- estimator;
201
351
  constructor(opts = {}) {
202
352
  this.preserveK = opts.preserveK ?? 5;
203
353
  this.eliseThreshold = opts.eliseThreshold ?? 2e3;
204
- this.estimator = opts.estimator ?? estimateTextTokens;
205
354
  }
206
355
  async compact(ctx, opts = {}) {
207
- const beforeTokens = this.estimateMessages(ctx.messages);
356
+ const beforeTokens = estimateMessages(ctx.messages);
208
357
  const beforeFull = this.estimateFullRequest(ctx);
209
358
  const reductions = [];
210
359
  const policy = readContextWindowPolicy(ctx);
211
360
  const preserveK = policy?.preserveK ?? this.preserveK;
212
361
  const eliseThreshold = policy?.eliseThreshold ?? this.eliseThreshold;
213
- const phase1Saved = this.eliseOldToolResults(ctx, preserveK, eliseThreshold);
214
- if (phase1Saved > 0) reductions.push({ phase: "elision", saved: phase1Saved });
362
+ const elide = eliseOldToolResults(ctx.messages, { preserveK, eliseThreshold });
363
+ if (elide.changed) ctx.state.replaceMessages(elide.messages);
364
+ if (elide.saved > 0) reductions.push({ phase: "elision", saved: elide.saved });
365
+ let collapsedDigest;
215
366
  if (opts.aggressive) {
216
- const phase2Saved = this.collapseAncientTurns(ctx, preserveK);
217
- if (phase2Saved > 0) reductions.push({ phase: "summary", saved: phase2Saved });
367
+ const phase2 = this.collapseAncientTurns(ctx, preserveK);
368
+ if (phase2.saved > 0) reductions.push({ phase: "summary", saved: phase2.saved });
369
+ collapsedDigest = phase2.digest;
218
370
  }
219
371
  const repaired = repairToolUseAdjacency(ctx.messages);
220
372
  if (repaired.report.changed) {
221
373
  ctx.state.replaceMessages(repaired.messages);
222
374
  }
223
- const afterTokens = this.estimateMessages(ctx.messages);
375
+ const afterTokens = estimateMessages(ctx.messages);
224
376
  const afterFull = this.estimateFullRequest(ctx);
225
377
  return {
226
378
  before: beforeTokens,
@@ -228,6 +380,7 @@ var HybridCompactor = class {
228
380
  fullRequestTokensBefore: beforeFull,
229
381
  fullRequestTokensAfter: afterFull,
230
382
  reductions,
383
+ collapsedDigest,
231
384
  repaired: repaired.report.changed ? {
232
385
  removedToolUses: repaired.report.removedToolUses,
233
386
  removedToolResults: repaired.report.removedToolResults,
@@ -240,72 +393,23 @@ var HybridCompactor = class {
240
393
  * This is the accurate figure for context-window pressure monitoring.
241
394
  */
242
395
  estimateFullRequest(ctx) {
243
- const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
244
- return breakdown.total;
245
- }
246
- eliseOldToolResults(ctx, preserveK = this.preserveK, eliseThreshold = this.eliseThreshold) {
247
- const messages = ctx.messages;
248
- let pairCount = 0;
249
- let preserveStart = messages.length;
250
- for (let i = messages.length - 1; i >= 0 && pairCount < preserveK; i--) {
251
- const m = messages[i];
252
- if (!m) continue;
253
- if (m.role === "user" || m.role === "assistant") {
254
- pairCount++;
255
- preserveStart = i;
256
- }
257
- }
258
- for (let i = preserveStart; i < messages.length; i++) {
259
- const m = messages[i];
260
- if (!m || typeof m.content === "string" || !Array.isArray(m.content)) continue;
261
- const hasToolUse2 = m.content.some((b) => b.type === "tool_use");
262
- if (hasToolUse2 && i + 1 < messages.length) {
263
- const next = messages[i + 1];
264
- if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => b.type === "tool_result")) {
265
- preserveStart = i + 1;
266
- }
267
- }
268
- }
269
- let saved = 0;
270
- let changed = false;
271
- const nextMessages = new Array(messages.length);
272
- for (let i = 0; i < messages.length; i++) {
273
- const msg = messages[i];
274
- if (i >= preserveStart) {
275
- nextMessages[i] = msg;
276
- continue;
277
- }
278
- if (!msg || !Array.isArray(msg.content)) {
279
- nextMessages[i] = msg;
280
- continue;
281
- }
282
- const newContent = msg.content.map((b) => {
283
- if (b.type !== "tool_result") return b;
284
- const tokens = estimateToolResultTokens(b.content);
285
- if (tokens < eliseThreshold) return b;
286
- saved += tokens;
287
- const elided = {
288
- type: "tool_result",
289
- tool_use_id: b.tool_use_id,
290
- content: `[elided: ~${tokens} tokens]`,
291
- is_error: b.is_error
292
- };
293
- return elided;
294
- });
295
- if (newContent.length === msg.content.length && newContent.every((b, idx) => b === msg.content[idx])) {
296
- nextMessages[i] = msg;
297
- } else {
298
- nextMessages[i] = { ...msg, content: newContent };
299
- changed = true;
300
- }
301
- }
302
- if (changed) ctx.state.replaceMessages(nextMessages);
303
- return saved;
396
+ return estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
304
397
  }
398
+ /**
399
+ * Lossless rule-based collapse of ancient turns into a single digest message.
400
+ *
401
+ * Preserves ALL textual content of the collapsed range — user instructions,
402
+ * assistant decisions/conclusions, and any prior digests (chained forward so
403
+ * the digest stays lossless across repeated compactions). Only `tool_use` /
404
+ * `tool_result` protocol blocks are dropped and replaced with a count marker;
405
+ * their full payload already lives in the session log. No sub-LLM call.
406
+ *
407
+ * Returns the token savings and the digest text (for audit logging).
408
+ */
305
409
  collapseAncientTurns(ctx, preserveK = this.preserveK) {
306
410
  const messages = ctx.messages;
307
411
  const cutTarget = Math.max(0, messages.length - preserveK * 2);
308
- if (cutTarget <= 0) return 0;
412
+ if (cutTarget <= 0) return { saved: 0 };
309
413
  let boundary = -1;
310
414
  for (let i = cutTarget; i < messages.length; i++) {
311
415
  const m = messages[i];
@@ -315,34 +419,20 @@ var HybridCompactor = class {
315
419
  break;
316
420
  }
317
421
  }
318
- if (boundary <= 0) return 0;
422
+ if (boundary <= 0) return { saved: 0 };
319
423
  const removed = messages.slice(0, boundary);
320
- const removedTokens = this.estimateMessages(removed);
321
- const summary = [
322
- {
323
- role: "user",
324
- content: `[previous_session_summary: ${removed.length} earlier turns compacted. Todo state preserved in context.]`
325
- },
326
- { role: "assistant", content: "Continuing from compacted context." }
327
- ];
424
+ const removedTokens = estimateMessages(removed);
425
+ const digest = buildLosslessDigest(removed) || `${removed.length} earlier turns (no textual content; tool I/O omitted \u2014 see session log)`;
426
+ const summaryMsg = {
427
+ role: "system",
428
+ content: `[prior_turns_digest: ${digest}]`
429
+ };
328
430
  const tail = ctx.messages.slice(boundary);
329
- ctx.state.replaceMessages([...summary, ...tail]);
330
- return Math.max(0, removedTokens - this.estimateMessages(summary));
331
- }
332
- estimateMessages(messages) {
333
- let total = 0;
334
- for (const m of messages) {
335
- if (typeof m.content === "string") {
336
- total += this.estimator(m.content);
337
- } else {
338
- for (const b of m.content) {
339
- if (b.type === "text") total += this.estimator(b.text);
340
- else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
341
- else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
342
- }
343
- }
344
- }
345
- return total;
431
+ ctx.state.replaceMessages([summaryMsg, ...tail]);
432
+ return {
433
+ saved: Math.max(0, removedTokens - estimateMessages([summaryMsg])),
434
+ digest
435
+ };
346
436
  }
347
437
  };
348
438
  function readContextWindowPolicy(ctx) {
@@ -354,15 +444,6 @@ function readContextWindowPolicy(ctx) {
354
444
  }
355
445
  return candidate;
356
446
  }
357
- function hasTextContent(m) {
358
- if (typeof m.content === "string") return m.content.trim().length > 0;
359
- return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
360
- }
361
-
362
- // src/types/blocks.ts
363
- function isTextBlock(b) {
364
- return b.type === "text";
365
- }
366
447
 
367
448
  // src/execution/intelligent-compactor.ts
368
449
  var IntelligentCompactor = class {
@@ -387,23 +468,25 @@ var IntelligentCompactor = class {
387
468
  this.summarizerModel = opts.summarizerModel;
388
469
  }
389
470
  async compact(ctx, opts = {}) {
390
- const beforeTokens = this.estimateTokens(ctx.messages);
471
+ const beforeTokens = estimateMessages(ctx.messages);
391
472
  const beforeFull = this.estimateFullRequest(ctx);
392
473
  const reductions = [];
393
474
  const load = beforeFull / this.maxContext;
394
475
  const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
395
- const saved1 = this.eliseOldToolResults(ctx);
476
+ const saved1 = this.elide(ctx);
396
477
  if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
478
+ let collapsedDigest;
397
479
  if (aggressive) {
398
- const saved2 = await this.summarizeAncientTurns(ctx);
399
- if (saved2 > 0) reductions.push({ phase: "summary", saved: saved2 });
480
+ const phase2 = await this.summarizeAncientTurns(ctx);
481
+ if (phase2.saved > 0) reductions.push({ phase: "summary", saved: phase2.saved });
482
+ collapsedDigest = phase2.digest;
400
483
  } else if (load >= this.warnThreshold) {
401
- const saved2 = this.lightweightCompact(ctx);
484
+ const saved2 = this.elide(ctx);
402
485
  if (saved2 > 0) reductions.push({ phase: "elision", saved: saved2 });
403
486
  }
404
487
  const repaired = repairToolUseAdjacency(ctx.messages);
405
488
  if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
406
- const afterTokens = this.estimateTokens(ctx.messages);
489
+ const afterTokens = estimateMessages(ctx.messages);
407
490
  const afterFull = this.estimateFullRequest(ctx);
408
491
  return {
409
492
  before: beforeTokens,
@@ -411,6 +494,7 @@ var IntelligentCompactor = class {
411
494
  fullRequestTokensBefore: beforeFull,
412
495
  fullRequestTokensAfter: afterFull,
413
496
  reductions,
497
+ collapsedDigest,
414
498
  repaired: repaired.report.changed ? {
415
499
  removedToolUses: repaired.report.removedToolUses,
416
500
  removedToolResults: repaired.report.removedToolResults,
@@ -423,75 +507,39 @@ var IntelligentCompactor = class {
423
507
  * This is the accurate figure for context-window pressure monitoring.
424
508
  */
425
509
  estimateFullRequest(ctx) {
426
- const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
427
- return breakdown.total;
510
+ return estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
511
+ }
512
+ /** Run shared tool-result elision and commit through ConversationState. */
513
+ elide(ctx) {
514
+ const result = eliseOldToolResults(ctx.messages, {
515
+ preserveK: this.preserveK,
516
+ eliseThreshold: this.eliseThreshold
517
+ });
518
+ if (result.changed) ctx.state.replaceMessages(result.messages);
519
+ return result.saved;
428
520
  }
429
521
  async summarizeAncientTurns(ctx) {
430
522
  const messages = ctx.messages;
431
523
  const cutoff = Math.max(0, messages.length - this.preserveK * 2);
432
- if (cutoff <= 2) return 0;
433
- const boundary = this.findSafeBoundary(messages, 0, cutoff);
434
- if (boundary <= 1) return 0;
524
+ if (cutoff <= 2) return { saved: 0 };
525
+ const boundary = findSafeBoundary(messages, 0, cutoff);
526
+ if (boundary <= 1) return { saved: 0 };
435
527
  const toSummarize = messages.slice(0, boundary);
436
- const removedTokens = this.estimateTokens(toSummarize);
528
+ const removedTokens = estimateMessages(toSummarize);
437
529
  let summaryText;
438
530
  try {
439
531
  summaryText = await this.callSummarizer(toSummarize, ctx);
440
532
  } catch {
441
- const preservedMessages = [];
442
- for (const m of toSummarize) {
443
- if (m.role === "system") continue;
444
- if (typeof m.content === "string") {
445
- preservedMessages.push(m);
446
- } else if (Array.isArray(m.content)) {
447
- const textParts = m.content.filter(isTextBlock);
448
- const toolParts = m.content.filter((b) => b.type === "tool_use" || b.type === "tool_result");
449
- const next = {
450
- role: m.role,
451
- content: [
452
- ...textParts,
453
- ...toolParts.length > 0 ? [{ type: "text", text: `[${toolParts.length} tool call(s) omitted]` }] : []
454
- ]
455
- };
456
- preservedMessages.push(next);
457
- }
458
- }
459
- summaryText = preservedMessages.map((m) => `[${m.role}]: ${typeof m.content === "string" ? m.content : m.content.filter(isTextBlock).map((b) => b.text).join(" ")}`).join("\n");
460
- if (!summaryText) summaryText = `${toSummarize.length} earlier turns (semantic content preserved)`;
533
+ summaryText = buildLosslessDigest(toSummarize) || `${toSummarize.length} earlier turns (semantic content preserved)`;
461
534
  }
462
535
  const summaryMsg = {
463
536
  role: "system",
464
537
  content: `[prior_turns_summary: ${summaryText}]`
465
538
  };
466
- const summaryTokens = this.estimateTokens([summaryMsg]);
539
+ const summaryTokens = estimateMessages([summaryMsg]);
467
540
  const tail = ctx.messages.slice(boundary);
468
541
  ctx.state.replaceMessages([summaryMsg, ...tail]);
469
- return Math.max(0, removedTokens - summaryTokens);
470
- }
471
- findSafeBoundary(messages, from, to) {
472
- for (let i = to; i >= from; i--) {
473
- const m = messages[i];
474
- if (!m) continue;
475
- if (m.role === "user" && this.hasTextContent(m)) {
476
- return this.findExchangeStart(messages, i);
477
- }
478
- }
479
- return -1;
480
- }
481
- findExchangeStart(messages, userIndex) {
482
- for (let i = userIndex - 1; i >= 0; i--) {
483
- const m = messages[i];
484
- if (!m) continue;
485
- if (m.role === "assistant") {
486
- const hasToolUse2 = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
487
- if (!hasToolUse2) {
488
- return i + 1;
489
- }
490
- } else if (m.role !== "user") ; else {
491
- return i;
492
- }
493
- }
494
- return 0;
542
+ return { saved: Math.max(0, removedTokens - summaryTokens), digest: summaryText };
495
543
  }
496
544
  async callSummarizer(messages, ctx) {
497
545
  const prompt = [
@@ -526,86 +574,6 @@ var IntelligentCompactor = class {
526
574
  }
527
575
  return [{ type: "text", text: lines.join("\n") }];
528
576
  }
529
- lightweightCompact(ctx) {
530
- return this.eliseOldToolResults(ctx);
531
- }
532
- eliseOldToolResults(ctx) {
533
- const messages = ctx.messages;
534
- let pairCount = 0;
535
- let preserveStart = messages.length;
536
- for (let i = messages.length - 1; i >= 0 && pairCount < this.preserveK; i--) {
537
- const m = messages[i];
538
- if (!m) continue;
539
- if (m.role === "user" || m.role === "assistant") {
540
- pairCount++;
541
- preserveStart = i;
542
- }
543
- }
544
- for (let i = preserveStart; i < messages.length; i++) {
545
- const m = messages[i];
546
- if (!m || typeof m.content === "string" || !Array.isArray(m.content)) continue;
547
- const hasToolUse2 = m.content.some((b) => b.type === "tool_use");
548
- if (hasToolUse2 && i + 1 < messages.length) {
549
- const next = messages[i + 1];
550
- if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => b.type === "tool_result")) {
551
- preserveStart = i + 1;
552
- }
553
- }
554
- }
555
- let saved = 0;
556
- let changed = false;
557
- const nextMessages = new Array(messages.length);
558
- for (let i = 0; i < messages.length; i++) {
559
- const msg = messages[i];
560
- if (i >= preserveStart) {
561
- nextMessages[i] = msg;
562
- continue;
563
- }
564
- if (!msg || !Array.isArray(msg.content)) {
565
- nextMessages[i] = msg;
566
- continue;
567
- }
568
- const newContent = msg.content.map((b) => {
569
- if (b.type !== "tool_result") return b;
570
- const tokens = estimateToolResultTokens(b.content);
571
- if (tokens < this.eliseThreshold) return b;
572
- saved += tokens;
573
- return {
574
- type: "tool_result",
575
- tool_use_id: b.tool_use_id,
576
- content: `[elided: ~${tokens} tokens]`,
577
- is_error: b.is_error
578
- };
579
- });
580
- if (newContent.length === msg.content.length && newContent.every((b, idx) => b === msg.content[idx])) {
581
- nextMessages[i] = msg;
582
- } else {
583
- nextMessages[i] = { ...msg, content: newContent };
584
- changed = true;
585
- }
586
- }
587
- if (changed) ctx.state.replaceMessages(nextMessages);
588
- return saved;
589
- }
590
- hasTextContent(m) {
591
- if (typeof m.content === "string") return m.content.trim().length > 0;
592
- return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
593
- }
594
- estimateTokens(messages) {
595
- let total = 0;
596
- for (const m of messages) {
597
- if (typeof m.content === "string") {
598
- total += estimateTextTokens(m.content);
599
- } else {
600
- for (const b of m.content) {
601
- if (b.type === "text") total += estimateTextTokens(b.text);
602
- else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
603
- else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
604
- }
605
- }
606
- }
607
- return total;
608
- }
609
577
  };
610
578
 
611
579
  // src/models/llm-selector.ts
@@ -631,20 +599,6 @@ Rules:
631
599
  - If unsure, keep rather than collapse (errors are more costly than waste)
632
600
 
633
601
  Return ONLY the JSON object, no markdown, no explanation outside the JSON.`;
634
- function estimateTokens(messages) {
635
- let total = 0;
636
- for (const m of messages) {
637
- if (typeof m.content === "string") {
638
- total += Math.ceil(m.content.length / 4);
639
- } else if (Array.isArray(m.content)) {
640
- for (const b of m.content) {
641
- if (b.type === "text") total += Math.ceil(b.text.length / 4);
642
- else total += Math.ceil(JSON.stringify(b).length / 4);
643
- }
644
- }
645
- }
646
- return total;
647
- }
648
602
  function formatMessages(messages, maxChars = 8e3) {
649
603
  const lines = [];
650
604
  let used = 0;
@@ -683,7 +637,7 @@ var LLMSelector = class {
683
637
  async select(messages, maxToKeep) {
684
638
  const effectiveBudget = Math.min(maxToKeep, this.maxContextTokens);
685
639
  const historyText = formatMessages(messages);
686
- const totalTokens = estimateTokens(messages);
640
+ const totalTokens = estimateMessageTokens(messages);
687
641
  const systemText = `${this.systemPrompt}
688
642
 
689
643
  Conversation (${messages.length} messages, ~${totalTokens} tokens, budget: ${effectiveBudget}):
@@ -715,10 +669,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
715
669
  let startIdx = 0;
716
670
  for (let i = messages.length - 1; i >= 0; i--) {
717
671
  const m = expectDefined(messages[i]);
718
- const cost = typeof m.content === "string" ? Math.ceil(m.content.length / 4) : m.content.reduce(
719
- (acc, b) => acc + (b.type === "text" ? Math.ceil(b.text.length / 4) : Math.ceil(JSON.stringify(b).length / 4)),
720
- 0
721
- );
672
+ const cost = estimateMessageTokens([m]);
722
673
  if (tokenCount + cost <= budget) {
723
674
  tokenCount += cost;
724
675
  } else {
@@ -951,52 +902,12 @@ Summarize the following message range:`;
951
902
  return Math.floor(this.maxContext * 0.75);
952
903
  }
953
904
  eliseOldToolResults(ctx) {
954
- const messages = ctx.messages;
955
- let pairCount = 0;
956
- let preserveStart = messages.length;
957
- for (let i = messages.length - 1; i >= 0 && pairCount < this.preserveK; i--) {
958
- const m = messages[i];
959
- if (!m) continue;
960
- if (m.role === "user" || m.role === "assistant") {
961
- pairCount++;
962
- preserveStart = i;
963
- }
964
- }
965
- let saved = 0;
966
- let changed = false;
967
- const nextMessages = new Array(messages.length);
968
- for (let i = 0; i < messages.length; i++) {
969
- const msg = messages[i];
970
- if (i >= preserveStart) {
971
- nextMessages[i] = msg;
972
- continue;
973
- }
974
- if (!msg || !Array.isArray(msg.content)) {
975
- nextMessages[i] = msg;
976
- continue;
977
- }
978
- const newContent = msg.content.map((b) => {
979
- if (b.type !== "tool_result") return b;
980
- const text = typeof b.content === "string" ? b.content : JSON.stringify(b.content);
981
- const tokens = this.roughTokenEstimate(text);
982
- if (tokens < this.eliseThreshold) return b;
983
- saved += tokens;
984
- return {
985
- type: "tool_result",
986
- tool_use_id: b.tool_use_id,
987
- content: `[elided: ~${tokens} tokens]`,
988
- is_error: b.is_error
989
- };
990
- });
991
- if (newContent.every((b, idx) => b === msg.content[idx])) {
992
- nextMessages[i] = msg;
993
- } else {
994
- nextMessages[i] = { ...msg, content: newContent };
995
- changed = true;
996
- }
997
- }
998
- if (changed) ctx.state.replaceMessages(nextMessages);
999
- return saved;
905
+ const result = eliseOldToolResults(ctx.messages, {
906
+ preserveK: this.preserveK,
907
+ eliseThreshold: this.eliseThreshold
908
+ });
909
+ if (result.changed) ctx.state.replaceMessages(result.messages);
910
+ return result.saved;
1000
911
  }
1001
912
  hasTextContent(m) {
1002
913
  if (typeof m.content === "string") return m.content.trim().length > 0;
@@ -1026,6 +937,73 @@ Summarize the following message range:`;
1026
937
  }
1027
938
  };
1028
939
 
940
+ // src/execution/strategy-compactor.ts
941
+ function createStrategyCompactor(opts = {}) {
942
+ const requested = opts.strategy ?? (opts.llmSelector ? "selective" : "hybrid");
943
+ const strategy = requested;
944
+ if (strategy === "intelligent" || strategy === "selective") {
945
+ return new ProviderBackedCompactor(strategy, opts);
946
+ }
947
+ return new HybridCompactor({
948
+ preserveK: opts.preserveK,
949
+ eliseThreshold: opts.eliseThreshold
950
+ });
951
+ }
952
+ var ProviderBackedCompactor = class {
953
+ constructor(strategy, opts) {
954
+ this.strategy = strategy;
955
+ this.opts = opts;
956
+ }
957
+ strategy;
958
+ opts;
959
+ async compact(ctx, compactOpts = {}) {
960
+ return this.resolveInner(ctx).compact(ctx, compactOpts);
961
+ }
962
+ /**
963
+ * Construct the concrete compactor for this run. Rebuilt per call (cheap, no
964
+ * I/O) so a model switch — which changes `ctx.provider.capabilities.maxContext`
965
+ * — is always reflected. Reads the active ContextWindowPolicy from `ctx.meta`
966
+ * so the LLM compactors honor the same thresholds/preserveK as the policy.
967
+ */
968
+ resolveInner(ctx) {
969
+ const provider = ctx.provider;
970
+ if (!provider) {
971
+ return new HybridCompactor({
972
+ preserveK: this.opts.preserveK,
973
+ eliseThreshold: this.opts.eliseThreshold
974
+ });
975
+ }
976
+ const policy = readPolicy(ctx);
977
+ const maxContext = provider.capabilities?.maxContext || void 0;
978
+ const thresholds = policy?.thresholds;
979
+ const common = {
980
+ provider,
981
+ maxContext,
982
+ preserveK: this.opts.preserveK ?? policy?.preserveK,
983
+ eliseThreshold: this.opts.eliseThreshold ?? policy?.eliseThreshold,
984
+ ...thresholds ? { warnThreshold: thresholds.warn, softThreshold: thresholds.soft, hardThreshold: thresholds.hard } : {}
985
+ };
986
+ if (this.strategy === "selective") {
987
+ return new SelectiveCompactor({
988
+ ...common,
989
+ selectorModel: this.opts.summarizerModel,
990
+ summarizerModel: this.opts.summarizerModel
991
+ });
992
+ }
993
+ return new IntelligentCompactor({
994
+ ...common,
995
+ summarizerModel: this.opts.summarizerModel
996
+ });
997
+ }
998
+ };
999
+ function readPolicy(ctx) {
1000
+ const policy = ctx.meta?.["contextWindowPolicy"];
1001
+ if (!policy || typeof policy !== "object") return null;
1002
+ const candidate = policy;
1003
+ if (typeof candidate.preserveK !== "number" || !candidate.thresholds) return null;
1004
+ return candidate;
1005
+ }
1006
+
1029
1007
  // src/types/errors.ts
1030
1008
  var ERROR_CODES = {
1031
1009
  // Provider
@@ -1113,6 +1091,11 @@ var FsError = class extends WrongStackError {
1113
1091
 
1114
1092
  // src/execution/auto-compaction-middleware.ts
1115
1093
  var LEVEL_RANK = { warn: 0, soft: 1, hard: 2 };
1094
+ var MAX_DIGEST_LOG_CHARS = 4e3;
1095
+ function truncateDigest(digest) {
1096
+ if (digest.length <= MAX_DIGEST_LOG_CHARS) return digest;
1097
+ return `${digest.slice(0, MAX_DIGEST_LOG_CHARS)}\u2026 [+${digest.length - MAX_DIGEST_LOG_CHARS} chars; full turns in session log]`;
1098
+ }
1116
1099
  var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1117
1100
  name = "AutoCompaction";
1118
1101
  compactor;
@@ -1173,7 +1156,12 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1173
1156
  }
1174
1157
  handler() {
1175
1158
  return async (ctx, next) => {
1176
- const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokensCalibrated(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
1159
+ const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokensCalibrated(
1160
+ ctx.messages,
1161
+ ctx.systemPrompt,
1162
+ ctx.tools ?? [],
1163
+ `${ctx.provider?.id ?? "unknown"}/${ctx.model}`
1164
+ ).total;
1177
1165
  const load = tokens / this._maxContext;
1178
1166
  const policy = this.policyProvider?.(ctx);
1179
1167
  const thresholds = policy?.thresholds ?? {
@@ -1235,7 +1223,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1235
1223
  after: report.after,
1236
1224
  level: pressure.level,
1237
1225
  aggressive,
1238
- reductions: report.reductions?.map((r) => ({ phase: r.phase, saved: r.saved }))
1226
+ reductions: report.reductions?.map((r) => ({ phase: r.phase, saved: r.saved })),
1227
+ // Record what was collapsed so the audit trail shows the preserved
1228
+ // content, not just token counts. Bounded to keep the log line small;
1229
+ // the full original turns are already in the session JSONL.
1230
+ ...report.collapsedDigest ? { digest: truncateDigest(report.collapsedDigest) } : {}
1239
1231
  });
1240
1232
  ctx.clearFileTracking();
1241
1233
  } catch (err) {
@@ -1271,6 +1263,8 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1271
1263
  var ToolCapabilities = {
1272
1264
  /** Can execute arbitrary commands in the user's shell (the `bash` tool). */
1273
1265
  SHELL_ARBITRARY: "shell.arbitrary",
1266
+ /** Can execute a restricted set of commands (the `exec` tool). */
1267
+ SHELL_RESTRICTED: "shell.restricted",
1274
1268
  /** Can write / modify / delete files inside the project. */
1275
1269
  FS_WRITE: "fs.write",
1276
1270
  /** Can write files outside the current project root (very high risk). */
@@ -1286,6 +1280,7 @@ var ToolCapabilities = {
1286
1280
  };
1287
1281
  var DANGEROUS_FOR_SUBAGENTS = [
1288
1282
  ToolCapabilities.SHELL_ARBITRARY,
1283
+ ToolCapabilities.SHELL_RESTRICTED,
1289
1284
  ToolCapabilities.FS_WRITE,
1290
1285
  ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
1291
1286
  ToolCapabilities.MCP_PROXY,
@@ -1863,6 +1858,13 @@ function extractMalformedRaw(input) {
1863
1858
  }
1864
1859
  }
1865
1860
 
1861
+ // src/utils/assert-never.ts
1862
+ function assertNever(x, message) {
1863
+ throw new Error(
1864
+ `Unhandled case: ${JSON.stringify(x)}`
1865
+ );
1866
+ }
1867
+
1866
1868
  // src/utils/regex-guard.ts
1867
1869
  var MAX_PATTERN_LEN = 512;
1868
1870
  var DANGEROUS_PATTERNS = [
@@ -1952,6 +1954,12 @@ var DoneConditionChecker = class {
1952
1954
  return { done: true, reason: `max tool calls (${this.condition.maxToolCalls}) reached`, ...state };
1953
1955
  }
1954
1956
  break;
1957
+ case "all_tasks_done":
1958
+ break;
1959
+ case "custom":
1960
+ break;
1961
+ default:
1962
+ return assertNever(this.condition.type);
1955
1963
  }
1956
1964
  return { done: false, iterations: state.iterations, toolCalls: state.toolCalls };
1957
1965
  }
@@ -2161,6 +2169,7 @@ function resolveWstackPaths(opts) {
2161
2169
  projectTrust: path2.join(projectDir, "trust.json"),
2162
2170
  projectMeta: path2.join(projectDir, "meta.json"),
2163
2171
  projectLocalConfig: path2.join(projectDir, "config.local.json"),
2172
+ inProjectConfig: path2.join(opts.projectRoot, ".wrongstack", "config.json"),
2164
2173
  inProjectAgentsFile: path2.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
2165
2174
  inProjectSkills: path2.join(opts.projectRoot, ".wrongstack", "skills"),
2166
2175
  inProjectWorktrees: path2.join(opts.projectRoot, ".wrongstack", "worktrees"),
@@ -2185,16 +2194,20 @@ async function loadGoal(filePath) {
2185
2194
  let raw;
2186
2195
  try {
2187
2196
  raw = await fs.readFile(filePath, "utf8");
2188
- } catch {
2189
- return null;
2197
+ } catch (err) {
2198
+ const code = err.code;
2199
+ if (code === "ENOENT") return null;
2200
+ throw err;
2190
2201
  }
2191
2202
  try {
2192
2203
  const parsed = JSON.parse(raw);
2193
2204
  if (parsed?.version !== 1 || typeof parsed.goal !== "string" || !Array.isArray(parsed.journal)) {
2205
+ console.warn(`[goal-store] Corrupt goal.json at ${filePath} \u2014 invalid schema. Consider deleting it and re-creating.`);
2194
2206
  return null;
2195
2207
  }
2196
2208
  return parsed;
2197
2209
  } catch {
2210
+ console.warn(`[goal-store] Corrupt goal.json at ${filePath} \u2014 JSON parse failed. Consider deleting it and re-creating.`);
2198
2211
  return null;
2199
2212
  }
2200
2213
  }
@@ -2227,9 +2240,9 @@ function parseProgressFromText(text) {
2227
2240
  const re = /\[progress:\s*(\d{1,3})%\]\s*(?:[—\-]\s*(.+))?/i;
2228
2241
  const m = text.match(re);
2229
2242
  if (!m) return null;
2230
- const progress = Math.min(100, Math.max(0, Number.parseInt(m[1], 10)));
2243
+ const progress = Math.min(100, Math.max(0, Number.parseInt(m[1] ?? "0", 10)));
2231
2244
  const note = m[2]?.trim() || void 0;
2232
- return { progress, note };
2245
+ return note === void 0 ? { progress } : { progress, note };
2233
2246
  }
2234
2247
  function recordProgress(goal, progress, note) {
2235
2248
  const clamped = Math.min(100, Math.max(0, progress));
@@ -2248,7 +2261,7 @@ function computeTrend(history) {
2248
2261
  const recent = history.slice(-5);
2249
2262
  const deltas = [];
2250
2263
  for (let i = 1; i < recent.length; i++) {
2251
- deltas.push(recent[i].progress - recent[i - 1].progress);
2264
+ deltas.push((recent[i]?.progress ?? 0) - (recent[i - 1]?.progress ?? 0));
2252
2265
  }
2253
2266
  if (deltas.length < 2) return void 0;
2254
2267
  const avgDelta = deltas.reduce((a, b) => a + b, 0) / deltas.length;
@@ -2994,6 +3007,7 @@ ${recentJournal}` : "No prior iterations.",
2994
3007
  note: note.slice(0, 240)
2995
3008
  });
2996
3009
  await saveGoal(this.goalPath, withEntry);
3010
+ this.opts.onEternalStop?.();
2997
3011
  }
2998
3012
  /**
2999
3013
  * Manually clear the goal — equivalent to `/goal clear` typed by the user.
@@ -3007,8 +3021,8 @@ ${recentJournal}` : "No prior iterations.",
3007
3021
  await saveGoal(this.goalPath, abandoned);
3008
3022
  }
3009
3023
  try {
3010
- const { unlink: unlink2 } = await import('fs/promises');
3011
- await unlink2(this.goalPath);
3024
+ const { unlink: unlink3 } = await import('fs/promises');
3025
+ await unlink3(this.goalPath);
3012
3026
  } catch {
3013
3027
  }
3014
3028
  this.opts.onEternalStop?.();
@@ -4009,6 +4023,53 @@ Working rules:
4009
4023
  "flaws"
4010
4024
  ]
4011
4025
  }
4026
+ },
4027
+ {
4028
+ config: {
4029
+ id: "refactor-planner",
4030
+ name: "Refactor Planner",
4031
+ role: "refactor-planner",
4032
+ tools: [...PLAN_TOOLS, "diff"],
4033
+ prompt: `You are the Refactor Planner agent. Your job is to analyze code
4034
+ structure and produce a concrete, phased refactoring plan with risk
4035
+ assessment, dependency ordering, and rollback strategy.
4036
+
4037
+ Scope:
4038
+ - Map module-level dependencies (import graph)
4039
+ - Identify coupling hotspots (high fan-in/out modules)
4040
+ - Assess refactoring risk by complexity and test coverage
4041
+ - Generate phased plans with checkpoint milestones
4042
+ - Produce diff-friendly task lists (one task = one concern)
4043
+
4044
+ Input format you accept:
4045
+ { "task": "plan | assess | roadmap", "target": "src/core", "constraint": "no-breaking-changes | minimal-downtime | full-rewrite", "focus": "architecture | performance | maintainability" }
4046
+
4047
+ Output: Markdown refactor plan with phases (Low Risk / Medium Risk / High Risk),
4048
+ dependency graph, rollback strategy, and exit criteria.
4049
+
4050
+ Working rules:
4051
+ - Always include rollback strategy \u2014 every refactor can fail
4052
+ - Merge tasks that take <1h into a single phase
4053
+ - Respect team constraints (reviewer availability, parallelization)
4054
+ - Never plan without analyzing the actual code first`
4055
+ },
4056
+ budget: HEAVY_BUDGET,
4057
+ capability: {
4058
+ phase: "planning",
4059
+ summary: "Refactoring planner: analyzes code structure, maps dependencies, produces risk-scored phased plans with rollback strategy.",
4060
+ keywords: [
4061
+ "refactor",
4062
+ "refactoring",
4063
+ "restructure",
4064
+ "debt",
4065
+ "technical debt",
4066
+ "clean up",
4067
+ "modularize",
4068
+ "decouple",
4069
+ "dependency graph",
4070
+ "code structure"
4071
+ ]
4072
+ }
4012
4073
  }
4013
4074
  ];
4014
4075
 
@@ -4548,6 +4609,157 @@ Working rules:
4548
4609
  "robustness"
4549
4610
  ]
4550
4611
  }
4612
+ },
4613
+ {
4614
+ config: {
4615
+ id: "security-scanner",
4616
+ name: "Security Scanner",
4617
+ role: "security-scanner",
4618
+ tools: [...TOOLS.inspect],
4619
+ prompt: `You are the Security Scanner agent. Your job is to scan code,
4620
+ configs, and dependencies for security issues from hardcoded secrets to
4621
+ supply chain risks.
4622
+
4623
+ Scope:
4624
+ - Detect hardcoded secrets: API keys, tokens, passwords, private keys
4625
+ - Find injection vectors: eval, innerHTML, SQL concat, shell injection
4626
+ - Identify insecure patterns: weak crypto, hardcoded IVs, disabled TLS
4627
+ - Scan dependencies for known CVEs (via npm/pnpm audit)
4628
+ - Flag supply chain risks: postinstall hooks, unverified scripts
4629
+
4630
+ Input format you accept:
4631
+ { "task": "scan | audit | secrets | dependencies", "paths": ["src", "config"], "depth": "quick | normal | deep" }
4632
+
4633
+ Output: Markdown security report with severity-ranked findings, injection
4634
+ vectors, dependency issues, and a remediation checklist.
4635
+
4636
+ Working rules:
4637
+ - Never scan node_modules \u2014 use npm audit instead
4638
+ - Always provide remediation steps, not just findings
4639
+ - Verify regex-based secrets before flagging (false positive risk)
4640
+ - When in doubt, flag as medium rather than ignoring potential issues`
4641
+ },
4642
+ budget: HEAVY_BUDGET,
4643
+ capability: {
4644
+ phase: "verify",
4645
+ summary: "Security scanner: detects hardcoded secrets, injection vectors, insecure patterns, and supply-chain risks with remediation.",
4646
+ keywords: [
4647
+ "security",
4648
+ "scan",
4649
+ "vulnerability",
4650
+ "secret",
4651
+ "api key",
4652
+ "hardcoded",
4653
+ "injection",
4654
+ "cve",
4655
+ "audit dependencies",
4656
+ "supply chain",
4657
+ "xss",
4658
+ "sqli",
4659
+ "shell injection",
4660
+ "sensitive data",
4661
+ "credential"
4662
+ ]
4663
+ }
4664
+ },
4665
+ {
4666
+ config: {
4667
+ id: "bug-hunter",
4668
+ name: "Bug Hunter",
4669
+ role: "bug-hunter",
4670
+ tools: [...TOOLS.inspect],
4671
+ prompt: `You are the Bug Hunter agent. Your job is to systematically scan
4672
+ source code for bugs, anti-patterns, and code smells using pattern matching
4673
+ and heuristics. Output a prioritized hit list with file:line references.
4674
+
4675
+ Scope:
4676
+ - Detect common bug patterns (uncaught errors, resource leaks, race conditions)
4677
+ - Identify anti-patterns (callback hell, God objects, circular deps)
4678
+ - Find TypeScript-specific issues (unsafe any, missing null checks, branded types)
4679
+ - Flag security-sensitive constructs (eval, innerHTML, hardcoded secrets)
4680
+ - Rank findings: critical > high > medium > low
4681
+
4682
+ Input format you accept:
4683
+ { "task": "scan | hunt | check", "paths": ["src/**/*.ts"], "focus": "bugs | patterns | security | all", "severityThreshold": "medium" }
4684
+
4685
+ Output: Markdown bug hunt report with critically/high/medium/low sections.
4686
+ Each entry: **[TYPE]** \`file:line\` \u2014 description + suggested fix
4687
+
4688
+ Working rules:
4689
+ - Never scan node_modules \u2014 it's noise
4690
+ - Always include file:line for every finding
4691
+ - If >30% of findings are false positives, note the confidence level
4692
+ - Ask director for clarification if paths are ambiguous`
4693
+ },
4694
+ budget: HEAVY_BUDGET,
4695
+ capability: {
4696
+ phase: "verify",
4697
+ summary: "Bug hunter: scans source code for bugs, anti-patterns, and code smells, producing a file:line-ranked hit list with fixes.",
4698
+ keywords: [
4699
+ "bug",
4700
+ "hunt",
4701
+ "scan",
4702
+ "code smell",
4703
+ "anti-pattern",
4704
+ "race condition",
4705
+ "memory leak",
4706
+ "null deref",
4707
+ "type safety",
4708
+ "unhandled error",
4709
+ "find bugs",
4710
+ "audit code",
4711
+ "code quality"
4712
+ ]
4713
+ }
4714
+ },
4715
+ {
4716
+ config: {
4717
+ id: "audit-log",
4718
+ name: "Audit Log",
4719
+ role: "audit-log",
4720
+ tools: [...TOOLS.inspect],
4721
+ prompt: `You are the Audit Log agent. Your job is to analyze structured JSONL
4722
+ session logs and produce actionable markdown reports.
4723
+
4724
+ Scope:
4725
+ - Parse session logs (iteration counts, tool calls, errors, usage)
4726
+ - Detect repeated failure patterns across multiple runs
4727
+ - Identify tool usage anomalies (over-use, failures, unexpected chains)
4728
+ - Track token consumption trends
4729
+ - Generate structured audit reports with severity ratings
4730
+
4731
+ Input format you accept:
4732
+ { "task": "analyze | report | trends", "sessionPath": "<path>", "focus": "errors | tools | usage | all" }
4733
+
4734
+ Output: Markdown audit report with Summary, Top Errors, Tool Usage table,
4735
+ Anomalies, and Cost Trend sections.
4736
+
4737
+ Working rules:
4738
+ - Never fabricate numbers \u2014 read the actual logs first
4739
+ - Always include file:line references for errors
4740
+ - If sessionPath is missing, ask the director to provide it
4741
+ - Report confidence level: high (>90% accuracy), medium, low`
4742
+ },
4743
+ budget: MEDIUM_BUDGET,
4744
+ capability: {
4745
+ phase: "verify",
4746
+ summary: "Audit log analyzer: parses session JSONL, detects failure patterns, tool anomalies, and cost trends with structured reports.",
4747
+ keywords: [
4748
+ "audit",
4749
+ "log",
4750
+ "logs",
4751
+ "session",
4752
+ "trace",
4753
+ "analyze logs",
4754
+ "error patterns",
4755
+ "cost analysis",
4756
+ "tool usage",
4757
+ "token usage",
4758
+ "post-mortem",
4759
+ "trend",
4760
+ "anomaly"
4761
+ ]
4762
+ }
4551
4763
  }
4552
4764
  ];
4553
4765
 
@@ -7555,6 +7767,7 @@ var DefaultRetryPolicy = class {
7555
7767
  if (err instanceof ProviderError) {
7556
7768
  if (err.status === 429) return 5;
7557
7769
  if (err.status === 529) return 3;
7770
+ if (err.status === 599) return 5;
7558
7771
  if (err.status >= 500) return 3;
7559
7772
  return 0;
7560
7773
  }
@@ -7785,6 +7998,6 @@ function parseDescription(raw) {
7785
7998
  return { trigger, scope };
7786
7999
  }
7787
8000
 
7788
- export { AutoCompactionMiddleware, AutonomousRunner, DefaultErrorHandler, DefaultRetryPolicy, DefaultSkillLoader, DoneConditionChecker, EternalAutonomyEngine, HybridCompactor, IntelligentCompactor, ParallelEternalEngine, SelectiveCompactor, ToolExecutor, buildGoalPreamble, createAutonomyBrain, formatDecisionSummary, makeAutonomyPromptContributor };
8001
+ export { AutoCompactionMiddleware, AutonomousRunner, DefaultErrorHandler, DefaultRetryPolicy, DefaultSkillLoader, DoneConditionChecker, EternalAutonomyEngine, HybridCompactor, IntelligentCompactor, ParallelEternalEngine, SelectiveCompactor, ToolExecutor, buildGoalPreamble, createAutonomyBrain, createStrategyCompactor, formatDecisionSummary, makeAutonomyPromptContributor };
7789
8002
  //# sourceMappingURL=index.js.map
7790
8003
  //# sourceMappingURL=index.js.map