opencode-acp 1.12.10-dev.1 → 1.12.10
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 +4 -4
- package/README.zh-CN.md +4 -4
- package/dist/index.js +55 -57
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/range-utils.d.ts.map +1 -1
- package/dist/lib/compress/range.d.ts.map +1 -1
- package/dist/lib/compress/types.d.ts +5 -2
- package/dist/lib/compress/types.d.ts.map +1 -1
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/prompts/compress-range.d.ts +1 -1
- package/dist/lib/prompts/compress-range.d.ts.map +1 -1
- package/dist/lib/prompts/extensions/nudge.d.ts.map +1 -1
- package/dist/lib/prompts/extensions/tool.d.ts +1 -1
- package/dist/lib/prompts/extensions/tool.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/ui/notification.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -422,13 +422,13 @@ For the complete list with root cause analysis, see the [bug tracker](https://gi
|
|
|
422
422
|
|
|
423
423
|
## Changelog
|
|
424
424
|
|
|
425
|
-
### v1.12.10
|
|
425
|
+
### v1.12.10 — Batch Compress + Decompress Range Mode + GC Memory-Loss Fix + Token Classification + Nudge Quality (PRs #73, #155, #156, #157, #158, #159, #161)
|
|
426
426
|
|
|
427
|
-
**Problem**:
|
|
427
|
+
**Problem**: Seven issues across compression UX, token accounting, GC safety, and nudge quality. (1) `decompress` required a per-block `acp_status` → decompress-per-block loop to restore multiple compressed blocks. (2) Since v1.12.9 (compress-as-anchor), compress tool `summary` content was misclassified as `toolTokens` instead of `summaryTokens`, inflating tool% and deflating summary% in the context breakdown. (3) The `compress` tool only accepted a single range per call — the model had to issue multiple calls to compress unrelated ranges, wasting turns. (4) The `[PROTECTED: ...]` label listed every tool in a protected message instead of only the triggering tools. (5) When all visible content was protected, the nudge still fired with an empty recommendation list. (6) When a nudge was suppressed, the next-turn check re-evaluated every turn. (7) **The GC system was silently destroying model-written summaries**: any block with `summary.length > 6000` chars was force-truncated to 3000 regardless of context pressure (0% pressure triggered truncation), and blocks with high `survivedCount` were auto-deactivated — causing irrecoverable memory loss across hundreds of sessions.
|
|
428
428
|
|
|
429
|
-
**Fix**: (1) **PR #73** — Added optional `startId`/`endId` to `decompress` schema; range mode batch-restores every active block whose `effectiveMessageIds` overlaps the resolved range
|
|
429
|
+
**Fix**: (1) **PR #73** — Added optional `startId`/`endId` to `decompress` schema; range mode batch-restores every active block whose `effectiveMessageIds` overlaps the resolved range. (2) **PR #155** — In `estimateContextComposition`, when `toolName === "compress"`, extract `summary` text and classify it as `summaryTokens`. (3) **PR #156** — The `compress` tool now accepts a `content` array of `{ topic, startId, endId, summary }` entries, allowing the model to compress multiple unrelated ranges in a single call with per-entry topics. (4) **PR #157** — `buildCompressibleRanges` only adds tools that actually trigger protection. (5) **PR #158** — Added `allProtected` check to suppress nudges when there's genuinely nothing to compress. (6) **PR #159** — When a nudge is suppressed, advance `lastPerMessageNudgeTokens` to `currentTokens` for discrete 5% check intervals. (7) **PR #161** — Removed the GC oversized-block override (`hasOversizedBlocks` bypass that truncated at 0% context) and the age-based deactivation loop entirely. Truncation now only fires at `majorGcThresholdPercent` (default 100%). `gc.maxBlockAge` is now a no-op. Aging warning threshold raised from 50% to 90% context to stop misleading the model.
|
|
430
430
|
|
|
431
|
-
Files: `lib/compress/decompress.ts`, `lib/compress/decompress-logic.ts`, `lib/messages/inject/utils.ts`, `lib/messages/inject/inject.ts`. Tests:
|
|
431
|
+
Files: `lib/hooks.ts`, `lib/config.ts`, `lib/prompts/extensions/nudge.ts`, `lib/compress/decompress.ts`, `lib/compress/decompress-logic.ts`, `lib/messages/inject/utils.ts`, `lib/messages/inject/inject.ts`. Tests: 758 pass.
|
|
432
432
|
|
|
433
433
|
### v1.12.9 — Compress-as-Anchor (PR #153)
|
|
434
434
|
|
package/README.zh-CN.md
CHANGED
|
@@ -395,13 +395,13 @@ ACP 在首次启动时自动将配置从 `dcp.jsonc` 迁移到 `acp.jsonc`,将
|
|
|
395
395
|
|
|
396
396
|
## 更新日志
|
|
397
397
|
|
|
398
|
-
### v1.12.10
|
|
398
|
+
### v1.12.10 — 批量压缩 + Decompress 范围模式 + GC 记忆丢失修复 + Token 分类 + Nudge 质量(PR #73, #155, #156, #157, #158, #159, #161)
|
|
399
399
|
|
|
400
|
-
|
|
400
|
+
**问题**:七个问题,涉及压缩 UX、token 统计、GC 安全和 nudge 质量。(1)`decompress` 需要先 `acp_status` 再逐块 decompress 的循环才能恢复多个压缩块。(2)自 v1.12.9 起,compress 工具的 `summary` 内容被错误分类为 `toolTokens` 而非 `summaryTokens`,导致上下文分布中 tool% 虚高、summary% 虚低。(3)`compress` 工具每次调用只能压缩一个范围 —— 模型需要多次调用才能压缩不相关的范围,浪费轮次。(4)`[PROTECTED: ...]` 标签列出受保护消息中的所有工具而非仅触发保护的工具。(5)当所有可见内容都是受保护内容时,nudge 仍然以空推荐列表注入。(6)当 nudge 被抑制时,下一轮检查每轮都重新评估。(7)**GC 系统在静默销毁模型编写的 summary**:任何 `summary.length > 6000` 字符的块在零上下文压力下被强制截断到 3000,且 `survivedCount` 过高的块被自动 deactivate —— 导致数百个会话的不可恢复记忆丢失。
|
|
401
401
|
|
|
402
|
-
**修复**:(1)**PR #73** —— 为 `decompress` schema 新增可选 `startId`/`endId`;范围模式批量恢复所有 `effectiveMessageIds`
|
|
402
|
+
**修复**:(1)**PR #73** —— 为 `decompress` schema 新增可选 `startId`/`endId`;范围模式批量恢复所有 `effectiveMessageIds` 与解析范围重叠的活跃块。(2)**PR #155** —— 在 `estimateContextComposition` 中,当 `toolName === "compress"` 时,提取 `summary` 文本并分类为 `summaryTokens`。(3)**PR #156** —— `compress` 工具现在接受 `content` 数组(`{ topic, startId, endId, summary }`),允许模型在单次调用中压缩多个不相关范围,每个范围有独立 topic。(4)**PR #157** —— `buildCompressibleRanges` 只添加实际触发保护的工具。(5)**PR #158** —— 新增 `allProtected` 检查,当确实没有可压缩内容时抑制 nudge。(6)**PR #159** —— 当 nudge 被抑制时,将 `lastPerMessageNudgeTokens` 前进到 `currentTokens`,创建离散 5% 检查间隔。(7)**PR #161** —— 删除 GC oversized-block 旁路(`hasOversizedBlocks`,在 0% 上下文压力下截断)和 age-based 自动 deactivate 循环。截断现在只在 `majorGcThresholdPercent`(默认 100%)时触发。`gc.maxBlockAge` 变为 no-op。aging warning 门槛从 50% 提高到 90%,不再误导模型。
|
|
403
403
|
|
|
404
|
-
文件:`lib/compress/decompress.ts`、`lib/compress/decompress-logic.ts`、`lib/messages/inject/utils.ts`、`lib/messages/inject/inject.ts`。测试:
|
|
404
|
+
文件:`lib/hooks.ts`、`lib/config.ts`、`lib/prompts/extensions/nudge.ts`、`lib/compress/decompress.ts`、`lib/compress/decompress-logic.ts`、`lib/messages/inject/utils.ts`、`lib/messages/inject/inject.ts`。测试:758 通过。
|
|
405
405
|
|
|
406
406
|
### v1.12.9 — Compress-as-Anchor(PR #153)
|
|
407
407
|
|
package/dist/index.js
CHANGED
|
@@ -1609,7 +1609,8 @@ var defaultConfig = {
|
|
|
1609
1609
|
gc: {
|
|
1610
1610
|
algorithm: "truncate",
|
|
1611
1611
|
promotionThreshold: 5,
|
|
1612
|
-
maxBlockAge:
|
|
1612
|
+
maxBlockAge: Number.MAX_SAFE_INTEGER,
|
|
1613
|
+
// no-op: age-based deactivation removed (memory-loss fix)
|
|
1613
1614
|
maxOldGenSummaryLength: 3e3,
|
|
1614
1615
|
majorGcThresholdPercent: "100%",
|
|
1615
1616
|
batchCleanup: {
|
|
@@ -2131,20 +2132,27 @@ function countMessageCharacters(msg) {
|
|
|
2131
2132
|
|
|
2132
2133
|
// lib/prompts/extensions/tool.ts
|
|
2133
2134
|
var RANGE_FORMAT_EXTENSION = `
|
|
2135
|
+
|
|
2134
2136
|
THE FORMAT OF COMPRESS
|
|
2135
2137
|
|
|
2136
2138
|
\`\`\`
|
|
2137
2139
|
{
|
|
2138
|
-
topic
|
|
2140
|
+
topic?: string, // OPTIONAL fallback topic for entries without their own.
|
|
2141
|
+
// Omit when every content entry specifies its own topic.
|
|
2139
2142
|
content: [ // One or more ranges to compress
|
|
2140
2143
|
{
|
|
2144
|
+
topic?: string, // OPTIONAL per-entry topic for this range.
|
|
2145
|
+
// Falls back to top-level topic.
|
|
2146
|
+
// Give each entry its own topic when compressing
|
|
2147
|
+
// unrelated ranges in one call.
|
|
2141
2148
|
startId: string, // Boundary ID at range start: mNNNNN or bN
|
|
2142
2149
|
endId: string, // Boundary ID at range end: mNNNNN or bN
|
|
2143
2150
|
summary: string // Complete technical summary replacing all content in range
|
|
2144
2151
|
}
|
|
2145
2152
|
]
|
|
2146
2153
|
}
|
|
2147
|
-
|
|
2154
|
+
\`\`\`
|
|
2155
|
+
Each entry needs a topic \u2014 either its own or the top-level fallback.`;
|
|
2148
2156
|
var MESSAGE_FORMAT_EXTENSION = `
|
|
2149
2157
|
THE FORMAT OF COMPRESS
|
|
2150
2158
|
|
|
@@ -3969,9 +3977,7 @@ function applyPendingCompressionDurations(state) {
|
|
|
3969
3977
|
// lib/compress/range-utils.ts
|
|
3970
3978
|
var BLOCK_PLACEHOLDER_REGEX = /\(b(\d+)\)|\{block_(\d+)\}/gi;
|
|
3971
3979
|
function validateArgs2(args) {
|
|
3972
|
-
|
|
3973
|
-
throw new Error("topic is required and must be a non-empty string");
|
|
3974
|
-
}
|
|
3980
|
+
const hasTopLevelTopic = typeof args.topic === "string" && args.topic.trim().length > 0;
|
|
3975
3981
|
if (!Array.isArray(args.content) || args.content.length === 0) {
|
|
3976
3982
|
throw new Error("content is required and must be a non-empty array");
|
|
3977
3983
|
}
|
|
@@ -3987,11 +3993,18 @@ function validateArgs2(args) {
|
|
|
3987
3993
|
if (typeof entry?.summary !== "string" || entry.summary.trim().length === 0) {
|
|
3988
3994
|
throw new Error(`${prefix}.summary is required and must be a non-empty string`);
|
|
3989
3995
|
}
|
|
3996
|
+
const hasEntryTopic = typeof entry?.topic === "string" && entry.topic.trim().length > 0;
|
|
3997
|
+
if (!hasEntryTopic && !hasTopLevelTopic) {
|
|
3998
|
+
throw new Error(
|
|
3999
|
+
`${prefix} needs a topic \u2014 provide ${prefix}.topic or the top-level topic`
|
|
4000
|
+
);
|
|
4001
|
+
}
|
|
3990
4002
|
}
|
|
3991
4003
|
}
|
|
3992
4004
|
function resolveRanges(args, searchContext, state) {
|
|
3993
4005
|
return args.content.map((entry, index) => {
|
|
3994
4006
|
const normalizedEntry = {
|
|
4007
|
+
topic: typeof entry.topic === "string" && entry.topic.trim().length > 0 ? entry.topic.trim() : void 0,
|
|
3995
4008
|
startId: entry.startId.trim(),
|
|
3996
4009
|
endId: entry.endId.trim(),
|
|
3997
4010
|
summary: entry.summary
|
|
@@ -4185,8 +4198,8 @@ function rebuildRangeInvocation(state, input, searchContext, invocation, protect
|
|
|
4185
4198
|
applyCompressionState(
|
|
4186
4199
|
state,
|
|
4187
4200
|
{
|
|
4188
|
-
topic: input.topic,
|
|
4189
|
-
batchTopic: input.topic,
|
|
4201
|
+
topic: plan.entry.topic ?? input.topic ?? "",
|
|
4202
|
+
batchTopic: typeof input.topic === "string" ? input.topic : void 0,
|
|
4190
4203
|
startId: plan.entry.startId,
|
|
4191
4204
|
endId: plan.entry.endId,
|
|
4192
4205
|
mode: "range",
|
|
@@ -4253,7 +4266,7 @@ function rebuildMessageInvocation(state, input, searchContext, invocation, gcCon
|
|
|
4253
4266
|
state,
|
|
4254
4267
|
{
|
|
4255
4268
|
topic: entry.topic,
|
|
4256
|
-
batchTopic: input.topic,
|
|
4269
|
+
batchTopic: typeof input.topic === "string" ? input.topic : void 0,
|
|
4257
4270
|
startId: entry.messageId,
|
|
4258
4271
|
endId: entry.messageId,
|
|
4259
4272
|
mode: "message",
|
|
@@ -5076,7 +5089,8 @@ async function sendCompressNotification(client, logger, config, state, sessionId
|
|
|
5076
5089
|
newlyCompressedToolIds.push(toolId);
|
|
5077
5090
|
}
|
|
5078
5091
|
}
|
|
5079
|
-
const
|
|
5092
|
+
const entryBlockTopics = entries.map((e) => state.prune.messages.blocksById.get(e.blockId)?.topic).filter((t) => typeof t === "string" && t.length > 0);
|
|
5093
|
+
const topic = batchTopic ?? (entries.length === 1 ? state.prune.messages.blocksById.get(entries[0]?.blockId ?? -1)?.topic ?? "(unknown topic)" : entryBlockTopics.length > 0 ? entryBlockTopics.join(" \xB7 ") : "(unknown topic)");
|
|
5080
5094
|
const contextTokensAfter = Math.max(
|
|
5081
5095
|
0,
|
|
5082
5096
|
contextTokensBefore - compressedTokens + summaryTokens
|
|
@@ -5578,9 +5592,14 @@ function createCompressMessageTool(ctx) {
|
|
|
5578
5592
|
import { tool as tool3 } from "@opencode-ai/plugin";
|
|
5579
5593
|
function buildSchema2(maxSummaryLengthHard) {
|
|
5580
5594
|
return {
|
|
5581
|
-
topic: tool3.schema.string().
|
|
5595
|
+
topic: tool3.schema.string().optional().describe(
|
|
5596
|
+
"Fallback topic for entries without their own. Omit when each content entry specifies its own topic."
|
|
5597
|
+
),
|
|
5582
5598
|
content: tool3.schema.array(
|
|
5583
5599
|
tool3.schema.object({
|
|
5600
|
+
topic: tool3.schema.string().optional().describe(
|
|
5601
|
+
"Short label (3-5 words) for THIS range, e.g. 'Auth System Exploration'. Omit to use top-level topic. When compressing multiple unrelated ranges, give each its own topic for better quality."
|
|
5602
|
+
),
|
|
5584
5603
|
startId: tool3.schema.string().describe(
|
|
5585
5604
|
"Message or block ID marking the beginning of range (e.g. m00001, b2)"
|
|
5586
5605
|
),
|
|
@@ -5590,7 +5609,7 @@ function buildSchema2(maxSummaryLengthHard) {
|
|
|
5590
5609
|
)
|
|
5591
5610
|
})
|
|
5592
5611
|
).describe(
|
|
5593
|
-
"One or more ranges to compress, each with start/end boundaries and a summary"
|
|
5612
|
+
"One or more ranges to compress, each with start/end boundaries and a summary. When compressing multiple unrelated ranges in one call, give each its own topic."
|
|
5594
5613
|
),
|
|
5595
5614
|
summaryMaxChars: tool3.schema.number().optional().describe(
|
|
5596
5615
|
`Override max summary length (default max: ${maxSummaryLengthHard} chars). Use when content is important and needs more detail \u2014 don't lose critical info just to fit the limit.`
|
|
@@ -5624,7 +5643,7 @@ function createCompressRangeTool(ctx) {
|
|
|
5624
5643
|
const { rawMessages, searchContext } = await prepareSession(
|
|
5625
5644
|
ctx,
|
|
5626
5645
|
toolCtx,
|
|
5627
|
-
`Compress Range: ${input.topic}`
|
|
5646
|
+
`Compress Range: ${input.topic ?? "(batch)"}`
|
|
5628
5647
|
);
|
|
5629
5648
|
const resolvedPlans = resolveRanges(input, searchContext, ctx.state);
|
|
5630
5649
|
validateNonOverlapping(resolvedPlans);
|
|
@@ -5766,7 +5785,7 @@ function createCompressRangeTool(ctx) {
|
|
|
5766
5785
|
const applied = applyCompressionState(
|
|
5767
5786
|
ctx.state,
|
|
5768
5787
|
{
|
|
5769
|
-
topic: input.topic,
|
|
5788
|
+
topic: preparedPlan.entry.topic ?? input.topic ?? "",
|
|
5770
5789
|
batchTopic: input.topic,
|
|
5771
5790
|
startId: preparedPlan.entry.startId,
|
|
5772
5791
|
endId: preparedPlan.entry.endId,
|
|
@@ -5791,7 +5810,13 @@ function createCompressRangeTool(ctx) {
|
|
|
5791
5810
|
summaryTokens
|
|
5792
5811
|
});
|
|
5793
5812
|
}
|
|
5794
|
-
await finalizeSession(
|
|
5813
|
+
await finalizeSession(
|
|
5814
|
+
ctx,
|
|
5815
|
+
toolCtx,
|
|
5816
|
+
rawMessages,
|
|
5817
|
+
notifications,
|
|
5818
|
+
input.topic
|
|
5819
|
+
);
|
|
5795
5820
|
} catch (error) {
|
|
5796
5821
|
restoreCompressionState(ctx.state, snapshot);
|
|
5797
5822
|
throw error;
|
|
@@ -6248,7 +6273,7 @@ function buildCompressedBlockGuidance(state, gcConfig, context) {
|
|
|
6248
6273
|
}
|
|
6249
6274
|
}
|
|
6250
6275
|
const usageRatio = context?.currentTokens && context?.modelContextLimit ? context.currentTokens / context.modelContextLimit : 0;
|
|
6251
|
-
if (gcConfig && usageRatio > 0.
|
|
6276
|
+
if (gcConfig && usageRatio > 0.9) {
|
|
6252
6277
|
const promotionThreshold = gcConfig.promotionThreshold;
|
|
6253
6278
|
const agingBlocks = [];
|
|
6254
6279
|
for (const blockId of activeBlockIds) {
|
|
@@ -6266,10 +6291,10 @@ function buildCompressedBlockGuidance(state, gcConfig, context) {
|
|
|
6266
6291
|
}
|
|
6267
6292
|
if (agingBlocks.length > 0) {
|
|
6268
6293
|
lines.push("");
|
|
6269
|
-
lines.push("\u26A0\uFE0F Block aging warning \u2014 these blocks may be truncated by GC
|
|
6294
|
+
lines.push("\u26A0\uFE0F Block aging warning \u2014 context near limit, these blocks may be truncated by last-resort GC:");
|
|
6270
6295
|
lines.push(...agingBlocks);
|
|
6271
6296
|
lines.push(
|
|
6272
|
-
"
|
|
6297
|
+
"Re-summarize these blocks into concise new ones to preserve key facts. At 100% context, oversized blocks are auto-truncated as a last resort."
|
|
6273
6298
|
);
|
|
6274
6299
|
}
|
|
6275
6300
|
}
|
|
@@ -8774,7 +8799,7 @@ TOOLS
|
|
|
8774
8799
|
|
|
8775
8800
|
You have five context-management tools:
|
|
8776
8801
|
|
|
8777
|
-
- \`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).
|
|
8802
|
+
- \`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: "..." }] })\`.
|
|
8778
8803
|
- \`decompress\` \u2014 Restore a previously compressed block's full original content, optionally to a file for large blocks. Use when a summary lacks the exact detail you need. Example: \`decompress({ blockId: "b5" })\` or \`decompress({ blockId: "b5", toFile: "path" })\`.
|
|
8779
8804
|
- \`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" })\`.
|
|
8780
8805
|
- \`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 })\`.
|
|
@@ -8873,6 +8898,16 @@ Rules:
|
|
|
8873
8898
|
BATCHING
|
|
8874
8899
|
When multiple independent ranges are ready and their boundaries do not overlap, include all of them as separate entries in the \`content\` array of a single tool call. Each entry should have its own \`startId\`, \`endId\`, and \`summary\`.
|
|
8875
8900
|
|
|
8901
|
+
When the ranges cover unrelated topics, give each entry its own \`topic\` for better summary quality \u2014 do not force unrelated content under a single shared topic. Omit the top-level \`topic\` when every entry has its own. Use the top-level \`topic\` only as a fallback when entries don't specify one.
|
|
8902
|
+
|
|
8903
|
+
\`\`\`
|
|
8904
|
+
compress({ content: [
|
|
8905
|
+
{ topic: "Auth System Exploration", startId: "m00010", endId: "m00050", summary: "..." },
|
|
8906
|
+
{ topic: "Bug Hunt", startId: "m00060", endId: "m00080", summary: "..." },
|
|
8907
|
+
{ topic: "Deployment", startId: "m00090", endId: "m00110", summary: "..." },
|
|
8908
|
+
]})
|
|
8909
|
+
\`\`\`
|
|
8910
|
+
|
|
8876
8911
|
KEEP AND REF MARKERS
|
|
8877
8912
|
When writing a summary, you may embed markers that reference specific messages in the compressed range. The system resolves them automatically:
|
|
8878
8913
|
|
|
@@ -10567,46 +10602,9 @@ function createSystemPromptHandler(state, logger, config, prompts) {
|
|
|
10567
10602
|
};
|
|
10568
10603
|
}
|
|
10569
10604
|
function runMajorGC(state, config, logger, messages) {
|
|
10570
|
-
const maxBlockAge = config.gc.maxBlockAge ?? 15;
|
|
10571
|
-
let agedOutCount = 0;
|
|
10572
|
-
let agedOutTokens = 0;
|
|
10573
|
-
const now = Date.now();
|
|
10574
|
-
for (const [blockId, block] of state.prune.messages.blocksById) {
|
|
10575
|
-
if (!block.active) continue;
|
|
10576
|
-
const age = block.survivedCount ?? 0;
|
|
10577
|
-
if (age > maxBlockAge) {
|
|
10578
|
-
block.active = false;
|
|
10579
|
-
block.deactivatedAt = now;
|
|
10580
|
-
block.deactivatedByBlockId = void 0;
|
|
10581
|
-
state.prune.messages.activeBlockIds.delete(Number(blockId));
|
|
10582
|
-
const anchorMapped = state.prune.messages.activeByAnchorMessageId.get(block.anchorMessageId);
|
|
10583
|
-
if (anchorMapped === Number(blockId)) {
|
|
10584
|
-
state.prune.messages.activeByAnchorMessageId.delete(block.anchorMessageId);
|
|
10585
|
-
}
|
|
10586
|
-
agedOutCount++;
|
|
10587
|
-
agedOutTokens += block.summaryTokens ?? Math.round(block.summary.length / 4);
|
|
10588
|
-
}
|
|
10589
|
-
}
|
|
10590
|
-
if (agedOutCount > 0) {
|
|
10591
|
-
logger.info("Major GC: deactivated aged-out blocks", {
|
|
10592
|
-
agedOutCount,
|
|
10593
|
-
agedOutTokens,
|
|
10594
|
-
maxBlockAge
|
|
10595
|
-
});
|
|
10596
|
-
saveSessionState(state, logger).catch(() => {
|
|
10597
|
-
});
|
|
10598
|
-
}
|
|
10599
10605
|
if (!state.modelContextLimit) return;
|
|
10600
10606
|
const currentTokens = getCurrentTokenUsage(state, messages);
|
|
10601
|
-
|
|
10602
|
-
let hasOversizedBlocks = false;
|
|
10603
|
-
for (const [, block] of state.prune.messages.blocksById) {
|
|
10604
|
-
if (block.active && block.summary.length > oversizedThreshold) {
|
|
10605
|
-
hasOversizedBlocks = true;
|
|
10606
|
-
break;
|
|
10607
|
-
}
|
|
10608
|
-
}
|
|
10609
|
-
if (!shouldRunMajorGC(currentTokens, state.modelContextLimit, config.gc) && !hasOversizedBlocks) return;
|
|
10607
|
+
if (!shouldRunMajorGC(currentTokens, state.modelContextLimit, config.gc)) return;
|
|
10610
10608
|
const oldBlocks = [];
|
|
10611
10609
|
for (const [blockId, block] of state.prune.messages.blocksById) {
|
|
10612
10610
|
if (!block.active) continue;
|