opencode-acp 1.14.2 → 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 +16 -0
- package/README.zh-CN.md +16 -0
- package/dist/index.js +102 -90
- 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/compress/state.d.ts.map +1 -1
- package/dist/lib/messages/inject/inject.d.ts.map +1 -1
- package/dist/lib/messages/inject/utils.d.ts +2 -2
- 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,22 @@ 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
|
+
|
|
539
|
+
### v1.14.3 — Soften Protected Zone + Reduce Defaults (PR #212)
|
|
540
|
+
|
|
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.
|
|
542
|
+
|
|
543
|
+
**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.
|
|
544
|
+
|
|
545
|
+
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.
|
|
546
|
+
|
|
531
547
|
### v1.14.2 — Split Protected Ranges + Soften Last-User-Message (PR #210)
|
|
532
548
|
|
|
533
549
|
**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,22 @@ 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
|
+
|
|
493
|
+
### v1.14.3 — 软化保护区 + 减小默认值(PR #212)
|
|
494
|
+
|
|
495
|
+
**问题**:`checkProtectedRange` 硬拒绝任何覆盖保护区最近消息的压缩调用(最后 N 条消息 + 最后 N tokens)。模型收到错误后必须换范围重试或使用 `dangerous: true`。此外,默认 `preserveRecentMessages: 20` 和 `preserveRecentTokens: 20000`(约 40 条消息)过于激进 —— 在自主会话中保护了近一半的对话。
|
|
496
|
+
|
|
497
|
+
**修复**:(1) 将 `checkProtectedRange` 硬拒绝转为 `filterProtectedRecentMessages` 软过滤 —— 保护区消息从压缩计划中过滤掉(与 `filterLastUserMessage` 和 `filterProtectedToolMessages` 同模式),非保护区消息正常压缩。除非所有消息都在保护区内,压缩总能成功。(2) 减小 `preserveRecentMessages` 默认值 20 → 5,`preserveRecentTokens` 默认值 20000 → 5000。(3) `dangerous` 参数现在无实际效果(没有硬拒绝了,不需要 bypass;保留在 schema 中向后兼容)。922 项测试通过。
|
|
498
|
+
|
|
499
|
+
文件:`lib/config.ts`、`lib/compress/{protected-content,pipeline,range,message}.ts`、`lib/messages/inject/utils.ts`。测试:`tests/soft-block.test.ts`。无持久化状态 schema 变更。配置默认值改变;已设置显式值的配置不受影响。
|
|
500
|
+
|
|
485
501
|
### v1.14.2 — 拆分保护区范围 + 软化最后用户消息保护(PR #210)
|
|
486
502
|
|
|
487
503
|
**问题**:在自主代理会话中(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: {
|
|
@@ -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;
|
|
@@ -3095,6 +3105,58 @@ function filterLastUserMessage(selection, searchContext, state, compress) {
|
|
|
3095
3105
|
messageTokenById: filteredMessageTokenById
|
|
3096
3106
|
};
|
|
3097
3107
|
}
|
|
3108
|
+
function filterProtectedRecentMessages(selection, searchContext, state, compress) {
|
|
3109
|
+
if (compress.lastSegmentSoftBlock === false) return selection;
|
|
3110
|
+
const preserveN = compress.preserveRecentMessages ?? 5;
|
|
3111
|
+
const preserveTokens = compress.preserveRecentTokens ?? 5e3;
|
|
3112
|
+
if (preserveN <= 0 && preserveTokens <= 0) return selection;
|
|
3113
|
+
const protectedIds = /* @__PURE__ */ new Set();
|
|
3114
|
+
const visible = [];
|
|
3115
|
+
for (const msg of searchContext.rawMessages) {
|
|
3116
|
+
const id = msg?.info?.id;
|
|
3117
|
+
if (!id || typeof id !== "string") continue;
|
|
3118
|
+
if (isSyntheticMessage(msg)) continue;
|
|
3119
|
+
if (isIgnoredUserMessage(msg)) continue;
|
|
3120
|
+
if (state.prune.messages.byMessageId.has(id)) continue;
|
|
3121
|
+
let tokens = 0;
|
|
3122
|
+
for (const part of msg.parts || []) {
|
|
3123
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
3124
|
+
tokens += Math.round(part.text.length / 4);
|
|
3125
|
+
} else if (part.type !== "text" && part.type !== "reasoning") {
|
|
3126
|
+
tokens += Math.round(JSON.stringify(part).length / 4);
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
visible.push({ id, tokens });
|
|
3130
|
+
}
|
|
3131
|
+
if (preserveN > 0) {
|
|
3132
|
+
for (const m of visible.slice(-preserveN)) {
|
|
3133
|
+
protectedIds.add(m.id);
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
if (preserveTokens > 0) {
|
|
3137
|
+
let tokenAccum = 0;
|
|
3138
|
+
for (let i = visible.length - 1; i >= 0 && tokenAccum < preserveTokens; i--) {
|
|
3139
|
+
protectedIds.add(visible[i].id);
|
|
3140
|
+
tokenAccum += visible[i].tokens;
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
if (protectedIds.size === 0) return selection;
|
|
3144
|
+
const hasProtected = selection.messageIds.some((id) => protectedIds.has(id));
|
|
3145
|
+
if (!hasProtected) return selection;
|
|
3146
|
+
const filteredMessageIds = selection.messageIds.filter((id) => !protectedIds.has(id));
|
|
3147
|
+
const filteredMessageTokenById = /* @__PURE__ */ new Map();
|
|
3148
|
+
for (const id of filteredMessageIds) {
|
|
3149
|
+
const tokens = selection.messageTokenById.get(id);
|
|
3150
|
+
if (tokens !== void 0) {
|
|
3151
|
+
filteredMessageTokenById.set(id, tokens);
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
return {
|
|
3155
|
+
...selection,
|
|
3156
|
+
messageIds: filteredMessageIds,
|
|
3157
|
+
messageTokenById: filteredMessageTokenById
|
|
3158
|
+
};
|
|
3159
|
+
}
|
|
3098
3160
|
|
|
3099
3161
|
// lib/compress/state.ts
|
|
3100
3162
|
var DEFAULT_PROMOTION_THRESHOLD = 5;
|
|
@@ -3148,6 +3210,7 @@ function applyCompressionState(state, input, selection, anchorMessageId, blockId
|
|
|
3148
3210
|
const messagesState = state.prune.messages;
|
|
3149
3211
|
const consumed = [...new Set(consumedBlockIds.filter((id) => Number.isInteger(id) && id > 0))];
|
|
3150
3212
|
const createdAt = Date.now();
|
|
3213
|
+
const isBlockBoundary = selection.startReference?.kind === "compressed-block" || selection.endReference?.kind === "compressed-block";
|
|
3151
3214
|
let minConsumedTier;
|
|
3152
3215
|
for (const consumedBlockId of consumed) {
|
|
3153
3216
|
const cb = messagesState.blocksById.get(consumedBlockId);
|
|
@@ -3158,9 +3221,8 @@ function applyCompressionState(state, input, selection, anchorMessageId, blockId
|
|
|
3158
3221
|
}
|
|
3159
3222
|
}
|
|
3160
3223
|
}
|
|
3161
|
-
const
|
|
3162
|
-
const
|
|
3163
|
-
const targetTierForConsumption = effectiveMinTier;
|
|
3224
|
+
const outputTier = isBlockBoundary ? Math.min(3, (minConsumedTier ?? 1) + 1) : 1;
|
|
3225
|
+
const targetTierForConsumption = isBlockBoundary ? minConsumedTier ?? 1 : 1;
|
|
3164
3226
|
const effectiveMessageIds = new Set(selection.messageIds);
|
|
3165
3227
|
const effectiveToolIds = new Set(selection.toolIds);
|
|
3166
3228
|
for (const consumedBlockId of consumed) {
|
|
@@ -5317,6 +5379,10 @@ ${progressBar}`;
|
|
|
5317
5379
|
}
|
|
5318
5380
|
let toastMessage = message;
|
|
5319
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
|
+
}
|
|
5320
5386
|
await client.tui.showToast({
|
|
5321
5387
|
body: {
|
|
5322
5388
|
title: "ACP: Compress Notification",
|
|
@@ -6429,66 +6495,6 @@ function checkPhantomBlock(state, plans) {
|
|
|
6429
6495
|
}
|
|
6430
6496
|
return null;
|
|
6431
6497
|
}
|
|
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
6498
|
|
|
6493
6499
|
// lib/compress/keep-markers.ts
|
|
6494
6500
|
var KEEP_REGEX = /\[\[KEEP:(m\d+)\]\]/g;
|
|
@@ -6667,6 +6673,14 @@ function createCompressMessageTool(factoryCtx) {
|
|
|
6667
6673
|
ctx.state,
|
|
6668
6674
|
ctx.config.compress
|
|
6669
6675
|
)
|
|
6676
|
+
})).map((plan) => ({
|
|
6677
|
+
...plan,
|
|
6678
|
+
selection: filterProtectedRecentMessages(
|
|
6679
|
+
plan.selection,
|
|
6680
|
+
searchContext,
|
|
6681
|
+
ctx.state,
|
|
6682
|
+
ctx.config.compress
|
|
6683
|
+
)
|
|
6670
6684
|
})).filter((plan) => plan.selection.messageIds.length > 0);
|
|
6671
6685
|
if (filteredPlans.length === 0) {
|
|
6672
6686
|
throw new Error(
|
|
@@ -6693,14 +6707,6 @@ function createCompressMessageTool(factoryCtx) {
|
|
|
6693
6707
|
);
|
|
6694
6708
|
}
|
|
6695
6709
|
}
|
|
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
6710
|
const notifications = [];
|
|
6705
6711
|
const preparedPlans = [];
|
|
6706
6712
|
for (const plan of filteredPlans) {
|
|
@@ -6895,6 +6901,14 @@ function createCompressRangeTool(factoryCtx) {
|
|
|
6895
6901
|
ctx.state,
|
|
6896
6902
|
ctx.config.compress
|
|
6897
6903
|
)
|
|
6904
|
+
})).map((plan) => ({
|
|
6905
|
+
...plan,
|
|
6906
|
+
selection: filterProtectedRecentMessages(
|
|
6907
|
+
plan.selection,
|
|
6908
|
+
searchContext,
|
|
6909
|
+
ctx.state,
|
|
6910
|
+
ctx.config.compress
|
|
6911
|
+
)
|
|
6898
6912
|
})).filter((plan) => plan.selection.messageIds.length > 0);
|
|
6899
6913
|
if (filteredPlans.length === 0) {
|
|
6900
6914
|
throw new Error(
|
|
@@ -6921,14 +6935,6 @@ function createCompressRangeTool(factoryCtx) {
|
|
|
6921
6935
|
);
|
|
6922
6936
|
}
|
|
6923
6937
|
}
|
|
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
6938
|
const notifications = [];
|
|
6933
6939
|
const preparedPlans = [];
|
|
6934
6940
|
let totalCompressedMessages = 0;
|
|
@@ -8429,8 +8435,8 @@ ${lines.join("\n")}`;
|
|
|
8429
8435
|
}
|
|
8430
8436
|
function computeProtectedRefs(messages, state, compress) {
|
|
8431
8437
|
if (compress.lastSegmentSoftBlock === false) return /* @__PURE__ */ new Set();
|
|
8432
|
-
const preserveN = compress.preserveRecentMessages ??
|
|
8433
|
-
const preserveTokens = compress.preserveRecentTokens ??
|
|
8438
|
+
const preserveN = compress.preserveRecentMessages ?? 5;
|
|
8439
|
+
const preserveTokens = compress.preserveRecentTokens ?? 5e3;
|
|
8434
8440
|
const result = /* @__PURE__ */ new Set();
|
|
8435
8441
|
const visible = [];
|
|
8436
8442
|
for (const msg of messages) {
|
|
@@ -8500,7 +8506,7 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
8500
8506
|
(m) => m.info.role === "user" && !isIgnoredUserMessage(m)
|
|
8501
8507
|
);
|
|
8502
8508
|
const currentTurnStart = lastUserIdx >= 0 ? lastUserIdx + 1 : 0;
|
|
8503
|
-
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));
|
|
8504
8510
|
if (currentTurnHasCompress) {
|
|
8505
8511
|
const wasNudgeTriggered = state.nudges.lastNudgeShownTokens !== void 0;
|
|
8506
8512
|
state.nudges.contextLimitAnchors.clear();
|
|
@@ -8510,7 +8516,8 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
8510
8516
|
state.nudges.lastToolOutputNudgeTokens = void 0;
|
|
8511
8517
|
state.nudges.lastTier2NudgeTokens = void 0;
|
|
8512
8518
|
state.nudges.lastTier3NudgeTokens = void 0;
|
|
8513
|
-
|
|
8519
|
+
const currentTurnHasSuccessfulCompress = messages.slice(currentTurnStart).some((m) => m.info.role === "assistant" && messageHasCompress(m));
|
|
8520
|
+
if (currentTurnHasSuccessfulCompress && wasNudgeTriggered && !state.nudges.compressBaselineSet) {
|
|
8514
8521
|
const baseline = state.nudges.lastPerMessageNudgeTokens;
|
|
8515
8522
|
const postCompress = currentTokens;
|
|
8516
8523
|
const preCompress = preCompressTokens;
|
|
@@ -8629,9 +8636,6 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
8629
8636
|
const growthSinceBaseline = currentTokens !== void 0 && growthReference !== void 0 ? currentTokens - growthReference : void 0;
|
|
8630
8637
|
const nudgeAllowed = emergencyOverride || decision.shouldNudge && growthSinceBaseline !== void 0 && growthSinceBaseline >= growthFloor;
|
|
8631
8638
|
const effectiveTipsVariant = emergencyOverride ? "maxLimit" : decision.tipsVariant;
|
|
8632
|
-
if (nudgeAllowed) {
|
|
8633
|
-
applyAnchoredNudges(state, config, messages, prompts, compressionPriorities, currentTokens, modelContextLimit, suffixMessage);
|
|
8634
|
-
}
|
|
8635
8639
|
if (state.nudges.lastPerMessageNudgeTokens === void 0 && currentTokens !== void 0) {
|
|
8636
8640
|
state.nudges.lastPerMessageNudgeTokens = currentTokens;
|
|
8637
8641
|
baselineReEstablished = true;
|
|
@@ -8680,10 +8684,18 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
8680
8684
|
const allProtected = contextRanges.compressible.length === 0 && contextRanges.protected.length > 0;
|
|
8681
8685
|
const allInProtectedZone = protectedRefs.size > 0 && unprotectedCompressible.length === 0;
|
|
8682
8686
|
const nothingToCompress = filterSuppressed || allProtected || allInProtectedZone;
|
|
8683
|
-
|
|
8687
|
+
const shouldInjectNudge = nudgeAllowed && (!nothingToCompress || emergencyOverride);
|
|
8688
|
+
let shouldInject = shouldInjectNudge;
|
|
8684
8689
|
if (nudgeAllowed && nothingToCompress && !emergencyOverride) {
|
|
8685
8690
|
state.nudges.lastNudgeShownTokens = void 0;
|
|
8686
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
|
+
}
|
|
8687
8699
|
if (suffixMessage && !shouldInject) {
|
|
8688
8700
|
const tierUsage = getTierTokenUsage(state);
|
|
8689
8701
|
const tierChecks = [
|
|
@@ -10070,7 +10082,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
|
10070
10082
|
import { join as join3 } from "path";
|
|
10071
10083
|
import { existsSync as existsSync3 } from "fs";
|
|
10072
10084
|
import { homedir as homedir3 } from "os";
|
|
10073
|
-
var LOG_VERSION = true ? "1.14.
|
|
10085
|
+
var LOG_VERSION = true ? "1.14.4" : "dev";
|
|
10074
10086
|
var Logger = class {
|
|
10075
10087
|
logDir;
|
|
10076
10088
|
enabled;
|