opencode-acp 1.13.8-dev.1 → 1.14.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 +80 -11
- package/README.zh-CN.md +63 -8
- package/dist/index.js +593 -227
- package/dist/index.js.map +1 -1
- package/dist/lib/commands/compression-targets.d.ts.map +1 -1
- package/dist/lib/commands/recompress.d.ts.map +1 -1
- package/dist/lib/commands/stats.d.ts.map +1 -1
- package/dist/lib/compress/decompress-logic.d.ts +3 -1
- package/dist/lib/compress/decompress-logic.d.ts.map +1 -1
- package/dist/lib/compress/decompress.d.ts.map +1 -1
- package/dist/lib/compress/hide-consumed.d.ts +17 -0
- package/dist/lib/compress/hide-consumed.d.ts.map +1 -0
- package/dist/lib/compress/index.d.ts +1 -0
- package/dist/lib/compress/index.d.ts.map +1 -1
- package/dist/lib/compress/message.d.ts.map +1 -1
- package/dist/lib/compress/pipeline.d.ts +11 -4
- package/dist/lib/compress/pipeline.d.ts.map +1 -1
- package/dist/lib/compress/protected-content.d.ts +1 -1
- package/dist/lib/compress/protected-content.d.ts.map +1 -1
- package/dist/lib/compress/range.d.ts.map +1 -1
- package/dist/lib/compress/state.d.ts.map +1 -1
- package/dist/lib/compress/status.d.ts.map +1 -1
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config.d.ts +6 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/messages/index.d.ts +0 -1
- package/dist/lib/messages/index.d.ts.map +1 -1
- package/dist/lib/messages/inject/inject.d.ts.map +1 -1
- package/dist/lib/messages/inject/utils.d.ts +18 -0
- package/dist/lib/messages/inject/utils.d.ts.map +1 -1
- package/dist/lib/messages/sync.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/state/persistence.d.ts +4 -0
- package/dist/lib/state/persistence.d.ts.map +1 -1
- package/dist/lib/state/state.d.ts.map +1 -1
- package/dist/lib/state/types.d.ts +12 -1
- package/dist/lib/state/types.d.ts.map +1 -1
- package/dist/lib/state/utils.d.ts +12 -1
- package/dist/lib/state/utils.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/lib/messages/inject/subagent-results.d.ts +0 -4
- package/dist/lib/messages/inject/subagent-results.d.ts.map +0 -1
- package/dist/lib/subagents/subagent-results.d.ts +0 -5
- package/dist/lib/subagents/subagent-results.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -909,6 +909,10 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
909
909
|
"compress.emergencyThresholdPercent",
|
|
910
910
|
"compress.maxVisibleSegments",
|
|
911
911
|
"compress.keepEmbedMaxChars",
|
|
912
|
+
"compress.lastSegmentSoftBlock",
|
|
913
|
+
"compress.preserveRecentMessages",
|
|
914
|
+
"compress.preserveRecentTokens",
|
|
915
|
+
"compress.preserveLastUserMessage",
|
|
912
916
|
"gc",
|
|
913
917
|
"gc.algorithm",
|
|
914
918
|
"gc.promotionThreshold",
|
|
@@ -1277,6 +1281,48 @@ function validateConfigTypes(config) {
|
|
|
1277
1281
|
actual: `${compress.keepEmbedMaxChars}`
|
|
1278
1282
|
});
|
|
1279
1283
|
}
|
|
1284
|
+
if (compress.lastSegmentSoftBlock !== void 0 && typeof compress.lastSegmentSoftBlock !== "boolean") {
|
|
1285
|
+
errors.push({
|
|
1286
|
+
key: "compress.lastSegmentSoftBlock",
|
|
1287
|
+
expected: "boolean",
|
|
1288
|
+
actual: typeof compress.lastSegmentSoftBlock
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
if (compress.preserveRecentMessages !== void 0 && typeof compress.preserveRecentMessages !== "number") {
|
|
1292
|
+
errors.push({
|
|
1293
|
+
key: "compress.preserveRecentMessages",
|
|
1294
|
+
expected: "number",
|
|
1295
|
+
actual: typeof compress.preserveRecentMessages
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
if (typeof compress.preserveRecentMessages === "number" && compress.preserveRecentMessages < 0) {
|
|
1299
|
+
errors.push({
|
|
1300
|
+
key: "compress.preserveRecentMessages",
|
|
1301
|
+
expected: "non-negative number (>= 0)",
|
|
1302
|
+
actual: `${compress.preserveRecentMessages}`
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
if (compress.preserveRecentTokens !== void 0 && typeof compress.preserveRecentTokens !== "number") {
|
|
1306
|
+
errors.push({
|
|
1307
|
+
key: "compress.preserveRecentTokens",
|
|
1308
|
+
expected: "number",
|
|
1309
|
+
actual: typeof compress.preserveRecentTokens
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
if (typeof compress.preserveRecentTokens === "number" && compress.preserveRecentTokens < 0) {
|
|
1313
|
+
errors.push({
|
|
1314
|
+
key: "compress.preserveRecentTokens",
|
|
1315
|
+
expected: "non-negative number (>= 0)",
|
|
1316
|
+
actual: `${compress.preserveRecentTokens}`
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
if (compress.preserveLastUserMessage !== void 0 && typeof compress.preserveLastUserMessage !== "boolean") {
|
|
1320
|
+
errors.push({
|
|
1321
|
+
key: "compress.preserveLastUserMessage",
|
|
1322
|
+
expected: "boolean",
|
|
1323
|
+
actual: typeof compress.preserveLastUserMessage
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1280
1326
|
if (typeof compress.iterationNudgeThreshold === "number" && compress.iterationNudgeThreshold < 1) {
|
|
1281
1327
|
errors.push({
|
|
1282
1328
|
key: "compress.iterationNudgeThreshold",
|
|
@@ -1601,7 +1647,10 @@ var defaultConfig = {
|
|
|
1601
1647
|
emergencyThresholdPercent: "98%",
|
|
1602
1648
|
maxVisibleSegments: 50,
|
|
1603
1649
|
keepEmbedMaxChars: 2e3,
|
|
1604
|
-
lastSegmentSoftBlock: true
|
|
1650
|
+
lastSegmentSoftBlock: true,
|
|
1651
|
+
preserveRecentMessages: 20,
|
|
1652
|
+
preserveRecentTokens: 2e4,
|
|
1653
|
+
preserveLastUserMessage: true
|
|
1605
1654
|
},
|
|
1606
1655
|
strategies: {
|
|
1607
1656
|
deduplication: {
|
|
@@ -1776,7 +1825,10 @@ function mergeCompress(base, override) {
|
|
|
1776
1825
|
emergencyThresholdPercent: override.emergencyThresholdPercent ?? base.emergencyThresholdPercent,
|
|
1777
1826
|
maxVisibleSegments: override.maxVisibleSegments ?? base.maxVisibleSegments,
|
|
1778
1827
|
keepEmbedMaxChars: override.keepEmbedMaxChars ?? base.keepEmbedMaxChars,
|
|
1779
|
-
lastSegmentSoftBlock: override.lastSegmentSoftBlock ?? base.lastSegmentSoftBlock
|
|
1828
|
+
lastSegmentSoftBlock: override.lastSegmentSoftBlock ?? base.lastSegmentSoftBlock,
|
|
1829
|
+
preserveRecentMessages: override.preserveRecentMessages ?? base.preserveRecentMessages,
|
|
1830
|
+
preserveRecentTokens: override.preserveRecentTokens ?? base.preserveRecentTokens,
|
|
1831
|
+
preserveLastUserMessage: override.preserveLastUserMessage ?? base.preserveLastUserMessage
|
|
1780
1832
|
};
|
|
1781
1833
|
}
|
|
1782
1834
|
function mergeCommands(base, override) {
|
|
@@ -2851,64 +2903,6 @@ function isToolNameProtected(toolName, patterns) {
|
|
|
2851
2903
|
return globPatterns.some((pattern) => matchesGlob(toolName, pattern));
|
|
2852
2904
|
}
|
|
2853
2905
|
|
|
2854
|
-
// lib/subagents/subagent-results.ts
|
|
2855
|
-
var SUB_AGENT_RESULT_BLOCK_REGEX = /(<task_result>\s*)([\s\S]*?)(\s*<\/task_result>)/i;
|
|
2856
|
-
function getSubAgentId(part) {
|
|
2857
|
-
const sessionId = part?.state?.metadata?.sessionId;
|
|
2858
|
-
if (typeof sessionId !== "string") {
|
|
2859
|
-
return null;
|
|
2860
|
-
}
|
|
2861
|
-
const value = sessionId.trim();
|
|
2862
|
-
return value.length > 0 ? value : null;
|
|
2863
|
-
}
|
|
2864
|
-
function buildSubagentResultText(messages) {
|
|
2865
|
-
const assistantMessages = messages.filter((message) => message.info.role === "assistant");
|
|
2866
|
-
if (assistantMessages.length === 0) {
|
|
2867
|
-
return "";
|
|
2868
|
-
}
|
|
2869
|
-
const lastAssistant = assistantMessages[assistantMessages.length - 1];
|
|
2870
|
-
const lastText = getLastTextPart(lastAssistant);
|
|
2871
|
-
if (assistantMessages.length < 2) {
|
|
2872
|
-
return lastText;
|
|
2873
|
-
}
|
|
2874
|
-
const secondToLastAssistant = assistantMessages[assistantMessages.length - 2];
|
|
2875
|
-
if (!assistantMessageHasCompressTool(secondToLastAssistant)) {
|
|
2876
|
-
return lastText;
|
|
2877
|
-
}
|
|
2878
|
-
const secondToLastText = getLastTextPart(secondToLastAssistant);
|
|
2879
|
-
return [secondToLastText, lastText].filter((text) => text.length > 0).join("\n\n");
|
|
2880
|
-
}
|
|
2881
|
-
function mergeSubagentResult(output, subAgentResultText) {
|
|
2882
|
-
if (!subAgentResultText || typeof output !== "string") {
|
|
2883
|
-
return output;
|
|
2884
|
-
}
|
|
2885
|
-
return output.replace(
|
|
2886
|
-
SUB_AGENT_RESULT_BLOCK_REGEX,
|
|
2887
|
-
(_match, openTag, _body, closeTag) => `${openTag}${subAgentResultText}${closeTag}`
|
|
2888
|
-
);
|
|
2889
|
-
}
|
|
2890
|
-
function getLastTextPart(message) {
|
|
2891
|
-
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
2892
|
-
for (let index = parts.length - 1; index >= 0; index--) {
|
|
2893
|
-
const part = parts[index];
|
|
2894
|
-
if (part.type !== "text" || typeof part.text !== "string") {
|
|
2895
|
-
continue;
|
|
2896
|
-
}
|
|
2897
|
-
const text = part.text.trim();
|
|
2898
|
-
if (!text) {
|
|
2899
|
-
continue;
|
|
2900
|
-
}
|
|
2901
|
-
return text;
|
|
2902
|
-
}
|
|
2903
|
-
return "";
|
|
2904
|
-
}
|
|
2905
|
-
function assistantMessageHasCompressTool(message) {
|
|
2906
|
-
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
2907
|
-
return parts.some(
|
|
2908
|
-
(part) => part.type === "tool" && part.tool === "compress" && part.state?.status === "completed"
|
|
2909
|
-
);
|
|
2910
|
-
}
|
|
2911
|
-
|
|
2912
2906
|
// lib/compress/protected-content.ts
|
|
2913
2907
|
function appendProtectedUserMessages(summary, selection, searchContext, state, enabled) {
|
|
2914
2908
|
if (!enabled) return summary;
|
|
@@ -2975,7 +2969,7 @@ function extractProtectedPromptInfo(text) {
|
|
|
2975
2969
|
}
|
|
2976
2970
|
return protectedTexts;
|
|
2977
2971
|
}
|
|
2978
|
-
async function appendProtectedTools(client, state,
|
|
2972
|
+
async function appendProtectedTools(client, state, summary, selection, searchContext, protectedTools, protectedFilePatterns = []) {
|
|
2979
2973
|
const protectedOutputs = [];
|
|
2980
2974
|
for (const messageId of selection.messageIds) {
|
|
2981
2975
|
const existingCompressionEntry = state.prune.messages.byMessageId.get(messageId);
|
|
@@ -3000,38 +2994,6 @@ async function appendProtectedTools(client, state, allowSubAgents, summary, sele
|
|
|
3000
2994
|
if (part.state?.status === "completed" && part.state?.output) {
|
|
3001
2995
|
output = typeof part.state.output === "string" ? part.state.output : JSON.stringify(part.state.output);
|
|
3002
2996
|
}
|
|
3003
|
-
if (allowSubAgents && part.tool === "task" && part.state?.status === "completed" && typeof part.state?.output === "string") {
|
|
3004
|
-
const cachedSubAgentResult = state.subAgentResultCache.get(part.callID);
|
|
3005
|
-
if (cachedSubAgentResult !== void 0) {
|
|
3006
|
-
if (cachedSubAgentResult) {
|
|
3007
|
-
output = mergeSubagentResult(
|
|
3008
|
-
part.state.output,
|
|
3009
|
-
cachedSubAgentResult
|
|
3010
|
-
);
|
|
3011
|
-
}
|
|
3012
|
-
} else {
|
|
3013
|
-
const subAgentSessionId = getSubAgentId(part);
|
|
3014
|
-
if (subAgentSessionId) {
|
|
3015
|
-
let subAgentResultText = "";
|
|
3016
|
-
try {
|
|
3017
|
-
const subAgentMessages = await fetchSessionMessages(
|
|
3018
|
-
client,
|
|
3019
|
-
subAgentSessionId
|
|
3020
|
-
);
|
|
3021
|
-
subAgentResultText = buildSubagentResultText(subAgentMessages);
|
|
3022
|
-
} catch {
|
|
3023
|
-
subAgentResultText = "";
|
|
3024
|
-
}
|
|
3025
|
-
if (subAgentResultText) {
|
|
3026
|
-
state.subAgentResultCache.set(part.callID, subAgentResultText);
|
|
3027
|
-
output = mergeSubagentResult(
|
|
3028
|
-
part.state.output,
|
|
3029
|
-
subAgentResultText
|
|
3030
|
-
);
|
|
3031
|
-
}
|
|
3032
|
-
}
|
|
3033
|
-
}
|
|
3034
|
-
}
|
|
3035
2997
|
if (output) {
|
|
3036
2998
|
protectedOutputs.push(`
|
|
3037
2999
|
### ${title}
|
|
@@ -3152,7 +3114,20 @@ ${footer}`;
|
|
|
3152
3114
|
function applyCompressionState(state, input, selection, anchorMessageId, blockId, summary, consumedBlockIds, gcConfig) {
|
|
3153
3115
|
const messagesState = state.prune.messages;
|
|
3154
3116
|
const consumed = [...new Set(consumedBlockIds.filter((id) => Number.isInteger(id) && id > 0))];
|
|
3155
|
-
const
|
|
3117
|
+
const createdAt = Date.now();
|
|
3118
|
+
let minConsumedTier;
|
|
3119
|
+
for (const consumedBlockId of consumed) {
|
|
3120
|
+
const cb = messagesState.blocksById.get(consumedBlockId);
|
|
3121
|
+
if (cb) {
|
|
3122
|
+
const cbTier = cb.tier ?? 1;
|
|
3123
|
+
if (minConsumedTier === void 0 || cbTier < minConsumedTier) {
|
|
3124
|
+
minConsumedTier = cbTier;
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
const effectiveMinTier = minConsumedTier ?? 0;
|
|
3129
|
+
const outputTier = Math.min(3, effectiveMinTier + 1);
|
|
3130
|
+
const targetTierForConsumption = effectiveMinTier;
|
|
3156
3131
|
const effectiveMessageIds = new Set(selection.messageIds);
|
|
3157
3132
|
const effectiveToolIds = new Set(selection.toolIds);
|
|
3158
3133
|
for (const consumedBlockId of consumed) {
|
|
@@ -3160,6 +3135,9 @@ function applyCompressionState(state, input, selection, anchorMessageId, blockId
|
|
|
3160
3135
|
if (!consumedBlock) {
|
|
3161
3136
|
continue;
|
|
3162
3137
|
}
|
|
3138
|
+
if ((consumedBlock.tier ?? 1) !== targetTierForConsumption) {
|
|
3139
|
+
continue;
|
|
3140
|
+
}
|
|
3163
3141
|
for (const messageId of consumedBlock.effectiveMessageIds) {
|
|
3164
3142
|
effectiveMessageIds.add(messageId);
|
|
3165
3143
|
}
|
|
@@ -3184,7 +3162,6 @@ function applyCompressionState(state, input, selection, anchorMessageId, blockId
|
|
|
3184
3162
|
initiallyActiveToolIds.add(toolId);
|
|
3185
3163
|
}
|
|
3186
3164
|
}
|
|
3187
|
-
const createdAt = Date.now();
|
|
3188
3165
|
const block = {
|
|
3189
3166
|
blockId,
|
|
3190
3167
|
runId: input.runId,
|
|
@@ -3194,6 +3171,7 @@ function applyCompressionState(state, input, selection, anchorMessageId, blockId
|
|
|
3194
3171
|
summaryTokens: input.summaryTokens,
|
|
3195
3172
|
durationMs: 0,
|
|
3196
3173
|
mode: input.mode,
|
|
3174
|
+
tier: outputTier,
|
|
3197
3175
|
topic: input.topic,
|
|
3198
3176
|
batchTopic: input.batchTopic,
|
|
3199
3177
|
startId: input.startId,
|
|
@@ -3201,8 +3179,11 @@ function applyCompressionState(state, input, selection, anchorMessageId, blockId
|
|
|
3201
3179
|
anchorMessageId,
|
|
3202
3180
|
compressMessageId: input.compressMessageId,
|
|
3203
3181
|
compressCallId: input.compressCallId,
|
|
3204
|
-
includedBlockIds:
|
|
3205
|
-
consumedBlockIds: consumed
|
|
3182
|
+
includedBlockIds: [...consumed],
|
|
3183
|
+
consumedBlockIds: consumed.filter((id) => {
|
|
3184
|
+
const cb = messagesState.blocksById.get(id);
|
|
3185
|
+
return cb && (cb.tier ?? 1) === targetTierForConsumption;
|
|
3186
|
+
}),
|
|
3206
3187
|
parentBlockIds: [],
|
|
3207
3188
|
directMessageIds: [],
|
|
3208
3189
|
directToolIds: [],
|
|
@@ -3231,6 +3212,9 @@ function applyCompressionState(state, input, selection, anchorMessageId, blockId
|
|
|
3231
3212
|
if (!consumedBlock || !consumedBlock.active) {
|
|
3232
3213
|
continue;
|
|
3233
3214
|
}
|
|
3215
|
+
if ((consumedBlock.tier ?? 1) !== targetTierForConsumption) {
|
|
3216
|
+
continue;
|
|
3217
|
+
}
|
|
3234
3218
|
consumedBlock.active = false;
|
|
3235
3219
|
consumedBlock.deactivatedAt = deactivatedAt;
|
|
3236
3220
|
consumedBlock.deactivatedByBlockId = blockId;
|
|
@@ -3256,6 +3240,9 @@ function applyCompressionState(state, input, selection, anchorMessageId, blockId
|
|
|
3256
3240
|
if (!consumedBlock) {
|
|
3257
3241
|
continue;
|
|
3258
3242
|
}
|
|
3243
|
+
if ((consumedBlock.tier ?? 1) !== targetTierForConsumption) {
|
|
3244
|
+
continue;
|
|
3245
|
+
}
|
|
3259
3246
|
for (const messageId of consumedBlock.effectiveMessageIds) {
|
|
3260
3247
|
const entry = messagesState.byMessageId.get(messageId);
|
|
3261
3248
|
if (!entry) {
|
|
@@ -3321,6 +3308,14 @@ function applyCompressionState(state, input, selection, anchorMessageId, blockId
|
|
|
3321
3308
|
block.directMessageIds = [...newlyCompressedMessageIds];
|
|
3322
3309
|
block.directToolIds = [...newlyCompressedToolIds];
|
|
3323
3310
|
block.compressedTokens = compressedTokens;
|
|
3311
|
+
let effectiveTokens = compressedTokens;
|
|
3312
|
+
for (const consumedBlockId of consumed) {
|
|
3313
|
+
const cb = messagesState.blocksById.get(consumedBlockId);
|
|
3314
|
+
if (cb && (cb.tier ?? 1) === targetTierForConsumption) {
|
|
3315
|
+
effectiveTokens += cb.effectiveCompressedTokens ?? cb.compressedTokens;
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
block.effectiveCompressedTokens = effectiveTokens;
|
|
3324
3319
|
state.stats.pruneTokenCounter += compressedTokens;
|
|
3325
3320
|
state.stats.totalPruneTokens += state.stats.pruneTokenCounter;
|
|
3326
3321
|
state.stats.pruneTokenCounter = 0;
|
|
@@ -3683,6 +3678,7 @@ function loadPruneMessagesState(persisted) {
|
|
|
3683
3678
|
runId: typeof block.runId === "number" && Number.isInteger(block.runId) && block.runId > 0 ? block.runId : blockId,
|
|
3684
3679
|
active: block.active === true,
|
|
3685
3680
|
deactivatedByUser: block.deactivatedByUser === true,
|
|
3681
|
+
deactivatedByUserDeep: block.deactivatedByUserDeep === true ? true : void 0,
|
|
3686
3682
|
compressedTokens: typeof block.compressedTokens === "number" && Number.isFinite(block.compressedTokens) ? Math.max(0, block.compressedTokens) : 0,
|
|
3687
3683
|
summaryTokens: typeof block.summaryTokens === "number" && Number.isFinite(block.summaryTokens) ? Math.max(0, block.summaryTokens) : typeof block.summary === "string" ? countTokens2(block.summary) : 0,
|
|
3688
3684
|
durationMs: typeof block.durationMs === "number" && Number.isFinite(block.durationMs) ? Math.max(0, block.durationMs) : 0,
|
|
@@ -3706,7 +3702,9 @@ function loadPruneMessagesState(persisted) {
|
|
|
3706
3702
|
deactivatedByBlockId: typeof block.deactivatedByBlockId === "number" && Number.isInteger(block.deactivatedByBlockId) ? block.deactivatedByBlockId : void 0,
|
|
3707
3703
|
summary: typeof block.summary === "string" ? block.summary : "",
|
|
3708
3704
|
survivedCount: typeof block.survivedCount === "number" && Number.isFinite(block.survivedCount) ? Math.max(0, Math.floor(block.survivedCount)) : 0,
|
|
3709
|
-
generation: block.generation === "young" || block.generation === "old" ? block.generation : void 0
|
|
3705
|
+
generation: block.generation === "young" || block.generation === "old" ? block.generation : void 0,
|
|
3706
|
+
tier: block.tier === 1 || block.tier === 2 || block.tier === 3 ? block.tier : void 0,
|
|
3707
|
+
effectiveCompressedTokens: typeof block.effectiveCompressedTokens === "number" && Number.isFinite(block.effectiveCompressedTokens) ? Math.max(0, block.effectiveCompressedTokens) : void 0
|
|
3710
3708
|
});
|
|
3711
3709
|
}
|
|
3712
3710
|
}
|
|
@@ -3765,17 +3763,38 @@ function collectTurnNudgeAnchors(messages) {
|
|
|
3765
3763
|
}
|
|
3766
3764
|
return anchors;
|
|
3767
3765
|
}
|
|
3768
|
-
function getActiveSummaryTokenUsage(state) {
|
|
3766
|
+
function getActiveSummaryTokenUsage(state, visibleMessageIds) {
|
|
3769
3767
|
let total = 0;
|
|
3770
3768
|
for (const blockId of state.prune.messages.activeBlockIds) {
|
|
3771
3769
|
const block = state.prune.messages.blocksById.get(blockId);
|
|
3772
3770
|
if (!block || !block.active) {
|
|
3773
3771
|
continue;
|
|
3774
3772
|
}
|
|
3773
|
+
if (visibleMessageIds && block.compressMessageId) {
|
|
3774
|
+
if (!visibleMessageIds.has(block.compressMessageId)) {
|
|
3775
|
+
continue;
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3775
3778
|
total += block.summaryTokens;
|
|
3776
3779
|
}
|
|
3777
3780
|
return total;
|
|
3778
3781
|
}
|
|
3782
|
+
function getTierTokenUsage(state) {
|
|
3783
|
+
let tier1Tokens = 0;
|
|
3784
|
+
let tier2Tokens = 0;
|
|
3785
|
+
let tier3Tokens = 0;
|
|
3786
|
+
for (const blockId of state.prune.messages.activeBlockIds) {
|
|
3787
|
+
const block = state.prune.messages.blocksById.get(blockId);
|
|
3788
|
+
if (!block || !block.active) {
|
|
3789
|
+
continue;
|
|
3790
|
+
}
|
|
3791
|
+
const tier = block.tier ?? 1;
|
|
3792
|
+
if (tier === 1) tier1Tokens += block.summaryTokens;
|
|
3793
|
+
else if (tier === 2) tier2Tokens += block.summaryTokens;
|
|
3794
|
+
else tier3Tokens += block.summaryTokens;
|
|
3795
|
+
}
|
|
3796
|
+
return { tier1Tokens, tier2Tokens, tier3Tokens };
|
|
3797
|
+
}
|
|
3779
3798
|
function resetOnCompaction(state) {
|
|
3780
3799
|
state.toolParameters.clear();
|
|
3781
3800
|
state.prune.tools = /* @__PURE__ */ new Map();
|
|
@@ -3787,6 +3806,8 @@ function resetOnCompaction(state) {
|
|
|
3787
3806
|
lastPerMessageNudgeTokens: void 0,
|
|
3788
3807
|
lastNudgeShownTokens: void 0,
|
|
3789
3808
|
lastToolOutputNudgeTokens: void 0,
|
|
3809
|
+
lastTier2NudgeTokens: void 0,
|
|
3810
|
+
lastTier3NudgeTokens: void 0,
|
|
3790
3811
|
shouldInjectThisTurn: void 0,
|
|
3791
3812
|
compressBaselineSet: false
|
|
3792
3813
|
};
|
|
@@ -3863,6 +3884,8 @@ async function saveSessionState(sessionState, logger, sessionName) {
|
|
|
3863
3884
|
lastPerMessageNudgeTokens: sessionState.nudges.lastPerMessageNudgeTokens,
|
|
3864
3885
|
lastNudgeShownTokens: sessionState.nudges.lastNudgeShownTokens,
|
|
3865
3886
|
lastToolOutputNudgeTokens: sessionState.nudges.lastToolOutputNudgeTokens,
|
|
3887
|
+
lastTier2NudgeTokens: sessionState.nudges.lastTier2NudgeTokens,
|
|
3888
|
+
lastTier3NudgeTokens: sessionState.nudges.lastTier3NudgeTokens,
|
|
3866
3889
|
compressBaselineSet: sessionState.nudges.compressBaselineSet
|
|
3867
3890
|
},
|
|
3868
3891
|
stats: sessionState.stats,
|
|
@@ -4475,6 +4498,8 @@ function createSessionState() {
|
|
|
4475
4498
|
lastPerMessageNudgeTokens: void 0,
|
|
4476
4499
|
lastNudgeShownTokens: void 0,
|
|
4477
4500
|
lastToolOutputNudgeTokens: void 0,
|
|
4501
|
+
lastTier2NudgeTokens: void 0,
|
|
4502
|
+
lastTier3NudgeTokens: void 0,
|
|
4478
4503
|
shouldInjectThisTurn: void 0,
|
|
4479
4504
|
compressBaselineSet: false
|
|
4480
4505
|
},
|
|
@@ -4487,7 +4512,6 @@ function createSessionState() {
|
|
|
4487
4512
|
pendingByCallId: /* @__PURE__ */ new Map()
|
|
4488
4513
|
},
|
|
4489
4514
|
toolParameters: /* @__PURE__ */ new Map(),
|
|
4490
|
-
subAgentResultCache: /* @__PURE__ */ new Map(),
|
|
4491
4515
|
toolIdList: [],
|
|
4492
4516
|
messageIds: {
|
|
4493
4517
|
byRawId: /* @__PURE__ */ new Map(),
|
|
@@ -4519,6 +4543,8 @@ function resetSessionState(state) {
|
|
|
4519
4543
|
lastPerMessageNudgeTokens: void 0,
|
|
4520
4544
|
lastNudgeShownTokens: void 0,
|
|
4521
4545
|
lastToolOutputNudgeTokens: void 0,
|
|
4546
|
+
lastTier2NudgeTokens: void 0,
|
|
4547
|
+
lastTier3NudgeTokens: void 0,
|
|
4522
4548
|
shouldInjectThisTurn: void 0,
|
|
4523
4549
|
compressBaselineSet: false
|
|
4524
4550
|
};
|
|
@@ -4527,7 +4553,6 @@ function resetSessionState(state) {
|
|
|
4527
4553
|
totalPruneTokens: 0
|
|
4528
4554
|
};
|
|
4529
4555
|
state.toolParameters.clear();
|
|
4530
|
-
state.subAgentResultCache.clear();
|
|
4531
4556
|
state.toolIdList = [];
|
|
4532
4557
|
state.messageIds = {
|
|
4533
4558
|
byRawId: /* @__PURE__ */ new Map(),
|
|
@@ -4576,6 +4601,8 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
|
|
|
4576
4601
|
state.nudges.lastPerMessageNudgeTokens = persisted.nudges.lastPerMessageNudgeTokens;
|
|
4577
4602
|
state.nudges.lastNudgeShownTokens = persisted.nudges.lastNudgeShownTokens;
|
|
4578
4603
|
state.nudges.lastToolOutputNudgeTokens = persisted.nudges.lastToolOutputNudgeTokens;
|
|
4604
|
+
state.nudges.lastTier2NudgeTokens = persisted.nudges.lastTier2NudgeTokens ?? persisted.nudges.lastTierNudgeTokens;
|
|
4605
|
+
state.nudges.lastTier3NudgeTokens = persisted.nudges.lastTier3NudgeTokens;
|
|
4579
4606
|
state.nudges.compressBaselineSet = persisted.nudges.compressBaselineSet ?? false;
|
|
4580
4607
|
state.stats = {
|
|
4581
4608
|
pruneTokenCounter: persisted.stats?.pruneTokenCounter || 0,
|
|
@@ -5139,7 +5166,7 @@ async function sendCompressNotification(client, logger, config, state, sessionId
|
|
|
5139
5166
|
const logTopics = entries.map((e) => state.prune.messages.blocksById.get(e.blockId)?.topic ?? "?");
|
|
5140
5167
|
const logCompressedTokens = entries.reduce((sum, e) => {
|
|
5141
5168
|
const block = state.prune.messages.blocksById.get(e.blockId);
|
|
5142
|
-
return sum + (block?.compressedTokens ?? 0);
|
|
5169
|
+
return sum + (block?.effectiveCompressedTokens ?? block?.compressedTokens ?? 0);
|
|
5143
5170
|
}, 0);
|
|
5144
5171
|
const logSummaryTokens = entries.reduce((sum, e) => sum + e.summaryTokens, 0);
|
|
5145
5172
|
logger.info("Compression completed", {
|
|
@@ -6083,7 +6110,7 @@ function evaluatePreCommitQuality(rawMessages, messageIds, messageTokenById, sum
|
|
|
6083
6110
|
}
|
|
6084
6111
|
}
|
|
6085
6112
|
|
|
6086
|
-
// node_modules/context-compress-algorithms/dist/chunk-
|
|
6113
|
+
// node_modules/context-compress-algorithms/dist/chunk-EBKMI537.js
|
|
6087
6114
|
var COMPRESS_PHILOSOPHY = `Compression Philosophy:
|
|
6088
6115
|
- All compression serves the primary task, but be frugal.
|
|
6089
6116
|
- Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.
|
|
@@ -6128,6 +6155,69 @@ PRIORITY \u2014 when the summary must be compact, preserve in this order:
|
|
|
6128
6155
|
5. Lessons learned: what failed and why.
|
|
6129
6156
|
|
|
6130
6157
|
Write dense, scannable bullets \u2014 not narrative prose. If the range spans distinct concerns (request \u2192 findings \u2192 decision), group bullets under short thematic headers so a reader can scan to the part they need. Every line must earn its place. Do not mimic the style of existing summaries in context; follow these rules.`;
|
|
6158
|
+
var TIER2_DISTILL_RULES = `TIER 2 COMPRESSION \u2014 DISTILLATION
|
|
6159
|
+
|
|
6160
|
+
You are compressing historical summaries (not raw conversation). These summaries have already captured the details. Your job is to DISTILL them: extract only what matters for future work, discard the process.
|
|
6161
|
+
|
|
6162
|
+
KEEP \u2014 these are the only things that survive distillation:
|
|
6163
|
+
- Decisions and their rationale ("chose X over Y because Z" \u2014 the "because" is load-bearing).
|
|
6164
|
+
- Final outcomes: version numbers shipped, PR numbers merged/closed, bugs fixed or deferred.
|
|
6165
|
+
- Key lessons: what failed and why ("tried X, failed because Y"). These prevent repeating mistakes.
|
|
6166
|
+
- Critical constraints discovered ("must support Node 22", "AGENTS.md forbids as any").
|
|
6167
|
+
- Design decisions with architectural impact ("chose compress-as-anchor over synthetic messages because prefix cache").
|
|
6168
|
+
- Whether content is OBSOLETE or SUPERSEDED \u2014 mark with one line: "[SUPERSEDED by PR #NNN]" or "[OBSOLETE: deleted in vX.Y.Z]". Do NOT keep the obsolete content's details \u2014 just the marker and reason.
|
|
6169
|
+
- Function/class/type names and module paths that are the SUBJECT of the work \u2014 e.g., "fixed filterCompressedRanges in prune.ts", "added SessionStateRegistry in state.ts". Not exact line numbers or full signatures \u2014 just enough to LOCATE the code without searching.
|
|
6170
|
+
- Exploration findings: if a block was exploratory with no decision, keep the CONCLUSION in one line ("explored X, not viable because Y"). Do not keep the exploration process.
|
|
6171
|
+
|
|
6172
|
+
DROP \u2014 these were useful during the work but are no longer needed:
|
|
6173
|
+
- Exact line numbers, diffs, verbose function signatures, full code listings.
|
|
6174
|
+
- Build/deploy process details, test execution steps.
|
|
6175
|
+
- Review process details (who reviewed, what rounds, test counts).
|
|
6176
|
+
- Verbose logs, command output, intermediate debugging steps.
|
|
6177
|
+
|
|
6178
|
+
FORMAT:
|
|
6179
|
+
- Start each distilled block with a source header line:
|
|
6180
|
+
\`Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]\`
|
|
6181
|
+
Example: \`Source: b5+b7 (56K+44K\u2192268 tok, 375x). [Tool-result recap + publish]\`
|
|
6182
|
+
- 3-5 bullet points per source block, each a self-contained fact.
|
|
6183
|
+
- Dense, scannable \u2014 no narrative prose.
|
|
6184
|
+
- Start with the outcome, not the process: "v1.13.0 shipped (7 PRs bundled)" not "implemented 7 PRs then reviewed then merged".
|
|
6185
|
+
- Cross-block synthesis: if multiple source blocks cover the same topic (same PR, same feature, same bug), MERGE them into a single group of bullets. Do not repeat the same fact from different blocks \u2014 keep it once under the most relevant source header.
|
|
6186
|
+
|
|
6187
|
+
SIZE TARGET: 50-150 tokens per source block (excluding the header). If you can't fit it in 150 tokens, you're keeping too much process. If a block has nothing worth keeping (pure noise), output just the header followed by "[no actionable content]".`;
|
|
6188
|
+
var TIER3_CONDENSE_RULES = `TIER 3 COMPRESSION \u2014 ULTRA-CONDENSATION
|
|
6189
|
+
|
|
6190
|
+
You are compressing distilled summaries (Tier 2) into ultra-condensed facts (Tier 3). The distilled summaries already contain only decisions and outcomes. Your job is to reduce them to bare factual references.
|
|
6191
|
+
|
|
6192
|
+
PRIORITY \u2014 when a source block has more facts than the size target allows, keep in this order:
|
|
6193
|
+
1. Shipped outcomes (versions released, PRs merged) \u2014 these are permanent record.
|
|
6194
|
+
2. Open work (PRs/issues still pending) \u2014 these may need follow-up.
|
|
6195
|
+
3. Key decisions with architectural impact ("chose X over Y because Z").
|
|
6196
|
+
4. Critical constraints ("must support Node 22").
|
|
6197
|
+
Drop everything else. Tier 3 is a lookup index, not a knowledge base.
|
|
6198
|
+
|
|
6199
|
+
FORMAT:
|
|
6200
|
+
- Start with a source header line:
|
|
6201
|
+
\`Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]\`
|
|
6202
|
+
- Output 1-3 facts per source block. Each fact is a single line: subject + outcome.
|
|
6203
|
+
- No explanations, no rationale, no process \u2014 just the fact.
|
|
6204
|
+
- Format: "[PR/Issue/Version] \u2014 [outcome in \u22648 words]"
|
|
6205
|
+
- Merge related facts from different source blocks if they concern the same topic.
|
|
6206
|
+
|
|
6207
|
+
EXAMPLES:
|
|
6208
|
+
- "v1.13.0 shipped \u2014 quality gate + GC fix (7 PRs)"
|
|
6209
|
+
- "PR #196 merged \u2014 preserve-first-user (supersedes #169)"
|
|
6210
|
+
- "Bug 1214 fixed \u2014 compress consumed all user messages"
|
|
6211
|
+
- "Chose compress-as-anchor \u2014 prefix cache benefit over synthetic injection"
|
|
6212
|
+
- "Constraint: AGENTS.md forbids as any \u2014 never suppress types"
|
|
6213
|
+
|
|
6214
|
+
DROP:
|
|
6215
|
+
- Multi-sentence context. If a fact needs >1 sentence, it's too detailed for Tier 3.
|
|
6216
|
+
- Lessons learned ("tried X, failed because Y") \u2014 drop UNLESS the failure is likely to recur and the block is <30 days old.
|
|
6217
|
+
- Design rationale details \u2014 keep the decision, drop the "because" unless it's a critical constraint.
|
|
6218
|
+
- Anything marked [OBSOLETE] or [SUPERSEDED] \u2014 drop entirely, note "[N blocks obsolete]" in the summary.
|
|
6219
|
+
|
|
6220
|
+
SIZE TARGET: 30-60 tokens per source block (including header). For a batch of N source blocks, total output \u2248 N \xD7 40 tokens. If a source block has only one trivial fact, output just the header + one line.`;
|
|
6131
6221
|
|
|
6132
6222
|
// lib/compress/quality-gate/rejection.ts
|
|
6133
6223
|
function formatMetric(result, name) {
|
|
@@ -6272,17 +6362,6 @@ async function finalizeSession(ctx, toolCtx, rawMessages, entries, batchTopic) {
|
|
|
6272
6362
|
contextTokensBefore
|
|
6273
6363
|
);
|
|
6274
6364
|
}
|
|
6275
|
-
function getLastVisibleMessageId(rawMessages, state) {
|
|
6276
|
-
for (let i = rawMessages.length - 1; i >= 0; i--) {
|
|
6277
|
-
const msg = rawMessages[i];
|
|
6278
|
-
const id = msg?.info?.id;
|
|
6279
|
-
if (!id || typeof id !== "string") continue;
|
|
6280
|
-
if (isSyntheticMessage(msg)) continue;
|
|
6281
|
-
if (state.prune.messages.byMessageId.has(id)) continue;
|
|
6282
|
-
return id;
|
|
6283
|
-
}
|
|
6284
|
-
return null;
|
|
6285
|
-
}
|
|
6286
6365
|
function checkPhantomBlock(state, plans) {
|
|
6287
6366
|
for (let i = 0; i < plans.length; i++) {
|
|
6288
6367
|
const plan = plans[i];
|
|
@@ -6300,6 +6379,16 @@ function checkPhantomBlock(state, plans) {
|
|
|
6300
6379
|
return !entry || entry.activeBlockIds.length === 0;
|
|
6301
6380
|
});
|
|
6302
6381
|
if (!hasNew) {
|
|
6382
|
+
if (plan.consumedBlockIds.length >= 2) {
|
|
6383
|
+
const tiers = new Set(
|
|
6384
|
+
plan.consumedBlockIds.map(
|
|
6385
|
+
(id) => state.prune.messages.blocksById.get(id)?.tier ?? 1
|
|
6386
|
+
)
|
|
6387
|
+
);
|
|
6388
|
+
if (tiers.size === 1) {
|
|
6389
|
+
continue;
|
|
6390
|
+
}
|
|
6391
|
+
}
|
|
6303
6392
|
return new Error(
|
|
6304
6393
|
`Compression range ${i + 1} contains only already-compressed messages (0 new direct messages, 0 tokens saved). Nothing to compress \u2014 pick a range with visible, uncompressed content. Use \`acp_status({scope:"uncompressed"})\` to see which ranges are still compressible.`
|
|
6305
6394
|
);
|
|
@@ -6307,16 +6396,71 @@ function checkPhantomBlock(state, plans) {
|
|
|
6307
6396
|
}
|
|
6308
6397
|
return null;
|
|
6309
6398
|
}
|
|
6310
|
-
function
|
|
6399
|
+
function computeProtectedRawIds(rawMessages, state, compress) {
|
|
6400
|
+
const preserveN = compress.preserveRecentMessages ?? 20;
|
|
6401
|
+
const preserveTokens = compress.preserveRecentTokens ?? 2e4;
|
|
6402
|
+
const preserveLastUser = compress.preserveLastUserMessage ?? true;
|
|
6403
|
+
const result = /* @__PURE__ */ new Set();
|
|
6404
|
+
const visible = [];
|
|
6405
|
+
for (const msg of rawMessages) {
|
|
6406
|
+
const id = msg?.info?.id;
|
|
6407
|
+
if (!id || typeof id !== "string") continue;
|
|
6408
|
+
if (isSyntheticMessage(msg)) continue;
|
|
6409
|
+
if (isIgnoredUserMessage(msg)) continue;
|
|
6410
|
+
if (state.prune.messages.byMessageId.has(id)) continue;
|
|
6411
|
+
let tokens = 0;
|
|
6412
|
+
for (const part of msg.parts || []) {
|
|
6413
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
6414
|
+
tokens += Math.round(part.text.length / 4);
|
|
6415
|
+
} else if (part.type !== "text" && part.type !== "reasoning") {
|
|
6416
|
+
tokens += Math.round(JSON.stringify(part).length / 4);
|
|
6417
|
+
}
|
|
6418
|
+
}
|
|
6419
|
+
visible.push({ id, tokens, isUser: msg.info.role === "user" });
|
|
6420
|
+
}
|
|
6421
|
+
if (preserveN > 0) {
|
|
6422
|
+
for (const m of visible.slice(-preserveN)) {
|
|
6423
|
+
result.add(m.id);
|
|
6424
|
+
}
|
|
6425
|
+
}
|
|
6426
|
+
if (preserveTokens > 0) {
|
|
6427
|
+
let tokenAccum = 0;
|
|
6428
|
+
for (let i = visible.length - 1; i >= 0 && tokenAccum < preserveTokens; i--) {
|
|
6429
|
+
result.add(visible[i].id);
|
|
6430
|
+
tokenAccum += visible[i].tokens;
|
|
6431
|
+
}
|
|
6432
|
+
}
|
|
6433
|
+
if (preserveLastUser) {
|
|
6434
|
+
for (let i = visible.length - 1; i >= 0; i--) {
|
|
6435
|
+
if (visible[i].isUser) {
|
|
6436
|
+
result.add(visible[i].id);
|
|
6437
|
+
break;
|
|
6438
|
+
}
|
|
6439
|
+
}
|
|
6440
|
+
}
|
|
6441
|
+
return result;
|
|
6442
|
+
}
|
|
6443
|
+
function checkProtectedRange(ctx, allPlanMessageIds, rawMessages, dangerous) {
|
|
6311
6444
|
if (ctx.config.compress.lastSegmentSoftBlock === false) return null;
|
|
6312
|
-
const
|
|
6313
|
-
if (
|
|
6314
|
-
const
|
|
6315
|
-
|
|
6445
|
+
const protectedIds = computeProtectedRawIds(rawMessages, ctx.state, ctx.config.compress);
|
|
6446
|
+
if (protectedIds.size === 0) return null;
|
|
6447
|
+
const coveredProtected = [];
|
|
6448
|
+
for (const ids of allPlanMessageIds) {
|
|
6449
|
+
for (const id of ids) {
|
|
6450
|
+
if (protectedIds.has(id)) {
|
|
6451
|
+
coveredProtected.push(id);
|
|
6452
|
+
}
|
|
6453
|
+
}
|
|
6454
|
+
}
|
|
6455
|
+
if (coveredProtected.length === 0) return null;
|
|
6316
6456
|
if (dangerous) return null;
|
|
6457
|
+
const sample = coveredProtected.slice(0, 3).join(", ");
|
|
6458
|
+
const nMsgs = ctx.config.compress.preserveRecentMessages ?? 20;
|
|
6459
|
+
const nToks = ctx.config.compress.preserveRecentTokens ?? 2e4;
|
|
6317
6460
|
return new Error(
|
|
6318
|
-
`This range includes
|
|
6461
|
+
`This range includes ${coveredProtected.length} protected recent message(s) (${sample}), which are likely still needed for the current task step.
|
|
6319
6462
|
|
|
6463
|
+
Protected zone: last ${nMsgs} messages + last ${nToks >= 1e3 ? `${nToks / 1e3}K` : nToks} tokens + most recent user message.
|
|
6320
6464
|
If you are certain this content is genuinely consumed and must be compressed, re-issue the call with \`dangerous: true\`.
|
|
6321
6465
|
Otherwise, compress older ranges that do not include the tail of the conversation.`
|
|
6322
6466
|
);
|
|
@@ -6515,7 +6659,7 @@ function createCompressMessageTool(factoryCtx) {
|
|
|
6515
6659
|
}
|
|
6516
6660
|
}
|
|
6517
6661
|
const dangerous = args.dangerous === true;
|
|
6518
|
-
const lastSegmentError =
|
|
6662
|
+
const lastSegmentError = checkProtectedRange(
|
|
6519
6663
|
ctx,
|
|
6520
6664
|
plans.map((p) => p.selection.messageIds),
|
|
6521
6665
|
rawMessages,
|
|
@@ -6535,7 +6679,6 @@ function createCompressMessageTool(factoryCtx) {
|
|
|
6535
6679
|
const summaryWithTools = await appendProtectedTools(
|
|
6536
6680
|
ctx.client,
|
|
6537
6681
|
ctx.state,
|
|
6538
|
-
ctx.config.experimental.allowSubAgents,
|
|
6539
6682
|
summaryWithPromptInfo,
|
|
6540
6683
|
plan.selection,
|
|
6541
6684
|
searchContext,
|
|
@@ -6736,7 +6879,7 @@ function createCompressRangeTool(factoryCtx) {
|
|
|
6736
6879
|
}
|
|
6737
6880
|
}
|
|
6738
6881
|
const dangerous = args.dangerous === true;
|
|
6739
|
-
const lastSegmentError =
|
|
6882
|
+
const lastSegmentError = checkProtectedRange(
|
|
6740
6883
|
ctx,
|
|
6741
6884
|
filteredPlans.map((p) => p.selection.messageIds),
|
|
6742
6885
|
rawMessages,
|
|
@@ -6780,7 +6923,6 @@ function createCompressRangeTool(factoryCtx) {
|
|
|
6780
6923
|
const summaryWithTools = await appendProtectedTools(
|
|
6781
6924
|
ctx.client,
|
|
6782
6925
|
ctx.state,
|
|
6783
|
-
ctx.config.experimental.allowSubAgents,
|
|
6784
6926
|
summaryWithPromptInfo,
|
|
6785
6927
|
plan.selection,
|
|
6786
6928
|
searchContext,
|
|
@@ -7009,10 +7151,9 @@ var syncCompressionBlocks = (state, logger, messages) => {
|
|
|
7009
7151
|
messagesState.activeBlockIds.clear();
|
|
7010
7152
|
messagesState.activeByAnchorMessageId.clear();
|
|
7011
7153
|
const now = Date.now();
|
|
7012
|
-
const missingOriginBlockIds = [];
|
|
7013
7154
|
const orderedBlocks = Array.from(messagesState.blocksById.values()).sort(sortBlocksByCreation);
|
|
7014
7155
|
for (const block of orderedBlocks) {
|
|
7015
|
-
if (block.deactivatedByUser) {
|
|
7156
|
+
if (block.deactivatedByUser || block.deactivatedByUserDeep) {
|
|
7016
7157
|
block.active = false;
|
|
7017
7158
|
if (block.deactivatedAt === void 0) {
|
|
7018
7159
|
block.deactivatedAt = now;
|
|
@@ -7020,12 +7161,6 @@ var syncCompressionBlocks = (state, logger, messages) => {
|
|
|
7020
7161
|
block.deactivatedByBlockId = void 0;
|
|
7021
7162
|
continue;
|
|
7022
7163
|
}
|
|
7023
|
-
if (typeof block.anchorMessageId === "string" && block.anchorMessageId.length > 0 && !messageIds.has(block.anchorMessageId)) {
|
|
7024
|
-
block.active = false;
|
|
7025
|
-
block.deactivatedAt = now;
|
|
7026
|
-
block.deactivatedByBlockId = void 0;
|
|
7027
|
-
continue;
|
|
7028
|
-
}
|
|
7029
7164
|
for (const consumedBlockId of block.consumedBlockIds) {
|
|
7030
7165
|
if (!messagesState.activeBlockIds.has(consumedBlockId)) {
|
|
7031
7166
|
continue;
|
|
@@ -7070,9 +7205,8 @@ var syncCompressionBlocks = (state, logger, messages) => {
|
|
|
7070
7205
|
reactivatedCount++;
|
|
7071
7206
|
}
|
|
7072
7207
|
}
|
|
7073
|
-
if (
|
|
7208
|
+
if (deactivatedCount > 0 || reactivatedCount > 0) {
|
|
7074
7209
|
logger.info("Synced compress block state", {
|
|
7075
|
-
missingOriginCount: missingOriginBlockIds.length,
|
|
7076
7210
|
deactivatedCount,
|
|
7077
7211
|
reactivatedCount
|
|
7078
7212
|
});
|
|
@@ -7483,7 +7617,7 @@ function listPriorityRefsBeforeIndex(messages, priorities, anchorIndex, priority
|
|
|
7483
7617
|
return refs;
|
|
7484
7618
|
}
|
|
7485
7619
|
|
|
7486
|
-
// node_modules/context-compress-algorithms/dist/chunk-
|
|
7620
|
+
// node_modules/context-compress-algorithms/dist/chunk-BZYW3CH5.js
|
|
7487
7621
|
var NUDGE_GROWTH_FLOOR = 6e3;
|
|
7488
7622
|
var NUDGE_GROWTH_CAP = 5e4;
|
|
7489
7623
|
var NUDGE_GROWTH_RATIO = 0.05;
|
|
@@ -7616,7 +7750,10 @@ function resolveContextTokenLimit(config, state, providerId, modelId, threshold)
|
|
|
7616
7750
|
return parseLimitValue(globalLimit);
|
|
7617
7751
|
}
|
|
7618
7752
|
function isContextOverLimits(config, state, providerId, modelId, messages) {
|
|
7619
|
-
const summaryTokenExtension = config.compress.summaryBuffer ? getActiveSummaryTokenUsage(
|
|
7753
|
+
const summaryTokenExtension = config.compress.summaryBuffer ? getActiveSummaryTokenUsage(
|
|
7754
|
+
state,
|
|
7755
|
+
new Set(messages.map((m) => m.info.id))
|
|
7756
|
+
) : 0;
|
|
7620
7757
|
const resolvedMaxContextLimit = resolveContextTokenLimit(
|
|
7621
7758
|
config,
|
|
7622
7759
|
state,
|
|
@@ -8239,6 +8376,57 @@ ${lines2.join("\n")}`;
|
|
|
8239
8376
|
return `Compressible ranges (oldest first):
|
|
8240
8377
|
${lines.join("\n")}`;
|
|
8241
8378
|
}
|
|
8379
|
+
function computeProtectedRefs(messages, state, compress) {
|
|
8380
|
+
if (compress.lastSegmentSoftBlock === false) return /* @__PURE__ */ new Set();
|
|
8381
|
+
const preserveN = compress.preserveRecentMessages ?? 20;
|
|
8382
|
+
const preserveTokens = compress.preserveRecentTokens ?? 2e4;
|
|
8383
|
+
const preserveLastUser = compress.preserveLastUserMessage ?? true;
|
|
8384
|
+
const result = /* @__PURE__ */ new Set();
|
|
8385
|
+
const visible = [];
|
|
8386
|
+
for (const msg of messages) {
|
|
8387
|
+
if (isSyntheticMessage(msg)) continue;
|
|
8388
|
+
if (isIgnoredUserMessage(msg)) continue;
|
|
8389
|
+
const ref = state.messageIds.byRawId.get(msg.info.id);
|
|
8390
|
+
if (!ref) continue;
|
|
8391
|
+
if (state.prune.messages.byMessageId.has(msg.info.id)) continue;
|
|
8392
|
+
let tokens = 0;
|
|
8393
|
+
for (const part of msg.parts || []) {
|
|
8394
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
8395
|
+
tokens += Math.round(part.text.length / 4);
|
|
8396
|
+
} else if (part.type !== "text" && part.type !== "reasoning") {
|
|
8397
|
+
tokens += Math.round(JSON.stringify(part).length / 4);
|
|
8398
|
+
}
|
|
8399
|
+
}
|
|
8400
|
+
visible.push({ ref, tokens, isUser: msg.info.role === "user" });
|
|
8401
|
+
}
|
|
8402
|
+
if (preserveN > 0) {
|
|
8403
|
+
for (const m of visible.slice(-preserveN)) {
|
|
8404
|
+
result.add(m.ref);
|
|
8405
|
+
}
|
|
8406
|
+
}
|
|
8407
|
+
if (preserveTokens > 0) {
|
|
8408
|
+
let tokenAccum = 0;
|
|
8409
|
+
for (let i = visible.length - 1; i >= 0 && tokenAccum < preserveTokens; i--) {
|
|
8410
|
+
result.add(visible[i].ref);
|
|
8411
|
+
tokenAccum += visible[i].tokens;
|
|
8412
|
+
}
|
|
8413
|
+
}
|
|
8414
|
+
if (preserveLastUser) {
|
|
8415
|
+
for (let i = visible.length - 1; i >= 0; i--) {
|
|
8416
|
+
if (visible[i].isUser) {
|
|
8417
|
+
result.add(visible[i].ref);
|
|
8418
|
+
break;
|
|
8419
|
+
}
|
|
8420
|
+
}
|
|
8421
|
+
}
|
|
8422
|
+
return result;
|
|
8423
|
+
}
|
|
8424
|
+
function excludeProtectedRanges(ranges, protectedRefs) {
|
|
8425
|
+
if (protectedRefs.size === 0) return ranges;
|
|
8426
|
+
return ranges.filter(
|
|
8427
|
+
(r) => !protectedRefs.has(r.startRef) && !protectedRefs.has(r.endRef)
|
|
8428
|
+
);
|
|
8429
|
+
}
|
|
8242
8430
|
|
|
8243
8431
|
// lib/messages/inject/inject.ts
|
|
8244
8432
|
var ACP_SUFFIX_SEED = "acp-dynamic-guidance";
|
|
@@ -8278,6 +8466,8 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
8278
8466
|
state.nudges.iterationNudgeAnchors.clear();
|
|
8279
8467
|
state.nudges.lastNudgeShownTokens = void 0;
|
|
8280
8468
|
state.nudges.lastToolOutputNudgeTokens = void 0;
|
|
8469
|
+
state.nudges.lastTier2NudgeTokens = void 0;
|
|
8470
|
+
state.nudges.lastTier3NudgeTokens = void 0;
|
|
8281
8471
|
if (wasNudgeTriggered && !state.nudges.compressBaselineSet) {
|
|
8282
8472
|
const baseline = state.nudges.lastPerMessageNudgeTokens;
|
|
8283
8473
|
const postCompress = currentTokens;
|
|
@@ -8416,8 +8606,10 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
8416
8606
|
config.compress.protectedTools,
|
|
8417
8607
|
config.protectedFilePatterns
|
|
8418
8608
|
);
|
|
8609
|
+
const protectedRefs = computeProtectedRefs(messages, state, config.compress);
|
|
8610
|
+
const unprotectedCompressible = excludeProtectedRanges(contextRanges.compressible, protectedRefs);
|
|
8419
8611
|
const recommendedRanges = filterRecommendedRanges(
|
|
8420
|
-
|
|
8612
|
+
unprotectedCompressible,
|
|
8421
8613
|
contextRanges.protected,
|
|
8422
8614
|
{ modelContextLimit, growthRatio: 0.05, logger }
|
|
8423
8615
|
);
|
|
@@ -8444,11 +8636,92 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
8444
8636
|
const filterSuppressed = contextRanges.compressible.length > 0 && !hasRecommendations;
|
|
8445
8637
|
const allProtected = contextRanges.compressible.length === 0 && contextRanges.protected.length > 0;
|
|
8446
8638
|
const nothingToCompress = filterSuppressed || allProtected;
|
|
8447
|
-
|
|
8639
|
+
let shouldInject = nudgeAllowed && (!nothingToCompress || emergencyOverride);
|
|
8448
8640
|
if (nudgeAllowed && nothingToCompress && !emergencyOverride && currentTokens !== void 0) {
|
|
8449
8641
|
state.nudges.lastPerMessageNudgeTokens = currentTokens;
|
|
8450
8642
|
state.nudges.lastNudgeShownTokens = void 0;
|
|
8451
8643
|
}
|
|
8644
|
+
if (suffixMessage && !shouldInject) {
|
|
8645
|
+
const tierUsage = getTierTokenUsage(state);
|
|
8646
|
+
const tierChecks = [
|
|
8647
|
+
{ triggerTier: 2, targetTier: 1, tokens: tierUsage.tier1Tokens, lastNudge: state.nudges.lastTier2NudgeTokens },
|
|
8648
|
+
{ triggerTier: 3, targetTier: 2, tokens: tierUsage.tier2Tokens, lastNudge: state.nudges.lastTier3NudgeTokens }
|
|
8649
|
+
];
|
|
8650
|
+
for (const tc of tierChecks) {
|
|
8651
|
+
if (tc.tokens < nudgeGrowthTokens) continue;
|
|
8652
|
+
const cadenceMet = tc.lastNudge === void 0 || currentTokens !== void 0 && currentTokens - tc.lastNudge >= growthFloor;
|
|
8653
|
+
if (!cadenceMet) continue;
|
|
8654
|
+
let candidates = [...state.prune.messages.activeBlockIds].map((id) => state.prune.messages.blocksById.get(id)).filter((b) => b !== void 0 && b.active && (b.tier ?? 1) === tc.targetTier).sort((a, b) => a.blockId - b.blockId);
|
|
8655
|
+
if (candidates.length >= 2) {
|
|
8656
|
+
const firstId = candidates[0].blockId;
|
|
8657
|
+
const lastId = candidates[candidates.length - 1].blockId;
|
|
8658
|
+
const nonTargetInIdRange = new Set(
|
|
8659
|
+
[...state.prune.messages.activeBlockIds].map((id) => state.prune.messages.blocksById.get(id)).filter(
|
|
8660
|
+
(b) => b !== void 0 && b.active && (b.tier ?? 1) !== tc.targetTier && b.blockId > firstId && b.blockId < lastId
|
|
8661
|
+
).map((b) => b.blockId)
|
|
8662
|
+
);
|
|
8663
|
+
if (nonTargetInIdRange.size > 0) {
|
|
8664
|
+
let bestStart = 0;
|
|
8665
|
+
let bestLen = 1;
|
|
8666
|
+
let curStart = 0;
|
|
8667
|
+
for (let i = 1; i < candidates.length; i++) {
|
|
8668
|
+
const prevId = candidates[i - 1].blockId;
|
|
8669
|
+
const currId = candidates[i].blockId;
|
|
8670
|
+
let hasGap = false;
|
|
8671
|
+
for (const nid of nonTargetInIdRange) {
|
|
8672
|
+
if (nid > prevId && nid < currId) {
|
|
8673
|
+
hasGap = true;
|
|
8674
|
+
break;
|
|
8675
|
+
}
|
|
8676
|
+
}
|
|
8677
|
+
if (hasGap) {
|
|
8678
|
+
const curLen = i - curStart;
|
|
8679
|
+
if (curLen > bestLen) {
|
|
8680
|
+
bestLen = curLen;
|
|
8681
|
+
bestStart = curStart;
|
|
8682
|
+
}
|
|
8683
|
+
curStart = i;
|
|
8684
|
+
}
|
|
8685
|
+
}
|
|
8686
|
+
const finalLen = candidates.length - curStart;
|
|
8687
|
+
if (finalLen > bestLen) {
|
|
8688
|
+
bestLen = finalLen;
|
|
8689
|
+
bestStart = curStart;
|
|
8690
|
+
}
|
|
8691
|
+
candidates = candidates.slice(bestStart, bestStart + bestLen);
|
|
8692
|
+
}
|
|
8693
|
+
}
|
|
8694
|
+
if (candidates.length < 2) continue;
|
|
8695
|
+
const rules = tc.triggerTier === 2 ? TIER2_DISTILL_RULES : TIER3_CONDENSE_RULES;
|
|
8696
|
+
const candidateTokens = candidates.reduce((s, b) => s + b.summaryTokens, 0);
|
|
8697
|
+
const firstBlock = candidates[0];
|
|
8698
|
+
const lastBlock = candidates[candidates.length - 1];
|
|
8699
|
+
const fmt = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
|
|
8700
|
+
const sourceTier = tc.triggerTier === 2 ? "Tier 1" : "Tier 2";
|
|
8701
|
+
const action = tc.triggerTier === 2 ? "Distill" : "Condense";
|
|
8702
|
+
const blockList = candidates.slice(0, 10).map((b) => `b${b.blockId} (age=${b.survivedCount}, ${fmt(b.summaryTokens)}tok): "${b.topic}"`).join("\n");
|
|
8703
|
+
const extraCount = candidates.length > 10 ? `
|
|
8704
|
+
...and ${candidates.length - 10} more` : "";
|
|
8705
|
+
const tierText = `
|
|
8706
|
+
|
|
8707
|
+
[Tier ${tc.triggerTier} Trigger] ${sourceTier} summaries accumulated (${fmt(candidateTokens)} tokens across ${candidates.length} blocks). ${action} them to free context.
|
|
8708
|
+
|
|
8709
|
+
Target blocks (oldest first):
|
|
8710
|
+
${blockList}${extraCount}
|
|
8711
|
+
|
|
8712
|
+
Compress range: \`content: [{ startId: "b${firstBlock.blockId}", endId: "b${lastBlock.blockId}", summary: "..." }]\`
|
|
8713
|
+
|
|
8714
|
+
${rules}`;
|
|
8715
|
+
appendToLastTextPart(suffixMessage, tierText);
|
|
8716
|
+
shouldInject = true;
|
|
8717
|
+
if (tc.triggerTier === 2) {
|
|
8718
|
+
state.nudges.lastTier2NudgeTokens = currentTokens;
|
|
8719
|
+
} else {
|
|
8720
|
+
state.nudges.lastTier3NudgeTokens = currentTokens;
|
|
8721
|
+
}
|
|
8722
|
+
break;
|
|
8723
|
+
}
|
|
8724
|
+
}
|
|
8452
8725
|
state.nudges.shouldInjectThisTurn = shouldInject;
|
|
8453
8726
|
let tipsText = null;
|
|
8454
8727
|
if (shouldInject) {
|
|
@@ -8608,65 +8881,6 @@ var injectMessageIds = (state, config, messages, compressionPriorities) => {
|
|
|
8608
8881
|
}
|
|
8609
8882
|
};
|
|
8610
8883
|
|
|
8611
|
-
// lib/messages/inject/subagent-results.ts
|
|
8612
|
-
async function fetchSubAgentMessages(client, sessionId) {
|
|
8613
|
-
const response = await client.session.messages({
|
|
8614
|
-
path: { id: sessionId }
|
|
8615
|
-
});
|
|
8616
|
-
return filterMessages(response?.data || response);
|
|
8617
|
-
}
|
|
8618
|
-
var injectExtendedSubAgentResults = async (client, state, logger, messages, allowSubAgents) => {
|
|
8619
|
-
if (!allowSubAgents) {
|
|
8620
|
-
return;
|
|
8621
|
-
}
|
|
8622
|
-
for (const message of messages) {
|
|
8623
|
-
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
8624
|
-
for (const part of parts) {
|
|
8625
|
-
if (part.type !== "tool" || part.tool !== "task" || !part.callID) {
|
|
8626
|
-
continue;
|
|
8627
|
-
}
|
|
8628
|
-
if (state.prune.tools.has(part.callID)) {
|
|
8629
|
-
continue;
|
|
8630
|
-
}
|
|
8631
|
-
if (part.state?.status !== "completed" || typeof part.state.output !== "string") {
|
|
8632
|
-
continue;
|
|
8633
|
-
}
|
|
8634
|
-
const cachedResult = state.subAgentResultCache.get(part.callID);
|
|
8635
|
-
if (cachedResult !== void 0) {
|
|
8636
|
-
if (cachedResult) {
|
|
8637
|
-
part.state.output = stripHallucinationsFromString(
|
|
8638
|
-
mergeSubagentResult(part.state.output, cachedResult)
|
|
8639
|
-
);
|
|
8640
|
-
}
|
|
8641
|
-
continue;
|
|
8642
|
-
}
|
|
8643
|
-
const subAgentSessionId = getSubAgentId(part);
|
|
8644
|
-
if (!subAgentSessionId) {
|
|
8645
|
-
continue;
|
|
8646
|
-
}
|
|
8647
|
-
let subAgentMessages = [];
|
|
8648
|
-
try {
|
|
8649
|
-
subAgentMessages = await fetchSubAgentMessages(client, subAgentSessionId);
|
|
8650
|
-
} catch (error) {
|
|
8651
|
-
logger.warn("Failed to fetch subagent session for output expansion", {
|
|
8652
|
-
subAgentSessionId,
|
|
8653
|
-
callID: part.callID,
|
|
8654
|
-
error: error instanceof Error ? error.message : String(error)
|
|
8655
|
-
});
|
|
8656
|
-
continue;
|
|
8657
|
-
}
|
|
8658
|
-
const subAgentResultText = buildSubagentResultText(subAgentMessages);
|
|
8659
|
-
if (!subAgentResultText) {
|
|
8660
|
-
continue;
|
|
8661
|
-
}
|
|
8662
|
-
state.subAgentResultCache.set(part.callID, subAgentResultText);
|
|
8663
|
-
part.state.output = stripHallucinationsFromString(
|
|
8664
|
-
mergeSubagentResult(part.state.output, subAgentResultText)
|
|
8665
|
-
);
|
|
8666
|
-
}
|
|
8667
|
-
}
|
|
8668
|
-
};
|
|
8669
|
-
|
|
8670
8884
|
// lib/messages/reasoning-strip.ts
|
|
8671
8885
|
function stripStaleMetadata(messages) {
|
|
8672
8886
|
const lastUserMessage = getLastUserMessage(messages);
|
|
@@ -8712,7 +8926,10 @@ function buildTarget(blocks) {
|
|
|
8712
8926
|
displayId: first.blockId,
|
|
8713
8927
|
runId: first.runId,
|
|
8714
8928
|
topic: grouped ? first.batchTopic || first.topic : first.topic,
|
|
8715
|
-
compressedTokens: ordered.reduce(
|
|
8929
|
+
compressedTokens: ordered.reduce(
|
|
8930
|
+
(total, block) => total + (block.effectiveCompressedTokens ?? block.compressedTokens),
|
|
8931
|
+
0
|
|
8932
|
+
),
|
|
8716
8933
|
durationMs: ordered.reduce((total, block) => Math.max(total, block.durationMs), 0),
|
|
8717
8934
|
grouped,
|
|
8718
8935
|
blocks: ordered
|
|
@@ -8880,17 +9097,25 @@ function snapshotActiveMessages(messagesState) {
|
|
|
8880
9097
|
}
|
|
8881
9098
|
return activeMessages;
|
|
8882
9099
|
}
|
|
8883
|
-
function deactivateCompressionTarget(messagesState, target) {
|
|
9100
|
+
function deactivateCompressionTarget(messagesState, target, options) {
|
|
8884
9101
|
const deactivatedAt = Date.now();
|
|
8885
9102
|
for (const block of target.blocks) {
|
|
8886
9103
|
block.active = false;
|
|
8887
9104
|
block.deactivatedByUser = true;
|
|
8888
9105
|
block.deactivatedAt = deactivatedAt;
|
|
8889
9106
|
block.deactivatedByBlockId = void 0;
|
|
8890
|
-
|
|
8891
|
-
const
|
|
8892
|
-
|
|
8893
|
-
|
|
9107
|
+
if (options?.full) {
|
|
9108
|
+
const visited = /* @__PURE__ */ new Set();
|
|
9109
|
+
const queue = [...block.consumedBlockIds];
|
|
9110
|
+
while (queue.length > 0) {
|
|
9111
|
+
const consumedId = queue.shift();
|
|
9112
|
+
if (visited.has(consumedId)) continue;
|
|
9113
|
+
visited.add(consumedId);
|
|
9114
|
+
const consumedBlock = messagesState.blocksById.get(consumedId);
|
|
9115
|
+
if (consumedBlock) {
|
|
9116
|
+
consumedBlock.deactivatedByUserDeep = true;
|
|
9117
|
+
queue.push(...consumedBlock.consumedBlockIds);
|
|
9118
|
+
}
|
|
8894
9119
|
}
|
|
8895
9120
|
}
|
|
8896
9121
|
}
|
|
@@ -9106,14 +9331,18 @@ ARGUMENTS:
|
|
|
9106
9331
|
IMPORTANT:
|
|
9107
9332
|
- Decompressing inflates context. Check context usage before decompressing.
|
|
9108
9333
|
- Message-mode blocks from the same batch (same runId) are restored together.
|
|
9109
|
-
-
|
|
9334
|
+
- TIER-AWARE: by default, decompressing a multi-tier block restores the PREVIOUS tier's
|
|
9335
|
+
summaries (e.g., decompress T2 \u2192 T1 summaries visible, not raw messages). Use full:true
|
|
9336
|
+
to restore all the way to original messages (can be very expensive for T2/T3 blocks).
|
|
9337
|
+
- After decompression, the restored content will appear in full in your next context window.
|
|
9110
9338
|
- Do NOT call this tool in parallel with compress \u2014 their state mutations may conflict.`;
|
|
9111
9339
|
function buildSchema3() {
|
|
9112
9340
|
return {
|
|
9113
9341
|
blockId: tool4.schema.string().optional().describe('Block reference to decompress (e.g., "b0", "b2"). Mutually exclusive with startId/endId.'),
|
|
9114
9342
|
startId: tool4.schema.string().optional().describe('Range start: message ref (e.g., "m00150") or block ref (e.g., "b2"). Used with endId.'),
|
|
9115
9343
|
endId: tool4.schema.string().optional().describe('Range end: message ref (e.g., "m00200") or block ref (e.g., "b5"). Used with startId.'),
|
|
9116
|
-
toFile: tool4.schema.string().optional().describe("If provided, writes restored content to this file path instead of inflating context. Block stays compressed. Path must be under /tmp or ~/.cache/opencode/. Example: '/tmp/block52.txt'")
|
|
9344
|
+
toFile: tool4.schema.string().optional().describe("If provided, writes restored content to this file path instead of inflating context. Block stays compressed. Path must be under /tmp or ~/.cache/opencode/. Example: '/tmp/block52.txt'"),
|
|
9345
|
+
full: tool4.schema.boolean().optional().describe("If true, restores ALL content down to original messages (multi-level decompress). Default: false \u2014 restores one tier up (e.g., decompressing a T2 block restores T1 summaries, not raw messages). Use full:true only when you need the exact original content and have context budget for it.")
|
|
9117
9346
|
};
|
|
9118
9347
|
}
|
|
9119
9348
|
function extractMessageId(m) {
|
|
@@ -9183,7 +9412,7 @@ function createDecompressTool(factoryCtx) {
|
|
|
9183
9412
|
const activeMessagesBefore = snapshotActiveMessages(messagesState);
|
|
9184
9413
|
const activeBlockIdsBefore = new Set(messagesState.activeBlockIds);
|
|
9185
9414
|
for (const target of targets) {
|
|
9186
|
-
deactivateCompressionTarget(messagesState, target);
|
|
9415
|
+
deactivateCompressionTarget(messagesState, target, { full: args.full === true });
|
|
9187
9416
|
}
|
|
9188
9417
|
syncCompressionBlocks(ctx.state, ctx.logger, rawMessages);
|
|
9189
9418
|
const { restoredMessageCount, restoredTokens } = computeRestoredMessages(
|
|
@@ -9262,13 +9491,54 @@ function formatIdRange(block) {
|
|
|
9262
9491
|
const count = block.effectiveMessageIds?.length || 0;
|
|
9263
9492
|
return count > 0 ? `${count} msg${count !== 1 ? "s" : ""}` : "\u2014";
|
|
9264
9493
|
}
|
|
9494
|
+
function getEffectiveCompressedTokens(block, blocksById, visited = /* @__PURE__ */ new Set()) {
|
|
9495
|
+
if (block.effectiveCompressedTokens !== void 0) {
|
|
9496
|
+
return block.effectiveCompressedTokens;
|
|
9497
|
+
}
|
|
9498
|
+
if (visited.has(block.blockId)) return 0;
|
|
9499
|
+
visited.add(block.blockId);
|
|
9500
|
+
let total = block.compressedTokens || 0;
|
|
9501
|
+
for (const consumedId of block.consumedBlockIds || []) {
|
|
9502
|
+
const consumed = blocksById.get(consumedId);
|
|
9503
|
+
if (consumed) {
|
|
9504
|
+
total += getEffectiveCompressedTokens(consumed, blocksById, visited);
|
|
9505
|
+
}
|
|
9506
|
+
}
|
|
9507
|
+
return total;
|
|
9508
|
+
}
|
|
9509
|
+
function tierLabel(block) {
|
|
9510
|
+
const tier = block.tier ?? 1;
|
|
9511
|
+
return `T${tier}`;
|
|
9512
|
+
}
|
|
9513
|
+
function tierBreakdown(blocks) {
|
|
9514
|
+
const tierTokens = {};
|
|
9515
|
+
for (const b of blocks) {
|
|
9516
|
+
const t = b.tier ?? 1;
|
|
9517
|
+
tierTokens[t] = (tierTokens[t] || 0) + (b.summaryTokens || 0);
|
|
9518
|
+
}
|
|
9519
|
+
const tiers = Object.keys(tierTokens).map(Number);
|
|
9520
|
+
if (tiers.length <= 1 && (!tierTokens[2] || tierTokens[2] === 0) && (!tierTokens[3] || tierTokens[3] === 0)) {
|
|
9521
|
+
return null;
|
|
9522
|
+
}
|
|
9523
|
+
const parts = [];
|
|
9524
|
+
for (const t of [1, 2, 3]) {
|
|
9525
|
+
if (tierTokens[t]) {
|
|
9526
|
+
parts.push(`T${t}: ${formatTokens(tierTokens[t])}`);
|
|
9527
|
+
}
|
|
9528
|
+
}
|
|
9529
|
+
return parts.join(" | ");
|
|
9530
|
+
}
|
|
9265
9531
|
function collectVisibleMessages(rawMessages, ctx) {
|
|
9266
9532
|
const pruneMap = ctx.state.prune.messages.byMessageId;
|
|
9267
9533
|
const byRawId = ctx.state.messageIds.byRawId;
|
|
9268
9534
|
const result = [];
|
|
9269
9535
|
let summaryTokens = 0;
|
|
9536
|
+
const visibleMessageIds = new Set(rawMessages.map((m) => m.info.id));
|
|
9270
9537
|
const activeBlocks = Array.from(ctx.state.prune.messages.activeBlockIds).map((id) => ctx.state.prune.messages.blocksById.get(id)).filter((b) => b !== void 0 && b.active);
|
|
9271
9538
|
for (const block of activeBlocks) {
|
|
9539
|
+
if (block.compressMessageId && !visibleMessageIds.has(block.compressMessageId)) {
|
|
9540
|
+
continue;
|
|
9541
|
+
}
|
|
9272
9542
|
summaryTokens += block.summaryTokens || 0;
|
|
9273
9543
|
}
|
|
9274
9544
|
rawMessages.forEach((msg, idx) => {
|
|
@@ -9329,19 +9599,32 @@ function renderOverview(visibleMessages, summaryTokens, blocks, fetchFailed, raw
|
|
|
9329
9599
|
lines.push("COMPRESSED BLOCKS");
|
|
9330
9600
|
lines.push(" No compressed blocks.");
|
|
9331
9601
|
} else {
|
|
9602
|
+
const blocksById = ctx.state.prune.messages.blocksById;
|
|
9332
9603
|
const totalSummary = blocks.reduce((s, b) => s + (b.summaryTokens || 0), 0);
|
|
9333
|
-
const
|
|
9334
|
-
|
|
9335
|
-
|
|
9604
|
+
const totalEffective = blocks.reduce(
|
|
9605
|
+
(s, b) => s + getEffectiveCompressedTokens(b, blocksById),
|
|
9606
|
+
0
|
|
9336
9607
|
);
|
|
9608
|
+
const header = `COMPRESSED BLOCKS \u2014 ${blocks.length} active (${formatTokens(totalSummary)} summary, ${formatTokens(totalEffective)} original)`;
|
|
9609
|
+
lines.push(header);
|
|
9610
|
+
const breakdown = tierBreakdown(blocks);
|
|
9611
|
+
if (breakdown) {
|
|
9612
|
+
lines.push(` Tier usage: ${breakdown}`);
|
|
9613
|
+
}
|
|
9337
9614
|
lines.push("");
|
|
9338
|
-
const sorted = [...blocks].sort((a, b) =>
|
|
9615
|
+
const sorted = [...blocks].sort((a, b) => {
|
|
9616
|
+
const effA = getEffectiveCompressedTokens(a, blocksById);
|
|
9617
|
+
const effB = getEffectiveCompressedTokens(b, blocksById);
|
|
9618
|
+
return effB - effA || b.createdAt - a.createdAt;
|
|
9619
|
+
});
|
|
9339
9620
|
for (const b of sorted.slice(0, 30)) {
|
|
9340
9621
|
const ageStr = formatAge(b.createdAt);
|
|
9341
9622
|
const range = formatIdRange(b);
|
|
9342
9623
|
const topic = b.topic || "(no topic)";
|
|
9624
|
+
const tier = tierLabel(b);
|
|
9625
|
+
const effTokens = getEffectiveCompressedTokens(b, blocksById);
|
|
9343
9626
|
lines.push(
|
|
9344
|
-
` b${b.blockId} ${formatTokens(
|
|
9627
|
+
` b${b.blockId} (${tier}) ${formatTokens(effTokens)}\u2192${formatTokens(b.summaryTokens)} ${ageStr} ${range} "${topic}"`
|
|
9345
9628
|
);
|
|
9346
9629
|
}
|
|
9347
9630
|
}
|
|
@@ -9445,7 +9728,7 @@ function renderUncompressedDrilldown(visibleMessages, toolFilter, sort, limit) {
|
|
|
9445
9728
|
}
|
|
9446
9729
|
return lines;
|
|
9447
9730
|
}
|
|
9448
|
-
function renderCompressedDrilldown(blocks, sort, limit) {
|
|
9731
|
+
function renderCompressedDrilldown(blocks, sort, limit, blocksById) {
|
|
9449
9732
|
const lines = [];
|
|
9450
9733
|
let sorted = [...blocks];
|
|
9451
9734
|
if (sort === "time") {
|
|
@@ -9453,13 +9736,22 @@ function renderCompressedDrilldown(blocks, sort, limit) {
|
|
|
9453
9736
|
} else if (sort === "age") {
|
|
9454
9737
|
sorted.sort((a, b) => (b.survivedCount || 0) - (a.survivedCount || 0));
|
|
9455
9738
|
} else {
|
|
9456
|
-
sorted.sort(
|
|
9739
|
+
sorted.sort(
|
|
9740
|
+
(a, b) => getEffectiveCompressedTokens(b, blocksById) - getEffectiveCompressedTokens(a, blocksById) || b.createdAt - a.createdAt
|
|
9741
|
+
);
|
|
9457
9742
|
}
|
|
9458
9743
|
const totalSummary = sorted.reduce((s, b) => s + (b.summaryTokens || 0), 0);
|
|
9459
|
-
const
|
|
9744
|
+
const totalEffective = sorted.reduce(
|
|
9745
|
+
(s, b) => s + getEffectiveCompressedTokens(b, blocksById),
|
|
9746
|
+
0
|
|
9747
|
+
);
|
|
9460
9748
|
lines.push(
|
|
9461
|
-
`COMPRESSED \u2014 ${sorted.length} blocks | ${formatTokens(
|
|
9749
|
+
`COMPRESSED \u2014 ${sorted.length} blocks | ${formatTokens(totalEffective)} original \u2192 ${formatTokens(totalSummary)} summary`
|
|
9462
9750
|
);
|
|
9751
|
+
const breakdown = tierBreakdown(sorted);
|
|
9752
|
+
if (breakdown) {
|
|
9753
|
+
lines.push(`Tier usage: ${breakdown}`);
|
|
9754
|
+
}
|
|
9463
9755
|
lines.push(`Sorted by ${sort === "time" ? "time" : sort === "age" ? "age" : "size"}`);
|
|
9464
9756
|
lines.push("");
|
|
9465
9757
|
const shown = sorted.slice(0, limit);
|
|
@@ -9467,10 +9759,12 @@ function renderCompressedDrilldown(blocks, sort, limit) {
|
|
|
9467
9759
|
const survived = b.survivedCount ?? 0;
|
|
9468
9760
|
const gen = b.generation ?? "young";
|
|
9469
9761
|
const effCount = b.effectiveMessageIds?.length ?? 0;
|
|
9470
|
-
const consumed = b.
|
|
9762
|
+
const consumed = b.includedBlockIds && b.includedBlockIds.length > 0 ? ` nested=[${b.includedBlockIds.map((n) => `b${n}`).join(",")}]` : "";
|
|
9471
9763
|
const topic = b.topic || "(no topic)";
|
|
9764
|
+
const tier = tierLabel(b);
|
|
9765
|
+
const effTokens = getEffectiveCompressedTokens(b, blocksById);
|
|
9472
9766
|
lines.push(
|
|
9473
|
-
` b${b.blockId} ${formatTokens(
|
|
9767
|
+
` b${b.blockId} (${tier}) ${formatTokens(effTokens)}\u2192${formatTokens(b.summaryTokens)} ${formatAge(b.createdAt)} ${formatIdRange(b)} age=${survived} ${gen} eff=${effCount}${consumed}`
|
|
9474
9768
|
);
|
|
9475
9769
|
lines.push(` "${topic}"`);
|
|
9476
9770
|
}
|
|
@@ -9511,7 +9805,14 @@ function createAcpStatusTool(factoryCtx) {
|
|
|
9511
9805
|
const allBlocks = activeIds.map((id) => msgState.blocksById.get(id)).filter((b) => b !== void 0 && b.active);
|
|
9512
9806
|
const lines = [];
|
|
9513
9807
|
if (scope === "compressed") {
|
|
9514
|
-
lines.push(
|
|
9808
|
+
lines.push(
|
|
9809
|
+
...renderCompressedDrilldown(
|
|
9810
|
+
allBlocks,
|
|
9811
|
+
sort,
|
|
9812
|
+
limit,
|
|
9813
|
+
msgState.blocksById
|
|
9814
|
+
)
|
|
9815
|
+
);
|
|
9515
9816
|
return lines.join("\n");
|
|
9516
9817
|
}
|
|
9517
9818
|
let visibleMsgs = [];
|
|
@@ -9670,6 +9971,53 @@ IMPORTANT: This was an automatic context pruning. You MUST continue your previou
|
|
|
9670
9971
|
});
|
|
9671
9972
|
}
|
|
9672
9973
|
|
|
9974
|
+
// lib/compress/hide-consumed.ts
|
|
9975
|
+
function hideConsumedCompressCalls(state, messages) {
|
|
9976
|
+
if (state.prune.messages.blocksById.size === 0) {
|
|
9977
|
+
return 0;
|
|
9978
|
+
}
|
|
9979
|
+
const consumedMessageIds = /* @__PURE__ */ new Set();
|
|
9980
|
+
for (const block of state.prune.messages.blocksById.values()) {
|
|
9981
|
+
if (block.active) continue;
|
|
9982
|
+
if (block.deactivatedByUser) continue;
|
|
9983
|
+
if (block.deactivatedByUserDeep) continue;
|
|
9984
|
+
if (block.deactivatedByBlockId === void 0) continue;
|
|
9985
|
+
if (!block.compressMessageId) continue;
|
|
9986
|
+
consumedMessageIds.add(block.compressMessageId);
|
|
9987
|
+
}
|
|
9988
|
+
if (consumedMessageIds.size === 0) {
|
|
9989
|
+
return 0;
|
|
9990
|
+
}
|
|
9991
|
+
const lastUserIdx = messages.findLastIndex(
|
|
9992
|
+
(m) => m.info.role === "user" && !isIgnoredUserMessage(m)
|
|
9993
|
+
);
|
|
9994
|
+
let hidden = 0;
|
|
9995
|
+
for (let i = 0; i < messages.length; i++) {
|
|
9996
|
+
if (lastUserIdx >= 0 && i >= lastUserIdx) break;
|
|
9997
|
+
const msg = messages[i];
|
|
9998
|
+
if (!consumedMessageIds.has(msg.info.id)) continue;
|
|
9999
|
+
const parts = Array.isArray(msg.parts) ? msg.parts : [];
|
|
10000
|
+
let changed = false;
|
|
10001
|
+
const remaining = parts.filter((p) => {
|
|
10002
|
+
if (p.type === "tool" && p.tool === "compress") {
|
|
10003
|
+
hidden++;
|
|
10004
|
+
changed = true;
|
|
10005
|
+
return false;
|
|
10006
|
+
}
|
|
10007
|
+
return true;
|
|
10008
|
+
});
|
|
10009
|
+
if (changed) {
|
|
10010
|
+
if (remaining.length > 0) {
|
|
10011
|
+
messages[i] = { ...msg, parts: remaining };
|
|
10012
|
+
} else {
|
|
10013
|
+
messages.splice(i, 1);
|
|
10014
|
+
i--;
|
|
10015
|
+
}
|
|
10016
|
+
}
|
|
10017
|
+
}
|
|
10018
|
+
return hidden;
|
|
10019
|
+
}
|
|
10020
|
+
|
|
9673
10021
|
// lib/logger.ts
|
|
9674
10022
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
9675
10023
|
import { join as join3 } from "path";
|
|
@@ -9898,7 +10246,7 @@ TOOLS
|
|
|
9898
10246
|
You have five context-management tools:
|
|
9899
10247
|
|
|
9900
10248
|
- \`compress\` \u2014 Replace a contiguous range of older conversation with a single detailed summary you write. Use when content is genuinely consumed (no longer needed for the current task step). Single range: \`compress({ topic: "API exploration", content: [{ startId: "m00150", endId: "m00220", summary: "..." }] })\`. Batch (multiple unrelated ranges, each with its own topic): \`compress({ content: [{ topic: "Auth", startId: "m00150", endId: "m00220", summary: "..." }, { topic: "Deploy", startId: "m00300", endId: "m00350", summary: "..." }] })\`.
|
|
9901
|
-
- \`decompress\` \u2014 Restore a previously compressed block's
|
|
10249
|
+
- \`decompress\` \u2014 Restore a previously compressed block's content. By default restores one tier up (T2\u2192T1 summaries, not raw messages). Use \`full: true\` to restore all the way to original messages. Use \`toFile\` to write to file instead of inflating context. Example: \`decompress({ blockId: "b5" })\` or \`decompress({ blockId: "b5", toFile: "path" })\` or \`decompress({ blockId: "b5", full: true })\`.
|
|
9902
10250
|
- \`search_context\` \u2014 Search compressed block summaries (and optionally visible messages) by keyword. Use BEFORE decompressing to find the right block. Example: \`search_context({ query: "auth token refresh" })\`.
|
|
9903
10251
|
- \`prune\` \u2014 Remove old tool outputs by tool type, keeping only recent calls. Unlike compress (which creates summaries), prune directly strips outputs. Use for disposable outputs like old todowrite states or edit echoes. Example: \`prune({ toolType: "todowrite", keepLatest: 3 })\`.
|
|
9904
10252
|
- \`acp_status\` \u2014 Context status with compressible ranges. No args = overview + ranges. \`scope:"uncompressed"\` for range view; add \`view:"messages"\` for per-message listing with \`tool\`/\`sort\` filters. \`scope:"compressed"\` for block details.
|
|
@@ -9932,6 +10280,16 @@ WHEN NOT TO COMPRESS
|
|
|
9932
10280
|
|
|
9933
10281
|
${HOW_TO_COMPRESS_RULES}
|
|
9934
10282
|
|
|
10283
|
+
MULTI-TIER COMPRESSION
|
|
10284
|
+
|
|
10285
|
+
Summaries accumulate as the session grows. When tier-1 summaries pile up, the system injects a [Tier 2 Trigger] prompting you to DISTILL old blocks into a single tier-2 summary. If tier-2 summaries also accumulate, a [Tier 3 Trigger] asks you to CONDENSE them further.
|
|
10286
|
+
|
|
10287
|
+
- Tier 1 (default): Full-detail compression of conversation ranges. Uses HOW TO COMPRESS rules above.
|
|
10288
|
+
- Tier 2: Distillation of old tier-1 block summaries. Uses TIER 2 DISTILLATION rules (decisions/outcomes only, drop paths/code/process).
|
|
10289
|
+
- Tier 3: Ultra-condensation of tier-2 summaries. Uses TIER 3 CONDENSATION rules (bare facts, 1-3 lines per block).
|
|
10290
|
+
|
|
10291
|
+
To compress blocks: use block IDs as boundaries: \`compress({ content: [{ startId: "b3", endId: "b15", summary: "..." }] })\`. This deactivates the consumed blocks and creates a new higher-tier block. The system prompt at the trigger tells you which rules to follow.
|
|
10292
|
+
|
|
9935
10293
|
PERIODIC CONTEXT STATUS
|
|
9936
10294
|
|
|
9937
10295
|
Periodically, as context grows, the system appends a short status line in a synthetic suffix message. It looks like:
|
|
@@ -11109,8 +11467,21 @@ async function handleRecompressCommand(ctx) {
|
|
|
11109
11467
|
const activeBlockIdsBefore = new Set(messagesState.activeBlockIds);
|
|
11110
11468
|
for (const block of target.blocks) {
|
|
11111
11469
|
block.deactivatedByUser = false;
|
|
11470
|
+
block.deactivatedByUserDeep = false;
|
|
11112
11471
|
block.deactivatedAt = void 0;
|
|
11113
11472
|
block.deactivatedByBlockId = void 0;
|
|
11473
|
+
const queue = [...block.consumedBlockIds];
|
|
11474
|
+
const visited = /* @__PURE__ */ new Set();
|
|
11475
|
+
while (queue.length > 0) {
|
|
11476
|
+
const consumedId = queue.shift();
|
|
11477
|
+
if (visited.has(consumedId)) continue;
|
|
11478
|
+
visited.add(consumedId);
|
|
11479
|
+
const consumedBlock = messagesState.blocksById.get(consumedId);
|
|
11480
|
+
if (consumedBlock) {
|
|
11481
|
+
consumedBlock.deactivatedByUserDeep = false;
|
|
11482
|
+
queue.push(...consumedBlock.consumedBlockIds);
|
|
11483
|
+
}
|
|
11484
|
+
}
|
|
11114
11485
|
}
|
|
11115
11486
|
syncCompressionBlocks(state, logger, messages);
|
|
11116
11487
|
let recompressedMessageCount = 0;
|
|
@@ -11197,8 +11568,9 @@ function formatCompressionTime(ms) {
|
|
|
11197
11568
|
async function handleStatsCommand(ctx) {
|
|
11198
11569
|
const { client, state, logger, sessionId, messages } = ctx;
|
|
11199
11570
|
const sessionTokens = state.stats.totalPruneTokens;
|
|
11571
|
+
const visibleMessageIds = new Set(messages.map((m) => m.info.id));
|
|
11200
11572
|
const sessionSummaryTokens = Array.from(state.prune.messages.blocksById.values()).reduce(
|
|
11201
|
-
(total, block) => block.active ? total + block.summaryTokens : total,
|
|
11573
|
+
(total, block) => block.active && visibleMessageIds.has(block.compressMessageId ?? "") ? total + block.summaryTokens : total,
|
|
11202
11574
|
0
|
|
11203
11575
|
);
|
|
11204
11576
|
const sessionDurationMs = getActiveCompressionTargets(state.prune.messages).reduce(
|
|
@@ -11776,14 +12148,8 @@ function createChatMessageTransformHandler(client, registry3, logger, config, pr
|
|
|
11776
12148
|
}
|
|
11777
12149
|
const prePruneTokens = getCurrentTokenUsage(state, output.messages);
|
|
11778
12150
|
prune(state, logger, config, output.messages);
|
|
12151
|
+
hideConsumedCompressCalls(state, output.messages);
|
|
11779
12152
|
assignMessageRefs(state, output.messages);
|
|
11780
|
-
await injectExtendedSubAgentResults(
|
|
11781
|
-
client,
|
|
11782
|
-
state,
|
|
11783
|
-
logger,
|
|
11784
|
-
output.messages,
|
|
11785
|
-
config.experimental.allowSubAgents
|
|
11786
|
-
);
|
|
11787
12153
|
const compressionPriorities = buildPriorityMap(config, state, output.messages);
|
|
11788
12154
|
prompts.reload();
|
|
11789
12155
|
injectCompressNudges(
|