opencode-acp 1.14.3 → 1.14.4
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 +30 -10
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/state.d.ts.map +1 -1
- package/dist/lib/messages/inject/inject.d.ts.map +1 -1
- package/dist/lib/messages/query.d.ts +12 -0
- package/dist/lib/messages/query.d.ts.map +1 -1
- package/dist/lib/ui/notification.d.ts.map +1 -1
- 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.4 — Tier Detection + E2E Tests + Debug Notification + Nudge Loop Fix (PRs #215, #214, #217, #218)
|
|
532
|
+
|
|
533
|
+
**Problem**: Four issues accumulated since v1.14.3. (1) **Tier misclassification** (PR #215): `applyCompressionState` determined the compression tier from `consumedBlockIds` — any consumed block bumped the tier up. This misclassified T1 compressions that incidentally overlapped existing T1 blocks as T2 (6 of 18 T2-labeled blocks in a real session were misclassified). (2) **E2E test gaps** (PR #214): E2E tests disabled all protection (`preserveRecentMessages: 0`), CI ran only 4/6 scenarios, and `verify.ts` only checked `blockCount` — the protection mechanism fixed 3× in v1.14.x was completely untested in E2E. (3) **Debug notifications invisible** (PR #217): With `debug: true`, compression notifications went to toast only — users couldn't see them in the chat session for debugging. (4) **Nudge injection loop** (PR #218, issue #216): `applyAnchoredNudges` fired before `nothingToCompress` was computed, so the nudge text ("compress now") was injected even when there was nothing to compress — the model saw a nudge with an empty recommendation list, tried random compressions, failed, and looped. Additionally, `messageHasCompress` only recognized `status === "completed"`, so failed compressions never reset the halved nudge threshold, keeping the loop alive.
|
|
534
|
+
|
|
535
|
+
**Fix**: (1) PR #215 — Tier is now determined by `selection.startReference.kind` / `endReference.kind`: message boundaries → T1, block boundaries → T2+ (max consumed tier + 1). When T1 consumes an old T1 block, the old block is still deactivated (superseded), but the new block correctly gets `tier=1`. 7 new regression tests. (2) PR #214 — Added per-scenario config override in `run-e2e.sh`, deepened `verify.ts` with `compressedCount`/`minCompressedCount`/`maxCompressedCount` checks, added 2 new scenarios (07: protection-filtered, 08: nudge-with-protection) using production config (`preserveRecentMessages: 5`, `preserveLastUserMessage: true`), and added scenarios 05/06/07/08 to CI (was 4, now 8). (3) PR #217 — When `config.debug` is on, `sendCompressNotification` now calls `sendIgnoredMessage()` to inject the notification into the chat session (user-visible, model-invisible via `ignored: true`) in addition to the toast. `dropEmptyMessages` (FIX #20) strips ignored-only messages before the next LLM call as defense-in-depth. Debug OFF: toast only (unchanged). (4) PR #218 — Moved `applyAnchoredNudges` after `shouldInject` computation and gated it by `if (shouldInject)`, so no nudge text is injected when there's nothing to compress. Added `messageHasCompressAttempt` (recognizes any compress call regardless of status) and uses it for the early-return / nudge-state clearing, while baseline adjustment still requires `messageHasCompress` (completed only). Failed compressions now clear the halved threshold without falsely adjusting the baseline.
|
|
536
|
+
|
|
537
|
+
Files: `lib/compress/state.ts` (PR #215), `scripts/e2e/{run-e2e.sh,verify.ts}` + `scripts/e2e/scenarios/{07-protection-filtered,08-nudge-with-protection}.json` + `.github/workflows/ci.yml` (PR #214), `lib/ui/notification.ts` (PR #217), `lib/messages/{query,inject/inject}.ts` (PR #218). Tests: `tests/e2e-tier-compression.test.ts`, `tests/query-pure.test.ts`, `tests/inject.test.ts`. 936 tests pass.
|
|
538
|
+
|
|
531
539
|
### v1.14.3 — Soften Protected Zone + Reduce Defaults (PR #212)
|
|
532
540
|
|
|
533
541
|
**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.
|
package/README.zh-CN.md
CHANGED
|
@@ -482,6 +482,14 @@ ACP 在首次启动时自动将配置从 `dcp.jsonc` 迁移到 `acp.jsonc`,将
|
|
|
482
482
|
|
|
483
483
|
## 更新日志
|
|
484
484
|
|
|
485
|
+
### v1.14.4 — Tier 检测 + E2E 测试 + Debug 通知 + Nudge 循环修复(PRs #215, #214, #217, #218)
|
|
486
|
+
|
|
487
|
+
**问题**:自 v1.14.3 以来积累的 4 个问题。(1) **Tier 误分类**(PR #215):`applyCompressionState` 从 `consumedBlockIds` 判断压缩层级——只要消费了任何已有 block 就会提升层级。这导致那些恰好覆盖了已有 T1 block 的 T1 压缩被错误分类为 T2(在一个真实 session 中,18 个标记为 T2 的 block 里有 6 个被误分类)。(2) **E2E 测试缺口**(PR #214):E2E 测试禁用了所有保护(`preserveRecentMessages: 0`),CI 只运行 4/6 个场景,`verify.ts` 只检查 `blockCount`——v1.14.x 修了 3 次的保护机制在 E2E 里完全没有覆盖。(3) **Debug 通知不可见**(PR #217):开启 `debug: true` 时,压缩通知只发到 toast——用户无法在 chat session 里看到用于调试的通知。(4) **Nudge 注入循环**(PR #218,issue #216):`applyAnchoredNudges` 在 `nothingToCompress` 计算之前就触发,导致即使没有可压缩内容,nudge 文本("compress now")也被注入——模型看到 nudge 但推荐列表为空,尝试随机压缩、失败、循环。此外 `messageHasCompress` 只识别 `status === "completed"`,失败的压缩不会重置被减半的 nudge 阈值,循环持续。
|
|
488
|
+
|
|
489
|
+
**修复**:(1) PR #215——层级改由 `selection.startReference.kind` / `endReference.kind` 判断:消息边界 → T1,block 边界 → T2+(消费的最高层级 + 1)。T1 消费旧 T1 block 时,旧 block 仍会被停用(被取代),但新 block 正确得到 `tier=1`。新增 7 个回归测试。(2) PR #214——在 `run-e2e.sh` 中增加按场景的配置覆盖,`verify.ts` 增加 `compressedCount`/`minCompressedCount`/`maxCompressedCount` 检查,新增 2 个场景(07: protection-filtered、08: nudge-with-protection)使用生产配置(`preserveRecentMessages: 5`、`preserveLastUserMessage: true`),CI 场景从 4 个增加到 8 个。(3) PR #217——当 `config.debug` 开启时,`sendCompressNotification` 在 toast 之外额外调用 `sendIgnoredMessage()` 把通知注入到 chat session(用户可见、模型不可见,通过 `ignored: true`)。`dropEmptyMessages`(FIX #20)作为纵深防御在下次 LLM 调用前剥离 ignored-only 消息。Debug 关闭:仅 toast(不变)。(4) PR #218——把 `applyAnchoredNudges` 移到 `shouldInject` 计算之后并用 `if (shouldInject)` 守卫,没有可压缩内容时不注入 nudge 文本。新增 `messageHasCompressAttempt`(识别任何状态的 compress 调用)用于提前返回 / nudge 状态清理,而 baseline 调整仍要求 `messageHasCompress`(仅 completed)。失败的压缩现在会清除被减半的阈值,但不会错误地调整 baseline。
|
|
490
|
+
|
|
491
|
+
文件:`lib/compress/state.ts`(PR #215),`scripts/e2e/{run-e2e.sh,verify.ts}` + `scripts/e2e/scenarios/{07-protection-filtered,08-nudge-with-protection}.json` + `.github/workflows/ci.yml`(PR #214),`lib/ui/notification.ts`(PR #217),`lib/messages/{query,inject/inject}.ts`(PR #218)。测试:`tests/e2e-tier-compression.test.ts`、`tests/query-pure.test.ts`、`tests/inject.test.ts`。936 个测试通过。
|
|
492
|
+
|
|
485
493
|
### v1.14.3 — 软化保护区 + 减小默认值(PR #212)
|
|
486
494
|
|
|
487
495
|
**问题**:`checkProtectedRange` 硬拒绝任何覆盖保护区最近消息的压缩调用(最后 N 条消息 + 最后 N tokens)。模型收到错误后必须换范围重试或使用 `dangerous: true`。此外,默认 `preserveRecentMessages: 20` 和 `preserveRecentTokens: 20000`(约 40 条消息)过于激进 —— 在自主会话中保护了近一半的对话。
|
package/dist/index.js
CHANGED
|
@@ -2085,6 +2085,16 @@ var messageHasCompress = (message) => {
|
|
|
2085
2085
|
(part) => part.type === "tool" && part.tool === "compress" && part.state?.status === "completed"
|
|
2086
2086
|
);
|
|
2087
2087
|
};
|
|
2088
|
+
var messageHasCompressAttempt = (message) => {
|
|
2089
|
+
if (!isMessageWithInfo(message)) {
|
|
2090
|
+
return false;
|
|
2091
|
+
}
|
|
2092
|
+
if (message.info.role !== "assistant") {
|
|
2093
|
+
return false;
|
|
2094
|
+
}
|
|
2095
|
+
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
2096
|
+
return parts.some((part) => part.type === "tool" && part.tool === "compress");
|
|
2097
|
+
};
|
|
2088
2098
|
var isIgnoredUserMessage = (message) => {
|
|
2089
2099
|
if (!isMessageWithInfo(message)) {
|
|
2090
2100
|
return false;
|
|
@@ -3200,6 +3210,7 @@ function applyCompressionState(state, input, selection, anchorMessageId, blockId
|
|
|
3200
3210
|
const messagesState = state.prune.messages;
|
|
3201
3211
|
const consumed = [...new Set(consumedBlockIds.filter((id) => Number.isInteger(id) && id > 0))];
|
|
3202
3212
|
const createdAt = Date.now();
|
|
3213
|
+
const isBlockBoundary = selection.startReference?.kind === "compressed-block" || selection.endReference?.kind === "compressed-block";
|
|
3203
3214
|
let minConsumedTier;
|
|
3204
3215
|
for (const consumedBlockId of consumed) {
|
|
3205
3216
|
const cb = messagesState.blocksById.get(consumedBlockId);
|
|
@@ -3210,9 +3221,8 @@ function applyCompressionState(state, input, selection, anchorMessageId, blockId
|
|
|
3210
3221
|
}
|
|
3211
3222
|
}
|
|
3212
3223
|
}
|
|
3213
|
-
const
|
|
3214
|
-
const
|
|
3215
|
-
const targetTierForConsumption = effectiveMinTier;
|
|
3224
|
+
const outputTier = isBlockBoundary ? Math.min(3, (minConsumedTier ?? 1) + 1) : 1;
|
|
3225
|
+
const targetTierForConsumption = isBlockBoundary ? minConsumedTier ?? 1 : 1;
|
|
3216
3226
|
const effectiveMessageIds = new Set(selection.messageIds);
|
|
3217
3227
|
const effectiveToolIds = new Set(selection.toolIds);
|
|
3218
3228
|
for (const consumedBlockId of consumed) {
|
|
@@ -5369,6 +5379,10 @@ ${progressBar}`;
|
|
|
5369
5379
|
}
|
|
5370
5380
|
let toastMessage = message;
|
|
5371
5381
|
toastMessage = config.pruneNotification === "minimal" ? toastMessage : truncateToastBody(toastMessage);
|
|
5382
|
+
if (config.debug) {
|
|
5383
|
+
const chatMessage = config.pruneNotification === "minimal" ? message : truncateToastBody(message);
|
|
5384
|
+
await sendIgnoredMessage(client, sessionId, chatMessage, params, logger);
|
|
5385
|
+
}
|
|
5372
5386
|
await client.tui.showToast({
|
|
5373
5387
|
body: {
|
|
5374
5388
|
title: "ACP: Compress Notification",
|
|
@@ -8492,7 +8506,7 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
8492
8506
|
(m) => m.info.role === "user" && !isIgnoredUserMessage(m)
|
|
8493
8507
|
);
|
|
8494
8508
|
const currentTurnStart = lastUserIdx >= 0 ? lastUserIdx + 1 : 0;
|
|
8495
|
-
const currentTurnHasCompress = messages.slice(currentTurnStart).some((m) => m.info.role === "assistant" &&
|
|
8509
|
+
const currentTurnHasCompress = messages.slice(currentTurnStart).some((m) => m.info.role === "assistant" && messageHasCompressAttempt(m));
|
|
8496
8510
|
if (currentTurnHasCompress) {
|
|
8497
8511
|
const wasNudgeTriggered = state.nudges.lastNudgeShownTokens !== void 0;
|
|
8498
8512
|
state.nudges.contextLimitAnchors.clear();
|
|
@@ -8502,7 +8516,8 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
8502
8516
|
state.nudges.lastToolOutputNudgeTokens = void 0;
|
|
8503
8517
|
state.nudges.lastTier2NudgeTokens = void 0;
|
|
8504
8518
|
state.nudges.lastTier3NudgeTokens = void 0;
|
|
8505
|
-
|
|
8519
|
+
const currentTurnHasSuccessfulCompress = messages.slice(currentTurnStart).some((m) => m.info.role === "assistant" && messageHasCompress(m));
|
|
8520
|
+
if (currentTurnHasSuccessfulCompress && wasNudgeTriggered && !state.nudges.compressBaselineSet) {
|
|
8506
8521
|
const baseline = state.nudges.lastPerMessageNudgeTokens;
|
|
8507
8522
|
const postCompress = currentTokens;
|
|
8508
8523
|
const preCompress = preCompressTokens;
|
|
@@ -8621,9 +8636,6 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
8621
8636
|
const growthSinceBaseline = currentTokens !== void 0 && growthReference !== void 0 ? currentTokens - growthReference : void 0;
|
|
8622
8637
|
const nudgeAllowed = emergencyOverride || decision.shouldNudge && growthSinceBaseline !== void 0 && growthSinceBaseline >= growthFloor;
|
|
8623
8638
|
const effectiveTipsVariant = emergencyOverride ? "maxLimit" : decision.tipsVariant;
|
|
8624
|
-
if (nudgeAllowed) {
|
|
8625
|
-
applyAnchoredNudges(state, config, messages, prompts, compressionPriorities, currentTokens, modelContextLimit, suffixMessage);
|
|
8626
|
-
}
|
|
8627
8639
|
if (state.nudges.lastPerMessageNudgeTokens === void 0 && currentTokens !== void 0) {
|
|
8628
8640
|
state.nudges.lastPerMessageNudgeTokens = currentTokens;
|
|
8629
8641
|
baselineReEstablished = true;
|
|
@@ -8672,10 +8684,18 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
8672
8684
|
const allProtected = contextRanges.compressible.length === 0 && contextRanges.protected.length > 0;
|
|
8673
8685
|
const allInProtectedZone = protectedRefs.size > 0 && unprotectedCompressible.length === 0;
|
|
8674
8686
|
const nothingToCompress = filterSuppressed || allProtected || allInProtectedZone;
|
|
8675
|
-
|
|
8687
|
+
const shouldInjectNudge = nudgeAllowed && (!nothingToCompress || emergencyOverride);
|
|
8688
|
+
let shouldInject = shouldInjectNudge;
|
|
8676
8689
|
if (nudgeAllowed && nothingToCompress && !emergencyOverride) {
|
|
8677
8690
|
state.nudges.lastNudgeShownTokens = void 0;
|
|
8678
8691
|
}
|
|
8692
|
+
if (shouldInjectNudge) {
|
|
8693
|
+
applyAnchoredNudges(state, config, messages, prompts, compressionPriorities, currentTokens, modelContextLimit, suffixMessage);
|
|
8694
|
+
}
|
|
8695
|
+
if (state.nudges.lastPerMessageNudgeTokens === void 0 && currentTokens !== void 0) {
|
|
8696
|
+
state.nudges.lastPerMessageNudgeTokens = currentTokens;
|
|
8697
|
+
baselineReEstablished = true;
|
|
8698
|
+
}
|
|
8679
8699
|
if (suffixMessage && !shouldInject) {
|
|
8680
8700
|
const tierUsage = getTierTokenUsage(state);
|
|
8681
8701
|
const tierChecks = [
|
|
@@ -10062,7 +10082,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
|
10062
10082
|
import { join as join3 } from "path";
|
|
10063
10083
|
import { existsSync as existsSync3 } from "fs";
|
|
10064
10084
|
import { homedir as homedir3 } from "os";
|
|
10065
|
-
var LOG_VERSION = true ? "1.14.
|
|
10085
|
+
var LOG_VERSION = true ? "1.14.4" : "dev";
|
|
10066
10086
|
var Logger = class {
|
|
10067
10087
|
logDir;
|
|
10068
10088
|
enabled;
|