@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.
- package/dist/{agent-bridge-PRAbEX-9.d.ts → agent-bridge-r9y6gdn4.d.ts} +1 -1
- package/dist/{agent-subagent-runner-DXWImnNV.d.ts → agent-subagent-runner-1GeQE_L0.d.ts} +7 -7
- package/dist/{brain-NZZqL8sn.d.ts → brain-Cp_3GIS2.d.ts} +14 -3
- package/dist/{compactor-C3stu664.d.ts → compactor-BueGt7LG.d.ts} +9 -1
- package/dist/{config-DZV9VK9n.d.ts → config-BaVThgnT.d.ts} +1 -1
- package/dist/{context-hvXINa6a.d.ts → context-C7G_MtLV.d.ts} +7 -0
- package/dist/coordination/index.d.ts +12 -12
- package/dist/coordination/index.js +35 -21
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +24 -24
- package/dist/defaults/index.js +869 -788
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +14 -14
- package/dist/execution/index.js +454 -321
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/{goal-preamble-COpt55W8.d.ts → goal-preamble-CYJLg0wk.d.ts} +55 -17
- package/dist/{index-Cj3le7TI.d.ts → index-BZdezm3g.d.ts} +8 -8
- package/dist/{index-4VVHuTZF.d.ts → index-CPweVoFM.d.ts} +5 -5
- package/dist/index.d.ts +48 -48
- package/dist/index.js +612 -513
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +64 -19
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js.map +1 -1
- package/dist/{llm-selector-Bmx4PsVM.d.ts → llm-selector-CP72f1lC.d.ts} +2 -2
- package/dist/{mcp-servers-CofNHOqn.d.ts → mcp-servers-Bl5LTvQg.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +53 -20
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-BzlsjKcy.d.ts → models-registry-D90K9UnM.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-CmDmUgq2.d.ts → multi-agent-coordinator-QWEzJDlm.d.ts} +1 -1
- package/dist/{null-fleet-bus-CyH2YqP4.d.ts → null-fleet-bus-BUyfqh23.d.ts} +13 -10
- package/dist/observability/index.d.ts +2 -2
- package/dist/{parallel-eternal-engine-uxVm4y4V.d.ts → parallel-eternal-engine-C75QuhAI.d.ts} +33 -11
- package/dist/{path-resolver-gfoGxpdd.d.ts → path-resolver-DRjQBkoO.d.ts} +3 -3
- package/dist/{permission-DhLrgA30.d.ts → permission-B7nKnEvQ.d.ts} +1 -1
- package/dist/{permission-policy-DGOvne7i.d.ts → permission-policy-8-6zBmfA.d.ts} +2 -13
- package/dist/{pipeline-CqF7uju2.d.ts → pipeline-BG7UgbDc.d.ts} +2 -2
- package/dist/{plan-templates-DoKhzenv.d.ts → plan-templates-CkKNPU3I.d.ts} +5 -5
- package/dist/{provider-runner-BPlI5sPZ.d.ts → provider-runner-BNpuIyOL.d.ts} +3 -3
- package/dist/{retry-policy-B4Q-FZgE.d.ts → retry-policy-rutAfVeR.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +10 -1
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-B7GnL6o9.d.ts → secret-vault-BTcC_T5v.d.ts} +3 -2
- package/dist/security/index.d.ts +5 -7
- package/dist/security/index.js +5 -36
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-BakeMnWR.d.ts → selector-4vDFZKt3.d.ts} +1 -1
- package/dist/{session-event-bridge-DE5Oekwx.d.ts → session-event-bridge-DWlvglC2.d.ts} +2 -2
- package/dist/{session-reader-DFC81-4p.d.ts → session-reader-BAtCxdaw.d.ts} +1 -1
- package/dist/storage/index.d.ts +13 -13
- package/dist/storage/index.js +100 -103
- package/dist/storage/index.js.map +1 -1
- package/dist/types/index.d.ts +18 -18
- package/dist/types/index.js +276 -103
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +37 -13
- package/dist/utils/index.js +63 -35
- package/dist/utils/index.js.map +1 -1
- package/package.json +2 -2
- package/skills/tech-stack/SKILL.md +253 -97
package/dist/execution/index.js
CHANGED
|
@@ -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
|
-
|
|
472
|
+
smart;
|
|
201
473
|
constructor(opts = {}) {
|
|
202
474
|
this.preserveK = opts.preserveK ?? 5;
|
|
203
475
|
this.eliseThreshold = opts.eliseThreshold ?? 2e3;
|
|
204
|
-
this.
|
|
476
|
+
this.smart = opts.smart ?? false;
|
|
205
477
|
}
|
|
206
478
|
async compact(ctx, opts = {}) {
|
|
207
|
-
const beforeTokens =
|
|
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
|
|
214
|
-
if (
|
|
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
|
|
217
|
-
if (
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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([
|
|
330
|
-
return
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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 =
|
|
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.
|
|
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
|
|
399
|
-
if (
|
|
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.
|
|
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 =
|
|
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
|
-
|
|
427
|
-
|
|
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 =
|
|
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 =
|
|
651
|
+
const removedTokens = estimateMessages(toSummarize);
|
|
437
652
|
let summaryText;
|
|
438
653
|
try {
|
|
439
654
|
summaryText = await this.callSummarizer(toSummarize, ctx);
|
|
440
655
|
} catch {
|
|
441
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
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(
|
|
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 = /[
|
|
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*(?:[
|
|
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 =
|
|
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
|
|
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
|