opencode-acp 1.4.1 → 1.5.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/README.md +2 -1
- package/dist/index.js +93 -131
- package/dist/index.js.map +1 -1
- package/dist/lib/config-validation.d.ts +0 -1
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/gc/merge.d.ts.map +1 -1
- package/dist/lib/message-ids.d.ts +0 -1
- package/dist/lib/message-ids.d.ts.map +1 -1
- package/dist/lib/messages/prune.d.ts.map +1 -1
- package/dist/lib/messages/utils.d.ts +0 -4
- package/dist/lib/messages/utils.d.ts.map +1 -1
- package/dist/lib/prompts/context-limit-nudge.d.ts +1 -1
- package/dist/lib/prompts/context-limit-nudge.d.ts.map +1 -1
- package/dist/lib/prompts/extensions/nudge.d.ts.map +1 -1
- package/dist/lib/prompts/system.d.ts +1 -1
- package/dist/lib/prompts/system.d.ts.map +1 -1
- package/dist/lib/prompts/turn-nudge.d.ts +1 -1
- package/dist/lib/prompts/turn-nudge.d.ts.map +1 -1
- package/dist/lib/state/state.d.ts.map +1 -1
- package/dist/lib/ui/notification.d.ts +0 -2
- package/dist/lib/ui/notification.d.ts.map +1 -1
- package/dist/lib/ui/utils.d.ts +0 -3
- package/dist/lib/ui/utils.d.ts.map +1 -1
- package/dist/lib/update.d.ts +0 -16
- package/dist/lib/update.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -389,7 +389,7 @@ ACP auto-migrates config from `dcp.jsonc` to `acp.jsonc` and prompts from `dcp-p
|
|
|
389
389
|
---
|
|
390
390
|
|
|
391
391
|
<details>
|
|
392
|
-
<summary><strong>Bug Fixes (
|
|
392
|
+
<summary><strong>Bug Fixes (38 total)</strong> -- applied on top of DCP v3.1.11</summary>
|
|
393
393
|
|
|
394
394
|
| # | Severity | Summary |
|
|
395
395
|
|---|----------|---------|
|
|
@@ -419,6 +419,7 @@ ACP auto-migrates config from `dcp.jsonc` to `acp.jsonc` and prompts from `dcp-p
|
|
|
419
419
|
| 35 | HIGH | Aging warnings shown at low context usage (<50%) -- triggers unnecessary compress, wastes tokens |
|
|
420
420
|
| 36 | HIGH | Compression summary emitted as a standalone user message before the user's real turn -- model reads its own prior assistant output as user input, causing dialog role confusion / self-Q&A loops |
|
|
421
421
|
| 37 | HIGH | Message-transform pipeline runs on OpenCode's hidden title/summary/compaction agent requests -- corrupts the request and shared session state, breaking session title generation |
|
|
422
|
+
| 38 | CRITICAL | pruneToolOutputs/pruneToolInputs/pruneToolErrors mutate existing messages in-place -- invalidates LLM prefix cache, causing 89% of fresh input tokens to be wasted on cache-invalidating re-sends |
|
|
422
423
|
|
|
423
424
|
For the complete list with root cause analysis, see the [bug tracker](https://github.com/ranxianglei/opencode-acp/issues).
|
|
424
425
|
|
package/dist/index.js
CHANGED
|
@@ -1485,7 +1485,7 @@ var defaultConfig = {
|
|
|
1485
1485
|
maxOldGenSummaryLength: 3e3,
|
|
1486
1486
|
majorGcThresholdPercent: "100%",
|
|
1487
1487
|
batchCleanup: {
|
|
1488
|
-
lowThreshold: "
|
|
1488
|
+
lowThreshold: "55%",
|
|
1489
1489
|
highThreshold: "75%",
|
|
1490
1490
|
forceThreshold: "90%"
|
|
1491
1491
|
}
|
|
@@ -4958,89 +4958,8 @@ var stripHallucinations = (messages) => {
|
|
|
4958
4958
|
};
|
|
4959
4959
|
|
|
4960
4960
|
// lib/messages/prune.ts
|
|
4961
|
-
var PRUNED_TOOL_OUTPUT_REPLACEMENT = "[Output removed to save context - information superseded or no longer needed]";
|
|
4962
|
-
var PRUNED_TOOL_ERROR_INPUT_REPLACEMENT = "[input removed due to failed tool call]";
|
|
4963
|
-
var PRUNED_QUESTION_INPUT_REPLACEMENT = "[questions removed - see output for user's answers]";
|
|
4964
4961
|
var prune = (state, logger, config, messages) => {
|
|
4965
4962
|
filterCompressedRanges(state, logger, config, messages);
|
|
4966
|
-
pruneToolOutputs(state, logger, messages);
|
|
4967
|
-
pruneToolInputs(state, logger, messages);
|
|
4968
|
-
pruneToolErrors(state, logger, messages);
|
|
4969
|
-
};
|
|
4970
|
-
var pruneToolOutputs = (state, logger, messages) => {
|
|
4971
|
-
for (const msg of messages) {
|
|
4972
|
-
if (isMessageCompacted(state, msg)) {
|
|
4973
|
-
continue;
|
|
4974
|
-
}
|
|
4975
|
-
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
4976
|
-
for (const part of parts) {
|
|
4977
|
-
if (part.type !== "tool") {
|
|
4978
|
-
continue;
|
|
4979
|
-
}
|
|
4980
|
-
if (!state.prune.tools.has(part.callID)) {
|
|
4981
|
-
continue;
|
|
4982
|
-
}
|
|
4983
|
-
if (part.state.status !== "completed") {
|
|
4984
|
-
continue;
|
|
4985
|
-
}
|
|
4986
|
-
if (part.tool === "question" || part.tool === "edit" || part.tool === "write") {
|
|
4987
|
-
continue;
|
|
4988
|
-
}
|
|
4989
|
-
part.state.output = PRUNED_TOOL_OUTPUT_REPLACEMENT;
|
|
4990
|
-
}
|
|
4991
|
-
}
|
|
4992
|
-
};
|
|
4993
|
-
var pruneToolInputs = (state, logger, messages) => {
|
|
4994
|
-
for (const msg of messages) {
|
|
4995
|
-
if (isMessageCompacted(state, msg)) {
|
|
4996
|
-
continue;
|
|
4997
|
-
}
|
|
4998
|
-
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
4999
|
-
for (const part of parts) {
|
|
5000
|
-
if (part.type !== "tool") {
|
|
5001
|
-
continue;
|
|
5002
|
-
}
|
|
5003
|
-
if (!state.prune.tools.has(part.callID)) {
|
|
5004
|
-
continue;
|
|
5005
|
-
}
|
|
5006
|
-
if (part.state.status !== "completed") {
|
|
5007
|
-
continue;
|
|
5008
|
-
}
|
|
5009
|
-
if (part.tool !== "question") {
|
|
5010
|
-
continue;
|
|
5011
|
-
}
|
|
5012
|
-
if (part.state.input?.questions !== void 0) {
|
|
5013
|
-
part.state.input.questions = PRUNED_QUESTION_INPUT_REPLACEMENT;
|
|
5014
|
-
}
|
|
5015
|
-
}
|
|
5016
|
-
}
|
|
5017
|
-
};
|
|
5018
|
-
var pruneToolErrors = (state, logger, messages) => {
|
|
5019
|
-
for (const msg of messages) {
|
|
5020
|
-
if (isMessageCompacted(state, msg)) {
|
|
5021
|
-
continue;
|
|
5022
|
-
}
|
|
5023
|
-
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
5024
|
-
for (const part of parts) {
|
|
5025
|
-
if (part.type !== "tool") {
|
|
5026
|
-
continue;
|
|
5027
|
-
}
|
|
5028
|
-
if (!state.prune.tools.has(part.callID)) {
|
|
5029
|
-
continue;
|
|
5030
|
-
}
|
|
5031
|
-
if (part.state.status !== "error") {
|
|
5032
|
-
continue;
|
|
5033
|
-
}
|
|
5034
|
-
const input = part.state.input;
|
|
5035
|
-
if (input && typeof input === "object") {
|
|
5036
|
-
for (const key of Object.keys(input)) {
|
|
5037
|
-
if (typeof input[key] === "string") {
|
|
5038
|
-
input[key] = PRUNED_TOOL_ERROR_INPUT_REPLACEMENT;
|
|
5039
|
-
}
|
|
5040
|
-
}
|
|
5041
|
-
}
|
|
5042
|
-
}
|
|
5043
|
-
}
|
|
5044
4963
|
};
|
|
5045
4964
|
var filterCompressedRanges = (state, logger, config, messages) => {
|
|
5046
4965
|
if (state.prune.messages.byMessageId.size === 0 && state.prune.messages.activeByAnchorMessageId.size === 0) {
|
|
@@ -5299,11 +5218,18 @@ function buildCompressedBlockGuidance(state, gcConfig, context) {
|
|
|
5299
5218
|
const activeBlockIds = Array.from(state.prune.messages.activeBlockIds).filter((id) => Number.isInteger(id) && id > 0).sort((a, b) => a - b);
|
|
5300
5219
|
const refs = activeBlockIds.map((id) => `b${id}`);
|
|
5301
5220
|
const blockCount = refs.length;
|
|
5302
|
-
|
|
5221
|
+
let blockList;
|
|
5222
|
+
if (blockCount <= 20) {
|
|
5223
|
+
blockList = blockCount > 0 ? refs.join(", ") : "none";
|
|
5224
|
+
} else {
|
|
5225
|
+
const recent = refs.slice(-20).join(", ");
|
|
5226
|
+
blockList = `${recent} (+${blockCount - 20} older, use decompress to access by ID)`;
|
|
5227
|
+
}
|
|
5303
5228
|
const lines = [
|
|
5304
5229
|
"Compressed block context:",
|
|
5305
5230
|
`- Active compressed blocks: ${blockCount} (${blockList})`,
|
|
5306
|
-
"- If your selected compression range includes any listed block, include each required placeholder exactly once in the summary using `(bN)`."
|
|
5231
|
+
"- If your selected compression range includes any listed block, include each required placeholder exactly once in the summary using `(bN)`.",
|
|
5232
|
+
"- \u{1F4A1} When you've finished using tool outputs, compress them \u2014 you can decompress later if needed. Lean context improves accuracy."
|
|
5307
5233
|
];
|
|
5308
5234
|
const usageRatio = context?.currentTokens && context?.modelContextLimit ? context.currentTokens / context.modelContextLimit : 0;
|
|
5309
5235
|
if (gcConfig && usageRatio > 0.5) {
|
|
@@ -5656,14 +5582,14 @@ function buildContextUsageGuidance(config, currentTokens, modelContextLimit) {
|
|
|
5656
5582
|
const formatK = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
|
|
5657
5583
|
const minPct = resolveThresholdPercent(config.compress.minContextLimit, modelContextLimit) ?? 45;
|
|
5658
5584
|
const maxPct = resolveThresholdPercent(config.compress.maxContextLimit, modelContextLimit) ?? 55;
|
|
5659
|
-
const base = `Context usage: ${formatK(currentTokens)} / ${formatK(modelContextLimit)} tokens (${percentage}%)
|
|
5585
|
+
const base = `Context usage: ${formatK(currentTokens)} / ${formatK(modelContextLimit)} tokens (${percentage}%).`;
|
|
5660
5586
|
let guidance;
|
|
5661
5587
|
if (pct < minPct) {
|
|
5662
|
-
guidance = "
|
|
5588
|
+
guidance = " \u{1F4A1} Be frugal with context \u2014 compress tool outputs you've finished using into summaries. You can decompress later; nothing is permanently lost. Lean context means better accuracy. Extract and keep what matters: user intent, key decisions, file paths, and important findings \u2014 even if buried in large messages. Compress everything else, including verbose parts of any message.";
|
|
5663
5589
|
} else if (pct < maxPct) {
|
|
5664
|
-
guidance = " Context is
|
|
5590
|
+
guidance = " \u26A0\uFE0F Context is growing \u2014 compress completed sections and high-token waste now. Preserve key details.";
|
|
5665
5591
|
} else {
|
|
5666
|
-
guidance = " Context is high \u2014 compress aggressively but selectively. Preserve only what is essential.";
|
|
5592
|
+
guidance = " \u{1F525} Context is high \u2014 compress aggressively but selectively. Preserve only what is essential.";
|
|
5667
5593
|
}
|
|
5668
5594
|
return `
|
|
5669
5595
|
|
|
@@ -6709,15 +6635,15 @@ COMPRESSION PHILOSOPHY
|
|
|
6709
6635
|
|
|
6710
6636
|
Compression replaces raw conversation content with dense summaries. When used correctly, it keeps your context sharp and focused. When used carelessly, it destroys information you need.
|
|
6711
6637
|
|
|
6712
|
-
The key principle: compress
|
|
6638
|
+
The key principle: compress proactively to keep context lean, but selectively. Large tool outputs (shell, diffs, logs) can be compressed into summaries at any time \u2014 you can decompress later if needed. Extract and keep what matters: user intent, key decisions, file paths, and important findings \u2014 even if buried in large messages. Compress everything else, including verbose parts of user messages, large code dumps, and long discussions.
|
|
6713
6639
|
|
|
6714
6640
|
Target the largest UNCOMPRESSED content first. Savings scale with original size \u2014 compressing a 5000-token tool output frees far more than re-shrinking an already-summarized 300-token block.
|
|
6715
6641
|
|
|
6716
6642
|
CONTEXT PRESSURE LEVELS
|
|
6717
6643
|
|
|
6718
|
-
-
|
|
6719
|
-
-
|
|
6720
|
-
-
|
|
6644
|
+
- Normal: Be frugal \u2014 compress tool outputs you've finished using into summaries. You can decompress later. Extract and keep what matters from any message; compress verbose parts \u2014 including large logs in user messages or generated code.
|
|
6645
|
+
- Elevated: Context is growing. Compress completed sections and high-token waste more urgently.
|
|
6646
|
+
- Critical: Compress aggressively now. Every compression should free meaningful tokens. Preserve only what is essential for the current task.
|
|
6721
6647
|
|
|
6722
6648
|
WHAT TO COMPRESS FIRST (high value, low risk)
|
|
6723
6649
|
|
|
@@ -6879,9 +6805,9 @@ General cleanup should be done periodically between other normal compression too
|
|
|
6879
6805
|
// lib/prompts/context-limit-nudge.ts
|
|
6880
6806
|
var CONTEXT_LIMIT_NUDGE = `
|
|
6881
6807
|
<system-reminder>
|
|
6882
|
-
\u26A0\uFE0F
|
|
6808
|
+
\u26A0\uFE0F Context limit reached \u2014 time to compress the largest ranges you no longer need. Prioritize completed tool outputs and resolved work. You can decompress specific blocks later if you need details. Keeping context lean helps you stay accurate.
|
|
6883
6809
|
|
|
6884
|
-
If mid-atomic-operation, finish that step first, then compress
|
|
6810
|
+
If mid-atomic-operation, finish that step first, then compress.
|
|
6885
6811
|
|
|
6886
6812
|
HOW TO CALL COMPRESS:
|
|
6887
6813
|
{
|
|
@@ -6896,7 +6822,7 @@ HOW TO CALL COMPRESS:
|
|
|
6896
6822
|
}
|
|
6897
6823
|
|
|
6898
6824
|
\u26A0\uFE0F ID RULES \u2014 MOST COMMON CAUSE OF ERRORS:
|
|
6899
|
-
- ONLY use IDs you can see in
|
|
6825
|
+
- ONLY use IDs you can see in tags in the messages ABOVE.
|
|
6900
6826
|
- Do NOT copy IDs from this example. Do NOT invent IDs.
|
|
6901
6827
|
- Do NOT use IDs from compressed block summaries \u2014 they are stale.
|
|
6902
6828
|
- startId must appear BEFORE endId in the conversation.
|
|
@@ -6912,14 +6838,14 @@ SUMMARY RULES:
|
|
|
6912
6838
|
// lib/prompts/turn-nudge.ts
|
|
6913
6839
|
var TURN_NUDGE = `
|
|
6914
6840
|
<system-reminder>
|
|
6915
|
-
Context is getting full.
|
|
6841
|
+
Context is getting full. If you've finished reading tool outputs or exploration results, compress them \u2014 you can decompress later if needed. This keeps your focus on the current task and improves accuracy.
|
|
6916
6842
|
|
|
6917
6843
|
{
|
|
6918
6844
|
"topic": "Short Label",
|
|
6919
6845
|
"content": [{ "startId": "<visible message ID>", "endId": "<visible message ID>", "summary": "..." }]
|
|
6920
6846
|
}
|
|
6921
6847
|
|
|
6922
|
-
\u26A0\uFE0F ONLY use IDs from
|
|
6848
|
+
\u26A0\uFE0F ONLY use IDs from tags visible above. Do NOT invent or copy example IDs.
|
|
6923
6849
|
</system-reminder>
|
|
6924
6850
|
`;
|
|
6925
6851
|
|
|
@@ -8293,10 +8219,12 @@ function parseGcThreshold(limit, modelContextLimit) {
|
|
|
8293
8219
|
|
|
8294
8220
|
// lib/gc/merge.ts
|
|
8295
8221
|
var DEFAULT_BATCH_CLEANUP = {
|
|
8296
|
-
lowThreshold: "
|
|
8222
|
+
lowThreshold: "55%",
|
|
8297
8223
|
highThreshold: "75%",
|
|
8298
8224
|
forceThreshold: "90%"
|
|
8299
8225
|
};
|
|
8226
|
+
var ESCALATE_MIN_MARKED = 3;
|
|
8227
|
+
var ESCALATE_MIN_RATIO = 0.4;
|
|
8300
8228
|
function resolveBatchCleanup(gc) {
|
|
8301
8229
|
return gc.batchCleanup ?? DEFAULT_BATCH_CLEANUP;
|
|
8302
8230
|
}
|
|
@@ -8320,11 +8248,15 @@ function collectActiveOldGenBlocks(state, maxOldGenSummaryLength) {
|
|
|
8320
8248
|
return blocks;
|
|
8321
8249
|
}
|
|
8322
8250
|
function collectActiveMarkedBlocks(state) {
|
|
8323
|
-
const
|
|
8251
|
+
const messagesState = state.prune.messages;
|
|
8252
|
+
const ids = Array.from(messagesState.markedForCleanup).sort((a, b) => a - b);
|
|
8324
8253
|
const blocks = [];
|
|
8325
8254
|
for (const id of ids) {
|
|
8326
|
-
const block =
|
|
8327
|
-
if (!block || !block.active)
|
|
8255
|
+
const block = messagesState.blocksById.get(id);
|
|
8256
|
+
if (!block || !block.active) {
|
|
8257
|
+
messagesState.markedForCleanup.delete(id);
|
|
8258
|
+
continue;
|
|
8259
|
+
}
|
|
8328
8260
|
blocks.push(block);
|
|
8329
8261
|
}
|
|
8330
8262
|
return blocks;
|
|
@@ -8449,21 +8381,53 @@ function mergeMarkedBlocks(state, markedIds, maxMergedLength) {
|
|
|
8449
8381
|
const savedTokens = Math.max(0, sourceTokens - newSummaryTokens);
|
|
8450
8382
|
return { mergedCount: sourceBlocks.length, savedTokens };
|
|
8451
8383
|
}
|
|
8452
|
-
function
|
|
8453
|
-
|
|
8454
|
-
if (blocks.length < 1) return void 0;
|
|
8455
|
-
const refs = blocks.map((b) => formatBlockRef(b.blockId)).join(", ");
|
|
8456
|
-
const sourceTokens = blocks.reduce(
|
|
8384
|
+
function estimateTokens(blocks) {
|
|
8385
|
+
return blocks.reduce(
|
|
8457
8386
|
(sum, block) => sum + (block.summaryTokens || Math.round(block.summary.length / 4)),
|
|
8458
8387
|
0
|
|
8459
8388
|
);
|
|
8460
|
-
|
|
8461
|
-
|
|
8389
|
+
}
|
|
8390
|
+
function buildNudgeText(state, maxMergedLength) {
|
|
8391
|
+
const marked = collectActiveMarkedBlocks(state);
|
|
8392
|
+
const oldGen = collectActiveOldGenBlocks(state, maxMergedLength);
|
|
8393
|
+
if (oldGen.length === 0) return void 0;
|
|
8394
|
+
const oldGenIds = new Set(oldGen.map((b) => b.blockId));
|
|
8395
|
+
const markedOldGen = marked.filter((b) => oldGenIds.has(b.blockId));
|
|
8396
|
+
const markedOldGenCount = markedOldGen.length;
|
|
8397
|
+
const oldGenCount = oldGen.length;
|
|
8398
|
+
const ratio = markedOldGenCount / oldGenCount;
|
|
8399
|
+
const ratioPct = Math.round(ratio * 100);
|
|
8400
|
+
const escalateMinPct = Math.round(ESCALATE_MIN_RATIO * 100);
|
|
8401
|
+
if (markedOldGenCount >= ESCALATE_MIN_MARKED && ratio >= ESCALATE_MIN_RATIO) {
|
|
8402
|
+
const refs = marked.map((b) => formatBlockRef(b.blockId)).join(", ");
|
|
8403
|
+
const firstRef = formatBlockRef(marked[0].blockId);
|
|
8404
|
+
const lastRef = formatBlockRef(marked[marked.length - 1].blockId);
|
|
8405
|
+
const estimatedSavings = Math.max(0, estimateTokens(marked) - Math.round(maxMergedLength / 4));
|
|
8406
|
+
return [
|
|
8407
|
+
`\u{1F525} ${markedOldGenCount}/${oldGenCount} old-gen blocks marked (${ratioPct}%) \u2014 ready for batch cleanup.`,
|
|
8408
|
+
`Compressing ${refs} (range ${firstRef}\u2013${lastRef}) would free ~${estimatedSavings} tokens in one cache break.`,
|
|
8409
|
+
`Call compress with this range now to consolidate them.`
|
|
8410
|
+
].join(" ");
|
|
8411
|
+
}
|
|
8412
|
+
if (marked.length >= 1) {
|
|
8413
|
+
const refs = marked.map((b) => formatBlockRef(b.blockId)).join(", ");
|
|
8414
|
+
const estimatedSavings = Math.max(0, estimateTokens(marked) - Math.round(maxMergedLength / 4));
|
|
8415
|
+
return [
|
|
8416
|
+
`\u26A0\uFE0F ${marked.length} block(s) marked for batch cleanup (${refs}).`,
|
|
8417
|
+
`Merge-compressing them would free ~${estimatedSavings} tokens.`,
|
|
8418
|
+
marked.length >= 2 ? "They will auto-merge when context pressure reaches the high threshold." : "A single marked block won't auto-merge on its own \u2014 use compress to consolidate it, or unmark_block if no longer needed.",
|
|
8419
|
+
`Mark more old-gen blocks (need \u2265${ESCALATE_MIN_MARKED} at \u2265${escalateMinPct}%) to trigger batch cleanup sooner.`,
|
|
8420
|
+
"To act now, use compress with a range covering these blocks."
|
|
8421
|
+
].join(" ");
|
|
8422
|
+
}
|
|
8423
|
+
const shown = oldGen.slice(0, 5);
|
|
8424
|
+
const oldGenRefs = shown.map((b) => formatBlockRef(b.blockId)).join(", ");
|
|
8425
|
+
const more = oldGenCount > 5 ? ` (+${oldGenCount - 5} more)` : "";
|
|
8462
8426
|
return [
|
|
8463
|
-
`\
|
|
8464
|
-
`
|
|
8465
|
-
|
|
8466
|
-
|
|
8427
|
+
`\u{1F4CB} Context pressure rising \u2014 ${oldGenCount} old-gen compressed block(s) occupy ~${estimateTokens(oldGen)} tokens (${oldGenRefs}${more}).`,
|
|
8428
|
+
`Review which blocks contain information you no longer need, and use mark_block to flag them.`,
|
|
8429
|
+
`Once enough are marked (\u2265${ESCALATE_MIN_MARKED} at \u2265${escalateMinPct}% of old-gen), they'll be batch-merged in one cache break to preserve cache hit rate.`,
|
|
8430
|
+
`Do NOT mark blocks you may still need.`
|
|
8467
8431
|
].join(" ");
|
|
8468
8432
|
}
|
|
8469
8433
|
function runBatchCleanup(state, config, logger, messages) {
|
|
@@ -8508,26 +8472,24 @@ function runBatchCleanup(state, config, logger, messages) {
|
|
|
8508
8472
|
}
|
|
8509
8473
|
if (currentTokens >= highTokens) {
|
|
8510
8474
|
const marked = collectActiveMarkedBlocks(state);
|
|
8511
|
-
if (marked.length
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8475
|
+
if (marked.length >= 2) {
|
|
8476
|
+
const ids = marked.map((b) => b.blockId);
|
|
8477
|
+
const result = mergeMarkedBlocks(state, ids, maxMergedLength);
|
|
8478
|
+
if (result.mergedCount > 0) {
|
|
8479
|
+
logger.info("Batch cleanup tier 2 (high): merged marked blocks", {
|
|
8480
|
+
mergedCount: result.mergedCount,
|
|
8481
|
+
savedTokens: result.savedTokens,
|
|
8482
|
+
currentTokens,
|
|
8483
|
+
highThreshold: batchCleanup.highThreshold
|
|
8484
|
+
});
|
|
8485
|
+
return {
|
|
8486
|
+
tier: 2,
|
|
8487
|
+
action: "merge",
|
|
8488
|
+
mergedCount: result.mergedCount,
|
|
8489
|
+
savedTokens: result.savedTokens
|
|
8490
|
+
};
|
|
8491
|
+
}
|
|
8518
8492
|
}
|
|
8519
|
-
logger.info("Batch cleanup tier 2 (high): merged marked blocks", {
|
|
8520
|
-
mergedCount: result.mergedCount,
|
|
8521
|
-
savedTokens: result.savedTokens,
|
|
8522
|
-
currentTokens,
|
|
8523
|
-
highThreshold: batchCleanup.highThreshold
|
|
8524
|
-
});
|
|
8525
|
-
return {
|
|
8526
|
-
tier: 2,
|
|
8527
|
-
action: "merge",
|
|
8528
|
-
mergedCount: result.mergedCount,
|
|
8529
|
-
savedTokens: result.savedTokens
|
|
8530
|
-
};
|
|
8531
8493
|
}
|
|
8532
8494
|
if (currentTokens >= lowTokens) {
|
|
8533
8495
|
const nudgeText = buildNudgeText(state, maxMergedLength);
|