opencode-acp 1.14.2 → 1.14.3
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 +8 -0
- package/README.zh-CN.md +8 -0
- package/dist/index.js +73 -81
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/message.d.ts.map +1 -1
- package/dist/lib/compress/protected-content.d.ts +1 -0
- package/dist/lib/compress/protected-content.d.ts.map +1 -1
- package/dist/lib/compress/range.d.ts.map +1 -1
- package/dist/lib/messages/inject/utils.d.ts +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -528,6 +528,14 @@ For the complete list with root cause analysis, see the [bug tracker](https://gi
|
|
|
528
528
|
|
|
529
529
|
## Changelog
|
|
530
530
|
|
|
531
|
+
### v1.14.3 — Soften Protected Zone + Reduce Defaults (PR #212)
|
|
532
|
+
|
|
533
|
+
**Problem**: `checkProtectedRange` hard-rejected any compress call covering protected recent messages (last N messages + last N tokens). The model got an error and had to retry with a different range or use `dangerous: true`. Additionally, the default `preserveRecentMessages: 20` and `preserveRecentTokens: 20000` (≈40 messages) were too aggressive — protecting nearly half the conversation in autonomous sessions.
|
|
534
|
+
|
|
535
|
+
**Fix**: (1) Converted `checkProtectedRange` hard-reject to `filterProtectedRecentMessages` soft-filter — protected messages are filtered from the compress plan (same pattern as `filterLastUserMessage` and `filterProtectedToolMessages`), non-protected messages compress normally. Compress always succeeds unless ALL messages are protected. (2) Reduced `preserveRecentMessages` default 20 → 5 and `preserveRecentTokens` default 20000 → 5000. (3) `dangerous` parameter is now a no-op (no hard-reject to bypass; stays in schema for backward compat). 922 tests pass.
|
|
536
|
+
|
|
537
|
+
Files: `lib/config.ts`, `lib/compress/{protected-content,pipeline,range,message}.ts`, `lib/messages/inject/utils.ts`. Tests: `tests/soft-block.test.ts`. No persisted-state schema changes. Config defaults changed; existing configs with explicit values are unaffected.
|
|
538
|
+
|
|
531
539
|
### v1.14.2 — Split Protected Ranges + Soften Last-User-Message (PR #210)
|
|
532
540
|
|
|
533
541
|
**Problem**: In autonomous agentic sessions (1 user message + many assistant/tool messages), `buildCompressibleRanges` created one giant compressible group because grouping only breaks on user messages — and tool results are `assistant` role in OpenCode. The giant group's endRef fell in the protected zone → `excludeProtectedRanges` removed the entire range → zero recommendations → nudge suppressed → model could never compress. Additionally, `preserveLastUserMessage` hard-rejected any compress call covering the last user message, blocking deliberate compressions of surrounding tool output.
|
package/README.zh-CN.md
CHANGED
|
@@ -482,6 +482,14 @@ ACP 在首次启动时自动将配置从 `dcp.jsonc` 迁移到 `acp.jsonc`,将
|
|
|
482
482
|
|
|
483
483
|
## 更新日志
|
|
484
484
|
|
|
485
|
+
### v1.14.3 — 软化保护区 + 减小默认值(PR #212)
|
|
486
|
+
|
|
487
|
+
**问题**:`checkProtectedRange` 硬拒绝任何覆盖保护区最近消息的压缩调用(最后 N 条消息 + 最后 N tokens)。模型收到错误后必须换范围重试或使用 `dangerous: true`。此外,默认 `preserveRecentMessages: 20` 和 `preserveRecentTokens: 20000`(约 40 条消息)过于激进 —— 在自主会话中保护了近一半的对话。
|
|
488
|
+
|
|
489
|
+
**修复**:(1) 将 `checkProtectedRange` 硬拒绝转为 `filterProtectedRecentMessages` 软过滤 —— 保护区消息从压缩计划中过滤掉(与 `filterLastUserMessage` 和 `filterProtectedToolMessages` 同模式),非保护区消息正常压缩。除非所有消息都在保护区内,压缩总能成功。(2) 减小 `preserveRecentMessages` 默认值 20 → 5,`preserveRecentTokens` 默认值 20000 → 5000。(3) `dangerous` 参数现在无实际效果(没有硬拒绝了,不需要 bypass;保留在 schema 中向后兼容)。922 项测试通过。
|
|
490
|
+
|
|
491
|
+
文件:`lib/config.ts`、`lib/compress/{protected-content,pipeline,range,message}.ts`、`lib/messages/inject/utils.ts`。测试:`tests/soft-block.test.ts`。无持久化状态 schema 变更。配置默认值改变;已设置显式值的配置不受影响。
|
|
492
|
+
|
|
485
493
|
### v1.14.2 — 拆分保护区范围 + 软化最后用户消息保护(PR #210)
|
|
486
494
|
|
|
487
495
|
**问题**:在自主代理会话中(1 条用户消息 + 多条 assistant/tool 消息),`buildCompressibleRanges` 创建了一个巨型可压缩组,因为分组只在用户消息处断开 —— 而 OpenCode 中 tool 结果是 `assistant` 角色。巨型组的 endRef 落在保护区内 → `excludeProtectedRanges` 移除整个范围 → 零推荐 → nudge 被抑制 → 模型永远无法压缩。此外,`preserveLastUserMessage` 硬拒绝任何覆盖最后用户消息的压缩调用,阻塞了对周围 tool 输出的有意压缩。
|
package/dist/index.js
CHANGED
|
@@ -1648,8 +1648,8 @@ var defaultConfig = {
|
|
|
1648
1648
|
maxVisibleSegments: 50,
|
|
1649
1649
|
keepEmbedMaxChars: 2e3,
|
|
1650
1650
|
lastSegmentSoftBlock: true,
|
|
1651
|
-
preserveRecentMessages:
|
|
1652
|
-
preserveRecentTokens:
|
|
1651
|
+
preserveRecentMessages: 5,
|
|
1652
|
+
preserveRecentTokens: 5e3,
|
|
1653
1653
|
preserveLastUserMessage: true
|
|
1654
1654
|
},
|
|
1655
1655
|
strategies: {
|
|
@@ -3095,6 +3095,58 @@ function filterLastUserMessage(selection, searchContext, state, compress) {
|
|
|
3095
3095
|
messageTokenById: filteredMessageTokenById
|
|
3096
3096
|
};
|
|
3097
3097
|
}
|
|
3098
|
+
function filterProtectedRecentMessages(selection, searchContext, state, compress) {
|
|
3099
|
+
if (compress.lastSegmentSoftBlock === false) return selection;
|
|
3100
|
+
const preserveN = compress.preserveRecentMessages ?? 5;
|
|
3101
|
+
const preserveTokens = compress.preserveRecentTokens ?? 5e3;
|
|
3102
|
+
if (preserveN <= 0 && preserveTokens <= 0) return selection;
|
|
3103
|
+
const protectedIds = /* @__PURE__ */ new Set();
|
|
3104
|
+
const visible = [];
|
|
3105
|
+
for (const msg of searchContext.rawMessages) {
|
|
3106
|
+
const id = msg?.info?.id;
|
|
3107
|
+
if (!id || typeof id !== "string") continue;
|
|
3108
|
+
if (isSyntheticMessage(msg)) continue;
|
|
3109
|
+
if (isIgnoredUserMessage(msg)) continue;
|
|
3110
|
+
if (state.prune.messages.byMessageId.has(id)) continue;
|
|
3111
|
+
let tokens = 0;
|
|
3112
|
+
for (const part of msg.parts || []) {
|
|
3113
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
3114
|
+
tokens += Math.round(part.text.length / 4);
|
|
3115
|
+
} else if (part.type !== "text" && part.type !== "reasoning") {
|
|
3116
|
+
tokens += Math.round(JSON.stringify(part).length / 4);
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
visible.push({ id, tokens });
|
|
3120
|
+
}
|
|
3121
|
+
if (preserveN > 0) {
|
|
3122
|
+
for (const m of visible.slice(-preserveN)) {
|
|
3123
|
+
protectedIds.add(m.id);
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
if (preserveTokens > 0) {
|
|
3127
|
+
let tokenAccum = 0;
|
|
3128
|
+
for (let i = visible.length - 1; i >= 0 && tokenAccum < preserveTokens; i--) {
|
|
3129
|
+
protectedIds.add(visible[i].id);
|
|
3130
|
+
tokenAccum += visible[i].tokens;
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
if (protectedIds.size === 0) return selection;
|
|
3134
|
+
const hasProtected = selection.messageIds.some((id) => protectedIds.has(id));
|
|
3135
|
+
if (!hasProtected) return selection;
|
|
3136
|
+
const filteredMessageIds = selection.messageIds.filter((id) => !protectedIds.has(id));
|
|
3137
|
+
const filteredMessageTokenById = /* @__PURE__ */ new Map();
|
|
3138
|
+
for (const id of filteredMessageIds) {
|
|
3139
|
+
const tokens = selection.messageTokenById.get(id);
|
|
3140
|
+
if (tokens !== void 0) {
|
|
3141
|
+
filteredMessageTokenById.set(id, tokens);
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
return {
|
|
3145
|
+
...selection,
|
|
3146
|
+
messageIds: filteredMessageIds,
|
|
3147
|
+
messageTokenById: filteredMessageTokenById
|
|
3148
|
+
};
|
|
3149
|
+
}
|
|
3098
3150
|
|
|
3099
3151
|
// lib/compress/state.ts
|
|
3100
3152
|
var DEFAULT_PROMOTION_THRESHOLD = 5;
|
|
@@ -6429,66 +6481,6 @@ function checkPhantomBlock(state, plans) {
|
|
|
6429
6481
|
}
|
|
6430
6482
|
return null;
|
|
6431
6483
|
}
|
|
6432
|
-
function computeProtectedRawIds(rawMessages, state, compress) {
|
|
6433
|
-
const preserveN = compress.preserveRecentMessages ?? 20;
|
|
6434
|
-
const preserveTokens = compress.preserveRecentTokens ?? 2e4;
|
|
6435
|
-
const result = /* @__PURE__ */ new Set();
|
|
6436
|
-
const visible = [];
|
|
6437
|
-
for (const msg of rawMessages) {
|
|
6438
|
-
const id = msg?.info?.id;
|
|
6439
|
-
if (!id || typeof id !== "string") continue;
|
|
6440
|
-
if (isSyntheticMessage(msg)) continue;
|
|
6441
|
-
if (isIgnoredUserMessage(msg)) continue;
|
|
6442
|
-
if (state.prune.messages.byMessageId.has(id)) continue;
|
|
6443
|
-
let tokens = 0;
|
|
6444
|
-
for (const part of msg.parts || []) {
|
|
6445
|
-
if (part.type === "text" && typeof part.text === "string") {
|
|
6446
|
-
tokens += Math.round(part.text.length / 4);
|
|
6447
|
-
} else if (part.type !== "text" && part.type !== "reasoning") {
|
|
6448
|
-
tokens += Math.round(JSON.stringify(part).length / 4);
|
|
6449
|
-
}
|
|
6450
|
-
}
|
|
6451
|
-
visible.push({ id, tokens, isUser: msg.info.role === "user" });
|
|
6452
|
-
}
|
|
6453
|
-
if (preserveN > 0) {
|
|
6454
|
-
for (const m of visible.slice(-preserveN)) {
|
|
6455
|
-
result.add(m.id);
|
|
6456
|
-
}
|
|
6457
|
-
}
|
|
6458
|
-
if (preserveTokens > 0) {
|
|
6459
|
-
let tokenAccum = 0;
|
|
6460
|
-
for (let i = visible.length - 1; i >= 0 && tokenAccum < preserveTokens; i--) {
|
|
6461
|
-
result.add(visible[i].id);
|
|
6462
|
-
tokenAccum += visible[i].tokens;
|
|
6463
|
-
}
|
|
6464
|
-
}
|
|
6465
|
-
return result;
|
|
6466
|
-
}
|
|
6467
|
-
function checkProtectedRange(ctx, allPlanMessageIds, rawMessages, dangerous) {
|
|
6468
|
-
if (ctx.config.compress.lastSegmentSoftBlock === false) return null;
|
|
6469
|
-
const protectedIds = computeProtectedRawIds(rawMessages, ctx.state, ctx.config.compress);
|
|
6470
|
-
if (protectedIds.size === 0) return null;
|
|
6471
|
-
const coveredProtected = [];
|
|
6472
|
-
for (const ids of allPlanMessageIds) {
|
|
6473
|
-
for (const id of ids) {
|
|
6474
|
-
if (protectedIds.has(id)) {
|
|
6475
|
-
coveredProtected.push(id);
|
|
6476
|
-
}
|
|
6477
|
-
}
|
|
6478
|
-
}
|
|
6479
|
-
if (coveredProtected.length === 0) return null;
|
|
6480
|
-
if (dangerous) return null;
|
|
6481
|
-
const sample = coveredProtected.slice(0, 3).join(", ");
|
|
6482
|
-
const nMsgs = ctx.config.compress.preserveRecentMessages ?? 20;
|
|
6483
|
-
const nToks = ctx.config.compress.preserveRecentTokens ?? 2e4;
|
|
6484
|
-
return new Error(
|
|
6485
|
-
`This range includes ${coveredProtected.length} protected recent message(s) (${sample}), which are likely still needed for the current task step.
|
|
6486
|
-
|
|
6487
|
-
Protected zone: last ${nMsgs} messages + last ${nToks >= 1e3 ? `${nToks / 1e3}K` : nToks} tokens.
|
|
6488
|
-
If you are certain this content is genuinely consumed and must be compressed, re-issue the call with \`dangerous: true\`.
|
|
6489
|
-
Otherwise, compress older ranges that do not include the tail of the conversation.`
|
|
6490
|
-
);
|
|
6491
|
-
}
|
|
6492
6484
|
|
|
6493
6485
|
// lib/compress/keep-markers.ts
|
|
6494
6486
|
var KEEP_REGEX = /\[\[KEEP:(m\d+)\]\]/g;
|
|
@@ -6667,6 +6659,14 @@ function createCompressMessageTool(factoryCtx) {
|
|
|
6667
6659
|
ctx.state,
|
|
6668
6660
|
ctx.config.compress
|
|
6669
6661
|
)
|
|
6662
|
+
})).map((plan) => ({
|
|
6663
|
+
...plan,
|
|
6664
|
+
selection: filterProtectedRecentMessages(
|
|
6665
|
+
plan.selection,
|
|
6666
|
+
searchContext,
|
|
6667
|
+
ctx.state,
|
|
6668
|
+
ctx.config.compress
|
|
6669
|
+
)
|
|
6670
6670
|
})).filter((plan) => plan.selection.messageIds.length > 0);
|
|
6671
6671
|
if (filteredPlans.length === 0) {
|
|
6672
6672
|
throw new Error(
|
|
@@ -6693,14 +6693,6 @@ function createCompressMessageTool(factoryCtx) {
|
|
|
6693
6693
|
);
|
|
6694
6694
|
}
|
|
6695
6695
|
}
|
|
6696
|
-
const dangerous = args.dangerous === true;
|
|
6697
|
-
const lastSegmentError = checkProtectedRange(
|
|
6698
|
-
ctx,
|
|
6699
|
-
filteredPlans.map((p) => p.selection.messageIds),
|
|
6700
|
-
rawMessages,
|
|
6701
|
-
dangerous
|
|
6702
|
-
);
|
|
6703
|
-
if (lastSegmentError) throw lastSegmentError;
|
|
6704
6696
|
const notifications = [];
|
|
6705
6697
|
const preparedPlans = [];
|
|
6706
6698
|
for (const plan of filteredPlans) {
|
|
@@ -6895,6 +6887,14 @@ function createCompressRangeTool(factoryCtx) {
|
|
|
6895
6887
|
ctx.state,
|
|
6896
6888
|
ctx.config.compress
|
|
6897
6889
|
)
|
|
6890
|
+
})).map((plan) => ({
|
|
6891
|
+
...plan,
|
|
6892
|
+
selection: filterProtectedRecentMessages(
|
|
6893
|
+
plan.selection,
|
|
6894
|
+
searchContext,
|
|
6895
|
+
ctx.state,
|
|
6896
|
+
ctx.config.compress
|
|
6897
|
+
)
|
|
6898
6898
|
})).filter((plan) => plan.selection.messageIds.length > 0);
|
|
6899
6899
|
if (filteredPlans.length === 0) {
|
|
6900
6900
|
throw new Error(
|
|
@@ -6921,14 +6921,6 @@ function createCompressRangeTool(factoryCtx) {
|
|
|
6921
6921
|
);
|
|
6922
6922
|
}
|
|
6923
6923
|
}
|
|
6924
|
-
const dangerous = args.dangerous === true;
|
|
6925
|
-
const lastSegmentError = checkProtectedRange(
|
|
6926
|
-
ctx,
|
|
6927
|
-
filteredPlans.map((p) => p.selection.messageIds),
|
|
6928
|
-
rawMessages,
|
|
6929
|
-
dangerous
|
|
6930
|
-
);
|
|
6931
|
-
if (lastSegmentError) throw lastSegmentError;
|
|
6932
6924
|
const notifications = [];
|
|
6933
6925
|
const preparedPlans = [];
|
|
6934
6926
|
let totalCompressedMessages = 0;
|
|
@@ -8429,8 +8421,8 @@ ${lines.join("\n")}`;
|
|
|
8429
8421
|
}
|
|
8430
8422
|
function computeProtectedRefs(messages, state, compress) {
|
|
8431
8423
|
if (compress.lastSegmentSoftBlock === false) return /* @__PURE__ */ new Set();
|
|
8432
|
-
const preserveN = compress.preserveRecentMessages ??
|
|
8433
|
-
const preserveTokens = compress.preserveRecentTokens ??
|
|
8424
|
+
const preserveN = compress.preserveRecentMessages ?? 5;
|
|
8425
|
+
const preserveTokens = compress.preserveRecentTokens ?? 5e3;
|
|
8434
8426
|
const result = /* @__PURE__ */ new Set();
|
|
8435
8427
|
const visible = [];
|
|
8436
8428
|
for (const msg of messages) {
|
|
@@ -10070,7 +10062,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
|
10070
10062
|
import { join as join3 } from "path";
|
|
10071
10063
|
import { existsSync as existsSync3 } from "fs";
|
|
10072
10064
|
import { homedir as homedir3 } from "os";
|
|
10073
|
-
var LOG_VERSION = true ? "1.14.
|
|
10065
|
+
var LOG_VERSION = true ? "1.14.3" : "dev";
|
|
10074
10066
|
var Logger = class {
|
|
10075
10067
|
logDir;
|
|
10076
10068
|
enabled;
|