@wrongstack/core 0.119.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 (64) 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 +31 -17
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +24 -24
  11. package/dist/defaults/index.js +638 -681
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/execution/index.d.ts +14 -14
  14. package/dist/execution/index.js +326 -317
  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-iuIUTQwk.d.ts} +47 -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 +47 -47
  21. package/dist/index.js +455 -503
  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-Dj2SYzha.d.ts} +24 -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 +4 -35
  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 +98 -101
  57. package/dist/storage/index.js.map +1 -1
  58. package/dist/types/index.d.ts +18 -18
  59. package/dist/types/index.js +152 -102
  60. package/dist/types/index.js.map +1 -1
  61. package/dist/utils/index.d.ts +37 -13
  62. package/dist/utils/index.js +60 -32
  63. package/dist/utils/index.js.map +1 -1
  64. package/package.json +2 -2
@@ -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
  }
@@ -2232,9 +2240,9 @@ function parseProgressFromText(text) {
2232
2240
  const re = /\[progress:\s*(\d{1,3})%\]\s*(?:[—\-]\s*(.+))?/i;
2233
2241
  const m = text.match(re);
2234
2242
  if (!m) return null;
2235
- 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)));
2236
2244
  const note = m[2]?.trim() || void 0;
2237
- return { progress, note };
2245
+ return note === void 0 ? { progress } : { progress, note };
2238
2246
  }
2239
2247
  function recordProgress(goal, progress, note) {
2240
2248
  const clamped = Math.min(100, Math.max(0, progress));
@@ -2999,6 +3007,7 @@ ${recentJournal}` : "No prior iterations.",
2999
3007
  note: note.slice(0, 240)
3000
3008
  });
3001
3009
  await saveGoal(this.goalPath, withEntry);
3010
+ this.opts.onEternalStop?.();
3002
3011
  }
3003
3012
  /**
3004
3013
  * Manually clear the goal — equivalent to `/goal clear` typed by the user.
@@ -7989,6 +7998,6 @@ function parseDescription(raw) {
7989
7998
  return { trigger, scope };
7990
7999
  }
7991
8000
 
7992
- 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 };
7993
8002
  //# sourceMappingURL=index.js.map
7994
8003
  //# sourceMappingURL=index.js.map