@wrongstack/core 0.119.1 → 0.148.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/{agent-bridge-PRAbEX-9.d.ts → agent-bridge-r9y6gdn4.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-DXWImnNV.d.ts → agent-subagent-runner-1GeQE_L0.d.ts} +7 -7
  3. package/dist/{brain-NZZqL8sn.d.ts → brain-Cp_3GIS2.d.ts} +14 -3
  4. package/dist/{compactor-C3stu664.d.ts → compactor-BueGt7LG.d.ts} +9 -1
  5. package/dist/{config-DZV9VK9n.d.ts → config-BaVThgnT.d.ts} +1 -1
  6. package/dist/{context-hvXINa6a.d.ts → context-C7G_MtLV.d.ts} +7 -0
  7. package/dist/coordination/index.d.ts +12 -12
  8. package/dist/coordination/index.js +35 -21
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +24 -24
  11. package/dist/defaults/index.js +869 -788
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/execution/index.d.ts +14 -14
  14. package/dist/execution/index.js +454 -321
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/extension/index.d.ts +6 -6
  17. package/dist/{goal-preamble-COpt55W8.d.ts → goal-preamble-CYJLg0wk.d.ts} +55 -17
  18. package/dist/{index-Cj3le7TI.d.ts → index-BZdezm3g.d.ts} +8 -8
  19. package/dist/{index-4VVHuTZF.d.ts → index-CPweVoFM.d.ts} +5 -5
  20. package/dist/index.d.ts +48 -48
  21. package/dist/index.js +612 -513
  22. package/dist/index.js.map +1 -1
  23. package/dist/infrastructure/index.d.ts +6 -6
  24. package/dist/infrastructure/index.js +64 -19
  25. package/dist/infrastructure/index.js.map +1 -1
  26. package/dist/kernel/index.d.ts +9 -9
  27. package/dist/kernel/index.js.map +1 -1
  28. package/dist/{llm-selector-Bmx4PsVM.d.ts → llm-selector-CP72f1lC.d.ts} +2 -2
  29. package/dist/{mcp-servers-CofNHOqn.d.ts → mcp-servers-Bl5LTvQg.d.ts} +3 -3
  30. package/dist/models/index.d.ts +5 -5
  31. package/dist/models/index.js +53 -20
  32. package/dist/models/index.js.map +1 -1
  33. package/dist/{models-registry-BzlsjKcy.d.ts → models-registry-D90K9UnM.d.ts} +1 -1
  34. package/dist/{multi-agent-coordinator-CmDmUgq2.d.ts → multi-agent-coordinator-QWEzJDlm.d.ts} +1 -1
  35. package/dist/{null-fleet-bus-CyH2YqP4.d.ts → null-fleet-bus-BUyfqh23.d.ts} +13 -10
  36. package/dist/observability/index.d.ts +2 -2
  37. package/dist/{parallel-eternal-engine-uxVm4y4V.d.ts → parallel-eternal-engine-C75QuhAI.d.ts} +33 -11
  38. package/dist/{path-resolver-gfoGxpdd.d.ts → path-resolver-DRjQBkoO.d.ts} +3 -3
  39. package/dist/{permission-DhLrgA30.d.ts → permission-B7nKnEvQ.d.ts} +1 -1
  40. package/dist/{permission-policy-DGOvne7i.d.ts → permission-policy-8-6zBmfA.d.ts} +2 -13
  41. package/dist/{pipeline-CqF7uju2.d.ts → pipeline-BG7UgbDc.d.ts} +2 -2
  42. package/dist/{plan-templates-DoKhzenv.d.ts → plan-templates-CkKNPU3I.d.ts} +5 -5
  43. package/dist/{provider-runner-BPlI5sPZ.d.ts → provider-runner-BNpuIyOL.d.ts} +3 -3
  44. package/dist/{retry-policy-B4Q-FZgE.d.ts → retry-policy-rutAfVeR.d.ts} +1 -1
  45. package/dist/sdd/index.d.ts +8 -8
  46. package/dist/sdd/index.js +10 -1
  47. package/dist/sdd/index.js.map +1 -1
  48. package/dist/{secret-vault-B7GnL6o9.d.ts → secret-vault-BTcC_T5v.d.ts} +3 -2
  49. package/dist/security/index.d.ts +5 -7
  50. package/dist/security/index.js +5 -36
  51. package/dist/security/index.js.map +1 -1
  52. package/dist/{selector-BakeMnWR.d.ts → selector-4vDFZKt3.d.ts} +1 -1
  53. package/dist/{session-event-bridge-DE5Oekwx.d.ts → session-event-bridge-DWlvglC2.d.ts} +2 -2
  54. package/dist/{session-reader-DFC81-4p.d.ts → session-reader-BAtCxdaw.d.ts} +1 -1
  55. package/dist/storage/index.d.ts +13 -13
  56. package/dist/storage/index.js +100 -103
  57. package/dist/storage/index.js.map +1 -1
  58. package/dist/types/index.d.ts +18 -18
  59. package/dist/types/index.js +276 -103
  60. package/dist/types/index.js.map +1 -1
  61. package/dist/utils/index.d.ts +37 -13
  62. package/dist/utils/index.js +63 -35
  63. package/dist/utils/index.js.map +1 -1
  64. package/package.json +2 -2
  65. package/skills/tech-stack/SKILL.md +253 -97
@@ -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,272 @@ 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 hasToolUse3 = m.content.some((b) => b.type === "tool_use");
259
+ if (hasToolUse3 && 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 extractText(m) {
324
+ if (typeof m.content === "string") return m.content;
325
+ return m.content.filter(isTextBlock).map((b) => b.text).join(" ");
326
+ }
327
+ function hasToolUse2(m) {
328
+ if (typeof m.content === "string") return false;
329
+ return m.content.some((b) => b.type === "tool_use");
330
+ }
331
+ function hasLargeToolResult(m, threshold = 3e3) {
332
+ if (typeof m.content === "string") return false;
333
+ return m.content.some(
334
+ (b) => b.type === "tool_result" && b.content && (typeof b.content === "string" ? b.content.length : JSON.stringify(b.content).length) > threshold
335
+ );
336
+ }
337
+ function scoreMessage(m, context) {
338
+ const text = extractText(m).toLowerCase();
339
+ if (text.trim().length === 0 && (hasToolUse2(m) || typeof m.content !== "string")) {
340
+ const hasResult = typeof m.content !== "string" && m.content.some((b) => b.type === "tool_result");
341
+ if (hasToolUse2(m) || hasResult) return 0;
342
+ }
343
+ if (context?.failureCounts && m.role === "user" && hasToolUse2(m) === false) {
344
+ const isFailure = /error|fail|exception|timeout|enonet|eacces|eperm|enoent|abort/i.test(text);
345
+ if (isFailure) {
346
+ const errKey = /(error|fail|exception|timeout|enonet|eacces|eperm|enoent|abort)/i.exec(text)?.[0]?.toLowerCase() ?? "error";
347
+ const count = (context.failureCounts.get(errKey) ?? 0) + 1;
348
+ context.failureCounts.set(errKey, count);
349
+ if (count >= 5) return 0;
350
+ if (count >= 3) return 1;
351
+ }
352
+ }
353
+ if (m.role === "user") {
354
+ if (/\b(wrong|no\b|stop\b|don'?t\b|actually|fix that|undo|revert|forget|ignore|skip)\b/i.test(
355
+ text
356
+ )) {
357
+ return 5;
358
+ }
359
+ }
360
+ if (/\b(error|exception|fatal|critical|crash|panic|abort|segfault|core dump|undefined is not|null pointer|typeerror|referenceerror|syntaxerror)\b/i.test(
361
+ text
362
+ )) {
363
+ return 5;
364
+ }
365
+ if (/\b(security|vulnerability|injection|xss|csrf|secret|apikey|api.key|hardcoded|leak|exploit|cve)\b/i.test(
366
+ text
367
+ )) {
368
+ return 5;
369
+ }
370
+ if (m.role === "assistant" && /\b(architecture|design|approach|strategy|pattern|refactor|migrate|restructure|decision|trade.?off)\b/i.test(
371
+ text
372
+ )) {
373
+ return 5;
374
+ }
375
+ if (hasLargeToolResult(m)) return 1;
376
+ if (m.role === "user" && !hasToolUse2(m) && /\b(files_with_matches|count|found \d+ match|directory tree|\.\.\. and \d+ more)\b/i.test(text)) {
377
+ return 1;
378
+ }
379
+ return 3;
380
+ }
381
+ function buildSmartDigest(messages) {
382
+ const lines = [];
383
+ const failureCounts = /* @__PURE__ */ new Map();
384
+ let noiseCount = 0;
385
+ for (const m of messages) {
386
+ const score = scoreMessage(m, { failureCounts });
387
+ const text = extractText(m);
388
+ const toolCount = countToolBlocks(m);
389
+ if (score === 0) {
390
+ noiseCount++;
391
+ continue;
392
+ }
393
+ const marker = toolCount > 0 ? ` [${toolCount} tool call(s)]` : "";
394
+ let display;
395
+ switch (score) {
396
+ case 5:
397
+ display = text.trim();
398
+ break;
399
+ case 3:
400
+ display = firstSentence(text);
401
+ break;
402
+ case 1:
403
+ display = oneLineSummary(m, text);
404
+ break;
405
+ default:
406
+ display = firstSentence(text);
407
+ }
408
+ if (display.length === 0 && toolCount === 0) continue;
409
+ lines.push(`[${m.role}]: ${display}${marker}`);
410
+ }
411
+ if (noiseCount > 0) {
412
+ lines.push(`[system]: ${noiseCount} low-importance turn(s) collapsed (repeated failures / pure tool I/O)`);
413
+ }
414
+ return lines.join("\n");
415
+ }
416
+ function countToolBlocks(m) {
417
+ if (typeof m.content === "string") return 0;
418
+ return m.content.filter(
419
+ (b) => b.type === "tool_use" || b.type === "tool_result"
420
+ ).length;
421
+ }
422
+ function firstSentence(text) {
423
+ const trimmed = text.trim();
424
+ if (trimmed.length === 0) return "";
425
+ const dot = trimmed.indexOf(". ");
426
+ if (dot === -1) return trimmed.length > 150 ? `${trimmed.slice(0, 147)}\u2026` : trimmed;
427
+ const sentence = trimmed.slice(0, dot + 1);
428
+ return sentence.length > 150 ? `${sentence.slice(0, 147)}\u2026` : sentence;
429
+ }
430
+ function oneLineSummary(m, text) {
431
+ const trimmed = text.trim();
432
+ if (trimmed.length === 0) {
433
+ if (typeof m.content !== "string") {
434
+ const results = m.content.filter((b) => b.type === "tool_result");
435
+ if (results.length > 0) {
436
+ return `[${results.length} tool result(s) \u2014 see session log]`;
437
+ }
438
+ }
439
+ return "[no text content]";
440
+ }
441
+ const firstLine = trimmed.split("\n")[0] ?? "";
442
+ return firstLine.length > 100 ? `${firstLine.slice(0, 97)}\u2026` : firstLine;
443
+ }
444
+ function findSafeBoundary(messages, from, to) {
445
+ for (let i = to; i >= from; i--) {
446
+ const m = messages[i];
447
+ if (!m) continue;
448
+ if (m.role === "user" && hasTextContent(m)) {
449
+ return findExchangeStart(messages, i);
450
+ }
451
+ }
452
+ return -1;
453
+ }
454
+ function findExchangeStart(messages, userIndex) {
455
+ for (let i = userIndex - 1; i >= 0; i--) {
456
+ const m = messages[i];
457
+ if (!m) continue;
458
+ if (m.role === "assistant") {
459
+ const hasToolUse3 = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
460
+ if (!hasToolUse3) return i + 1;
461
+ } else if (m.role === "user") {
462
+ return i;
463
+ }
464
+ }
465
+ return 0;
466
+ }
467
+
196
468
  // src/execution/compactor.ts
197
469
  var HybridCompactor = class {
198
470
  preserveK;
199
471
  eliseThreshold;
200
- estimator;
472
+ smart;
201
473
  constructor(opts = {}) {
202
474
  this.preserveK = opts.preserveK ?? 5;
203
475
  this.eliseThreshold = opts.eliseThreshold ?? 2e3;
204
- this.estimator = opts.estimator ?? estimateTextTokens;
476
+ this.smart = opts.smart ?? false;
205
477
  }
206
478
  async compact(ctx, opts = {}) {
207
- const beforeTokens = this.estimateMessages(ctx.messages);
479
+ const beforeTokens = estimateMessages(ctx.messages);
208
480
  const beforeFull = this.estimateFullRequest(ctx);
209
481
  const reductions = [];
210
482
  const policy = readContextWindowPolicy(ctx);
211
483
  const preserveK = policy?.preserveK ?? this.preserveK;
212
484
  const eliseThreshold = policy?.eliseThreshold ?? this.eliseThreshold;
213
- const phase1Saved = this.eliseOldToolResults(ctx, preserveK, eliseThreshold);
214
- if (phase1Saved > 0) reductions.push({ phase: "elision", saved: phase1Saved });
485
+ const elide = eliseOldToolResults(ctx.messages, { preserveK, eliseThreshold });
486
+ if (elide.changed) ctx.state.replaceMessages(elide.messages);
487
+ if (elide.saved > 0) reductions.push({ phase: "elision", saved: elide.saved });
488
+ let collapsedDigest;
215
489
  if (opts.aggressive) {
216
- const phase2Saved = this.collapseAncientTurns(ctx, preserveK);
217
- if (phase2Saved > 0) reductions.push({ phase: "summary", saved: phase2Saved });
490
+ const phase2 = this.collapseAncientTurns(ctx, preserveK);
491
+ if (phase2.saved > 0) reductions.push({ phase: "summary", saved: phase2.saved });
492
+ collapsedDigest = phase2.digest;
218
493
  }
219
494
  const repaired = repairToolUseAdjacency(ctx.messages);
220
495
  if (repaired.report.changed) {
221
496
  ctx.state.replaceMessages(repaired.messages);
222
497
  }
223
- const afterTokens = this.estimateMessages(ctx.messages);
498
+ const afterTokens = estimateMessages(ctx.messages);
224
499
  const afterFull = this.estimateFullRequest(ctx);
225
500
  return {
226
501
  before: beforeTokens,
@@ -228,6 +503,7 @@ var HybridCompactor = class {
228
503
  fullRequestTokensBefore: beforeFull,
229
504
  fullRequestTokensAfter: afterFull,
230
505
  reductions,
506
+ collapsedDigest,
231
507
  repaired: repaired.report.changed ? {
232
508
  removedToolUses: repaired.report.removedToolUses,
233
509
  removedToolResults: repaired.report.removedToolResults,
@@ -240,72 +516,23 @@ var HybridCompactor = class {
240
516
  * This is the accurate figure for context-window pressure monitoring.
241
517
  */
242
518
  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;
519
+ return estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
304
520
  }
521
+ /**
522
+ * Lossless rule-based collapse of ancient turns into a single digest message.
523
+ *
524
+ * Preserves ALL textual content of the collapsed range — user instructions,
525
+ * assistant decisions/conclusions, and any prior digests (chained forward so
526
+ * the digest stays lossless across repeated compactions). Only `tool_use` /
527
+ * `tool_result` protocol blocks are dropped and replaced with a count marker;
528
+ * their full payload already lives in the session log. No sub-LLM call.
529
+ *
530
+ * Returns the token savings and the digest text (for audit logging).
531
+ */
305
532
  collapseAncientTurns(ctx, preserveK = this.preserveK) {
306
533
  const messages = ctx.messages;
307
534
  const cutTarget = Math.max(0, messages.length - preserveK * 2);
308
- if (cutTarget <= 0) return 0;
535
+ if (cutTarget <= 0) return { saved: 0 };
309
536
  let boundary = -1;
310
537
  for (let i = cutTarget; i < messages.length; i++) {
311
538
  const m = messages[i];
@@ -315,34 +542,20 @@ var HybridCompactor = class {
315
542
  break;
316
543
  }
317
544
  }
318
- if (boundary <= 0) return 0;
545
+ if (boundary <= 0) return { saved: 0 };
319
546
  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
- ];
547
+ const removedTokens = estimateMessages(removed);
548
+ const digest = this.smart ? buildSmartDigest(removed) || `${removed.length} earlier turns (no textual content; tool I/O omitted \u2014 see session log)` : buildLosslessDigest(removed) || `${removed.length} earlier turns (no textual content; tool I/O omitted \u2014 see session log)`;
549
+ const summaryMsg = {
550
+ role: "system",
551
+ content: `[prior_turns_digest: ${digest}]`
552
+ };
328
553
  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;
554
+ ctx.state.replaceMessages([summaryMsg, ...tail]);
555
+ return {
556
+ saved: Math.max(0, removedTokens - estimateMessages([summaryMsg])),
557
+ digest
558
+ };
346
559
  }
347
560
  };
348
561
  function readContextWindowPolicy(ctx) {
@@ -354,15 +567,6 @@ function readContextWindowPolicy(ctx) {
354
567
  }
355
568
  return candidate;
356
569
  }
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
570
 
367
571
  // src/execution/intelligent-compactor.ts
368
572
  var IntelligentCompactor = class {
@@ -387,23 +591,25 @@ var IntelligentCompactor = class {
387
591
  this.summarizerModel = opts.summarizerModel;
388
592
  }
389
593
  async compact(ctx, opts = {}) {
390
- const beforeTokens = this.estimateTokens(ctx.messages);
594
+ const beforeTokens = estimateMessages(ctx.messages);
391
595
  const beforeFull = this.estimateFullRequest(ctx);
392
596
  const reductions = [];
393
597
  const load = beforeFull / this.maxContext;
394
598
  const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
395
- const saved1 = this.eliseOldToolResults(ctx);
599
+ const saved1 = this.elide(ctx);
396
600
  if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
601
+ let collapsedDigest;
397
602
  if (aggressive) {
398
- const saved2 = await this.summarizeAncientTurns(ctx);
399
- if (saved2 > 0) reductions.push({ phase: "summary", saved: saved2 });
603
+ const phase2 = await this.summarizeAncientTurns(ctx);
604
+ if (phase2.saved > 0) reductions.push({ phase: "summary", saved: phase2.saved });
605
+ collapsedDigest = phase2.digest;
400
606
  } else if (load >= this.warnThreshold) {
401
- const saved2 = this.lightweightCompact(ctx);
607
+ const saved2 = this.elide(ctx);
402
608
  if (saved2 > 0) reductions.push({ phase: "elision", saved: saved2 });
403
609
  }
404
610
  const repaired = repairToolUseAdjacency(ctx.messages);
405
611
  if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
406
- const afterTokens = this.estimateTokens(ctx.messages);
612
+ const afterTokens = estimateMessages(ctx.messages);
407
613
  const afterFull = this.estimateFullRequest(ctx);
408
614
  return {
409
615
  before: beforeTokens,
@@ -411,6 +617,7 @@ var IntelligentCompactor = class {
411
617
  fullRequestTokensBefore: beforeFull,
412
618
  fullRequestTokensAfter: afterFull,
413
619
  reductions,
620
+ collapsedDigest,
414
621
  repaired: repaired.report.changed ? {
415
622
  removedToolUses: repaired.report.removedToolUses,
416
623
  removedToolResults: repaired.report.removedToolResults,
@@ -423,75 +630,39 @@ var IntelligentCompactor = class {
423
630
  * This is the accurate figure for context-window pressure monitoring.
424
631
  */
425
632
  estimateFullRequest(ctx) {
426
- const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
427
- return breakdown.total;
633
+ return estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
634
+ }
635
+ /** Run shared tool-result elision and commit through ConversationState. */
636
+ elide(ctx) {
637
+ const result = eliseOldToolResults(ctx.messages, {
638
+ preserveK: this.preserveK,
639
+ eliseThreshold: this.eliseThreshold
640
+ });
641
+ if (result.changed) ctx.state.replaceMessages(result.messages);
642
+ return result.saved;
428
643
  }
429
644
  async summarizeAncientTurns(ctx) {
430
645
  const messages = ctx.messages;
431
646
  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;
647
+ if (cutoff <= 2) return { saved: 0 };
648
+ const boundary = findSafeBoundary(messages, 0, cutoff);
649
+ if (boundary <= 1) return { saved: 0 };
435
650
  const toSummarize = messages.slice(0, boundary);
436
- const removedTokens = this.estimateTokens(toSummarize);
651
+ const removedTokens = estimateMessages(toSummarize);
437
652
  let summaryText;
438
653
  try {
439
654
  summaryText = await this.callSummarizer(toSummarize, ctx);
440
655
  } 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)`;
656
+ summaryText = buildLosslessDigest(toSummarize) || `${toSummarize.length} earlier turns (semantic content preserved)`;
461
657
  }
462
658
  const summaryMsg = {
463
659
  role: "system",
464
660
  content: `[prior_turns_summary: ${summaryText}]`
465
661
  };
466
- const summaryTokens = this.estimateTokens([summaryMsg]);
662
+ const summaryTokens = estimateMessages([summaryMsg]);
467
663
  const tail = ctx.messages.slice(boundary);
468
664
  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;
665
+ return { saved: Math.max(0, removedTokens - summaryTokens), digest: summaryText };
495
666
  }
496
667
  async callSummarizer(messages, ctx) {
497
668
  const prompt = [
@@ -526,86 +697,6 @@ var IntelligentCompactor = class {
526
697
  }
527
698
  return [{ type: "text", text: lines.join("\n") }];
528
699
  }
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
700
  };
610
701
 
611
702
  // src/models/llm-selector.ts
@@ -631,20 +722,6 @@ Rules:
631
722
  - If unsure, keep rather than collapse (errors are more costly than waste)
632
723
 
633
724
  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
725
  function formatMessages(messages, maxChars = 8e3) {
649
726
  const lines = [];
650
727
  let used = 0;
@@ -683,7 +760,7 @@ var LLMSelector = class {
683
760
  async select(messages, maxToKeep) {
684
761
  const effectiveBudget = Math.min(maxToKeep, this.maxContextTokens);
685
762
  const historyText = formatMessages(messages);
686
- const totalTokens = estimateTokens(messages);
763
+ const totalTokens = estimateMessageTokens(messages);
687
764
  const systemText = `${this.systemPrompt}
688
765
 
689
766
  Conversation (${messages.length} messages, ~${totalTokens} tokens, budget: ${effectiveBudget}):
@@ -715,10 +792,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
715
792
  let startIdx = 0;
716
793
  for (let i = messages.length - 1; i >= 0; i--) {
717
794
  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
- );
795
+ const cost = estimateMessageTokens([m]);
722
796
  if (tokenCount + cost <= budget) {
723
797
  tokenCount += cost;
724
798
  } else {
@@ -951,52 +1025,12 @@ Summarize the following message range:`;
951
1025
  return Math.floor(this.maxContext * 0.75);
952
1026
  }
953
1027
  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;
1028
+ const result = eliseOldToolResults(ctx.messages, {
1029
+ preserveK: this.preserveK,
1030
+ eliseThreshold: this.eliseThreshold
1031
+ });
1032
+ if (result.changed) ctx.state.replaceMessages(result.messages);
1033
+ return result.saved;
1000
1034
  }
1001
1035
  hasTextContent(m) {
1002
1036
  if (typeof m.content === "string") return m.content.trim().length > 0;
@@ -1026,6 +1060,74 @@ Summarize the following message range:`;
1026
1060
  }
1027
1061
  };
1028
1062
 
1063
+ // src/execution/strategy-compactor.ts
1064
+ function createStrategyCompactor(opts = {}) {
1065
+ const requested = opts.strategy ?? (opts.llmSelector ? "selective" : "hybrid");
1066
+ const strategy = requested;
1067
+ if (strategy === "intelligent" || strategy === "selective") {
1068
+ return new ProviderBackedCompactor(strategy, opts);
1069
+ }
1070
+ return new HybridCompactor({
1071
+ preserveK: opts.preserveK,
1072
+ eliseThreshold: opts.eliseThreshold,
1073
+ smart: opts.smart
1074
+ });
1075
+ }
1076
+ var ProviderBackedCompactor = class {
1077
+ constructor(strategy, opts) {
1078
+ this.strategy = strategy;
1079
+ this.opts = opts;
1080
+ }
1081
+ strategy;
1082
+ opts;
1083
+ async compact(ctx, compactOpts = {}) {
1084
+ return this.resolveInner(ctx).compact(ctx, compactOpts);
1085
+ }
1086
+ /**
1087
+ * Construct the concrete compactor for this run. Rebuilt per call (cheap, no
1088
+ * I/O) so a model switch — which changes `ctx.provider.capabilities.maxContext`
1089
+ * — is always reflected. Reads the active ContextWindowPolicy from `ctx.meta`
1090
+ * so the LLM compactors honor the same thresholds/preserveK as the policy.
1091
+ */
1092
+ resolveInner(ctx) {
1093
+ const provider = ctx.provider;
1094
+ if (!provider) {
1095
+ return new HybridCompactor({
1096
+ preserveK: this.opts.preserveK,
1097
+ eliseThreshold: this.opts.eliseThreshold
1098
+ });
1099
+ }
1100
+ const policy = readPolicy(ctx);
1101
+ const maxContext = provider.capabilities?.maxContext || void 0;
1102
+ const thresholds = policy?.thresholds;
1103
+ const common = {
1104
+ provider,
1105
+ maxContext,
1106
+ preserveK: this.opts.preserveK ?? policy?.preserveK,
1107
+ eliseThreshold: this.opts.eliseThreshold ?? policy?.eliseThreshold,
1108
+ ...thresholds ? { warnThreshold: thresholds.warn, softThreshold: thresholds.soft, hardThreshold: thresholds.hard } : {}
1109
+ };
1110
+ if (this.strategy === "selective") {
1111
+ return new SelectiveCompactor({
1112
+ ...common,
1113
+ selectorModel: this.opts.summarizerModel,
1114
+ summarizerModel: this.opts.summarizerModel
1115
+ });
1116
+ }
1117
+ return new IntelligentCompactor({
1118
+ ...common,
1119
+ summarizerModel: this.opts.summarizerModel
1120
+ });
1121
+ }
1122
+ };
1123
+ function readPolicy(ctx) {
1124
+ const policy = ctx.meta?.["contextWindowPolicy"];
1125
+ if (!policy || typeof policy !== "object") return null;
1126
+ const candidate = policy;
1127
+ if (typeof candidate.preserveK !== "number" || !candidate.thresholds) return null;
1128
+ return candidate;
1129
+ }
1130
+
1029
1131
  // src/types/errors.ts
1030
1132
  var ERROR_CODES = {
1031
1133
  // Provider
@@ -1113,6 +1215,11 @@ var FsError = class extends WrongStackError {
1113
1215
 
1114
1216
  // src/execution/auto-compaction-middleware.ts
1115
1217
  var LEVEL_RANK = { warn: 0, soft: 1, hard: 2 };
1218
+ var MAX_DIGEST_LOG_CHARS = 4e3;
1219
+ function truncateDigest(digest) {
1220
+ if (digest.length <= MAX_DIGEST_LOG_CHARS) return digest;
1221
+ return `${digest.slice(0, MAX_DIGEST_LOG_CHARS)}\u2026 [+${digest.length - MAX_DIGEST_LOG_CHARS} chars; full turns in session log]`;
1222
+ }
1116
1223
  var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1117
1224
  name = "AutoCompaction";
1118
1225
  compactor;
@@ -1173,7 +1280,12 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1173
1280
  }
1174
1281
  handler() {
1175
1282
  return async (ctx, next) => {
1176
- const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokensCalibrated(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
1283
+ const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokensCalibrated(
1284
+ ctx.messages,
1285
+ ctx.systemPrompt,
1286
+ ctx.tools ?? [],
1287
+ `${ctx.provider?.id ?? "unknown"}/${ctx.model}`
1288
+ ).total;
1177
1289
  const load = tokens / this._maxContext;
1178
1290
  const policy = this.policyProvider?.(ctx);
1179
1291
  const thresholds = policy?.thresholds ?? {
@@ -1235,7 +1347,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1235
1347
  after: report.after,
1236
1348
  level: pressure.level,
1237
1349
  aggressive,
1238
- reductions: report.reductions?.map((r) => ({ phase: r.phase, saved: r.saved }))
1350
+ reductions: report.reductions?.map((r) => ({ phase: r.phase, saved: r.saved })),
1351
+ // Record what was collapsed so the audit trail shows the preserved
1352
+ // content, not just token counts. Bounded to keep the log line small;
1353
+ // the full original turns are already in the session JSONL.
1354
+ ...report.collapsedDigest ? { digest: truncateDigest(report.collapsedDigest) } : {}
1239
1355
  });
1240
1356
  ctx.clearFileTracking();
1241
1357
  } catch (err) {
@@ -1271,6 +1387,8 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1271
1387
  var ToolCapabilities = {
1272
1388
  /** Can execute arbitrary commands in the user's shell (the `bash` tool). */
1273
1389
  SHELL_ARBITRARY: "shell.arbitrary",
1390
+ /** Can execute a restricted set of commands (the `exec` tool). */
1391
+ SHELL_RESTRICTED: "shell.restricted",
1274
1392
  /** Can write / modify / delete files inside the project. */
1275
1393
  FS_WRITE: "fs.write",
1276
1394
  /** Can write files outside the current project root (very high risk). */
@@ -1286,6 +1404,7 @@ var ToolCapabilities = {
1286
1404
  };
1287
1405
  var DANGEROUS_FOR_SUBAGENTS = [
1288
1406
  ToolCapabilities.SHELL_ARBITRARY,
1407
+ ToolCapabilities.SHELL_RESTRICTED,
1289
1408
  ToolCapabilities.FS_WRITE,
1290
1409
  ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
1291
1410
  ToolCapabilities.MCP_PROXY,
@@ -1812,7 +1931,7 @@ ${excerpt}`;
1812
1931
  subjectFor(toolName, input, subjectKey) {
1813
1932
  if (!input || typeof input !== "object") return void 0;
1814
1933
  const obj = input;
1815
- const globChars = /[*?\[\]]/g;
1934
+ const globChars = /[*?[\]]/g;
1816
1935
  const escapeGlob = (s) => s.replace(globChars, (c) => `\\${c}`);
1817
1936
  const normalizePath = (s) => escapeGlob(s.replace(/\\/g, "/"));
1818
1937
  if (subjectKey) {
@@ -1863,6 +1982,13 @@ function extractMalformedRaw(input) {
1863
1982
  }
1864
1983
  }
1865
1984
 
1985
+ // src/utils/assert-never.ts
1986
+ function assertNever(x, message) {
1987
+ throw new Error(
1988
+ `Unhandled case: ${JSON.stringify(x)}`
1989
+ );
1990
+ }
1991
+
1866
1992
  // src/utils/regex-guard.ts
1867
1993
  var MAX_PATTERN_LEN = 512;
1868
1994
  var DANGEROUS_PATTERNS = [
@@ -1952,6 +2078,12 @@ var DoneConditionChecker = class {
1952
2078
  return { done: true, reason: `max tool calls (${this.condition.maxToolCalls}) reached`, ...state };
1953
2079
  }
1954
2080
  break;
2081
+ case "all_tasks_done":
2082
+ break;
2083
+ case "custom":
2084
+ break;
2085
+ default:
2086
+ return assertNever(this.condition.type);
1955
2087
  }
1956
2088
  return { done: false, iterations: state.iterations, toolCalls: state.toolCalls };
1957
2089
  }
@@ -2229,12 +2361,12 @@ function appendJournal(goal, entry) {
2229
2361
  };
2230
2362
  }
2231
2363
  function parseProgressFromText(text) {
2232
- const re = /\[progress:\s*(\d{1,3})%\]\s*(?:[—\-]\s*(.+))?/i;
2364
+ const re = /\[progress:\s*(\d{1,3})%\]\s*(?:[—-]\s*(.+))?/i;
2233
2365
  const m = text.match(re);
2234
2366
  if (!m) return null;
2235
- const progress = Math.min(100, Math.max(0, Number.parseInt(m[1], 10)));
2367
+ const progress = Math.min(100, Math.max(0, Number.parseInt(m[1] ?? "0", 10)));
2236
2368
  const note = m[2]?.trim() || void 0;
2237
- return { progress, note };
2369
+ return note === void 0 ? { progress } : { progress, note };
2238
2370
  }
2239
2371
  function recordProgress(goal, progress, note) {
2240
2372
  const clamped = Math.min(100, Math.max(0, progress));
@@ -2407,7 +2539,7 @@ ${request.context}` : "",
2407
2539
  },
2408
2540
  { signal }
2409
2541
  );
2410
- const text = extractText(response).trim();
2542
+ const text = extractText2(response).trim();
2411
2543
  if (request.options?.length) {
2412
2544
  for (const opt of request.options) {
2413
2545
  if (text.toLowerCase().includes(opt.id.toLowerCase())) {
@@ -2435,7 +2567,7 @@ ${request.context}` : "",
2435
2567
  return { type: "deny", reason: "Autonomy Brain LLM unavailable for decision." };
2436
2568
  }
2437
2569
  }
2438
- function extractText(result) {
2570
+ function extractText2(result) {
2439
2571
  if (!result || typeof result !== "object") return "";
2440
2572
  const r = result;
2441
2573
  if (Array.isArray(r.content)) {
@@ -2999,6 +3131,7 @@ ${recentJournal}` : "No prior iterations.",
2999
3131
  note: note.slice(0, 240)
3000
3132
  });
3001
3133
  await saveGoal(this.goalPath, withEntry);
3134
+ this.opts.onEternalStop?.();
3002
3135
  }
3003
3136
  /**
3004
3137
  * Manually clear the goal — equivalent to `/goal clear` typed by the user.
@@ -7989,6 +8122,6 @@ function parseDescription(raw) {
7989
8122
  return { trigger, scope };
7990
8123
  }
7991
8124
 
7992
- export { AutoCompactionMiddleware, AutonomousRunner, DefaultErrorHandler, DefaultRetryPolicy, DefaultSkillLoader, DoneConditionChecker, EternalAutonomyEngine, HybridCompactor, IntelligentCompactor, ParallelEternalEngine, SelectiveCompactor, ToolExecutor, buildGoalPreamble, createAutonomyBrain, formatDecisionSummary, makeAutonomyPromptContributor };
8125
+ export { AutoCompactionMiddleware, AutonomousRunner, DefaultErrorHandler, DefaultRetryPolicy, DefaultSkillLoader, DoneConditionChecker, EternalAutonomyEngine, HybridCompactor, IntelligentCompactor, ParallelEternalEngine, SelectiveCompactor, ToolExecutor, buildGoalPreamble, createAutonomyBrain, createStrategyCompactor, formatDecisionSummary, makeAutonomyPromptContributor };
7993
8126
  //# sourceMappingURL=index.js.map
7994
8127
  //# sourceMappingURL=index.js.map