opencode-acp 1.10.0 → 1.10.1
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 +157 -65
- package/README.zh-CN.md +117 -60
- package/dist/index.js +11 -5
- package/dist/index.js.map +1 -1
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/messages/inject/inject.d.ts.map +1 -1
- package/dist/lib/state/persistence.d.ts +1 -0
- package/dist/lib/state/persistence.d.ts.map +1 -1
- package/dist/lib/state/state.d.ts.map +1 -1
- package/package.json +1 -1
package/README.zh-CN.md
CHANGED
|
@@ -37,17 +37,17 @@ ACP 将上下文管理的所有权限全部交给模型自己,而不依靠外
|
|
|
37
37
|
|
|
38
38
|
**支持 5 亿级别 token,p95 上下文比例在 30% 左右,平均缓存命中率 85% 以上。**(注意这是平均缓存命中率,不是单会话命中率——后面[对 Prompt 缓存的影响](#对-prompt-缓存的影响)会解释,这实际上比传统压缩算法大幅度节省了 token。)
|
|
39
39
|
|
|
40
|
-
|
|
|
41
|
-
|
|
42
|
-
| **消息总条数**
|
|
43
|
-
| **累计处理 token**
|
|
44
|
-
| **prompt-cache 命中率** | 86.2%
|
|
45
|
-
| **上下文 p50(中位)**
|
|
46
|
-
| **上下文 p75**
|
|
47
|
-
| **上下文 p90**
|
|
48
|
-
| **上下文 p95**
|
|
49
|
-
| **上下文 p99**
|
|
50
|
-
| **峰值**
|
|
40
|
+
| | 会话一 | 会话二 |
|
|
41
|
+
| ----------------------- | -------------- | -------------- |
|
|
42
|
+
| **消息总条数** | 3,024 | 2,028 |
|
|
43
|
+
| **累计处理 token** | 5.82 亿 | 4.63 亿 |
|
|
44
|
+
| **prompt-cache 命中率** | 86.2% | 89.0% |
|
|
45
|
+
| **上下文 p50(中位)** | 1.2 K(<1%) | 1.8 K(<1%) |
|
|
46
|
+
| **上下文 p75** | 2.8 K | 3.5 K |
|
|
47
|
+
| **上下文 p90** | 10.8 万(11%) | 5.8 万(6%) |
|
|
48
|
+
| **上下文 p95** | 25.1 万(25%) | 33.5 万(34%) |
|
|
49
|
+
| **上下文 p99** | 42.5 万(43%) | 44.2 万(44%) |
|
|
50
|
+
| **峰值** | 48.8 万(49%) | 76.9 万(77%) |
|
|
51
51
|
|
|
52
52
|
(上下文百分比均以 1M 窗口计。)
|
|
53
53
|
|
|
@@ -63,9 +63,9 @@ opencode plugin opencode-acp@latest --global
|
|
|
63
63
|
|
|
64
64
|
```json
|
|
65
65
|
{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
"plugin": {
|
|
67
|
+
"opencode-acp": "latest"
|
|
68
|
+
}
|
|
69
69
|
}
|
|
70
70
|
```
|
|
71
71
|
|
|
@@ -126,16 +126,16 @@ stateDiagram-v2
|
|
|
126
126
|
|
|
127
127
|
ACP 提供 `/acp` 斜杠命令(为向后兼容也接受 `/dcp`):
|
|
128
128
|
|
|
129
|
-
| 命令
|
|
130
|
-
|
|
131
|
-
| `/acp`
|
|
132
|
-
| `/acp context`
|
|
133
|
-
| `/acp stats`
|
|
134
|
-
| `/acp sweep [n]`
|
|
135
|
-
| `/acp manual [on\|off]` | 切换手动模式。开启后,AI 不会自动使用上下文管理工具
|
|
136
|
-
| `/acp compress [focus]` | 触发一次 `compress` 工具执行。可选的焦点文本指示要压缩的内容,遵循当前 `compress.mode`
|
|
137
|
-
| `/acp decompress <n>`
|
|
138
|
-
| `/acp recompress <n>`
|
|
129
|
+
| 命令 | 说明 |
|
|
130
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
131
|
+
| `/acp` | 显示可用的 ACP 命令 |
|
|
132
|
+
| `/acp context` | 按类别(system、user、assistant、tools 等)显示 token 用量明细,以及通过剪枝节省的量 |
|
|
133
|
+
| `/acp stats` | 跨所有会话的累计剪枝统计 |
|
|
134
|
+
| `/acp sweep [n]` | 剪除自上次用户消息以来的所有工具。可选数量:`/acp sweep 10` 剪除最近 10 个工具。遵循 `commands.protectedTools` 设置 |
|
|
135
|
+
| `/acp manual [on\|off]` | 切换手动模式。开启后,AI 不会自动使用上下文管理工具 |
|
|
136
|
+
| `/acp compress [focus]` | 触发一次 `compress` 工具执行。可选的焦点文本指示要压缩的内容,遵循当前 `compress.mode` |
|
|
137
|
+
| `/acp decompress <n>` | 按 ID 恢复特定的活动压缩。不带参数运行时显示可用的压缩 ID、token 大小和主题 |
|
|
138
|
+
| `/acp recompress <n>` | 按 ID 重新应用用户解压的压缩。不带参数运行时显示可重新压缩的 ID、token 大小和主题 |
|
|
139
139
|
|
|
140
140
|
---
|
|
141
141
|
|
|
@@ -156,9 +156,9 @@ ACP 使用自己的配置文件,按以下顺序搜索:
|
|
|
156
156
|
>
|
|
157
157
|
> ```jsonc
|
|
158
158
|
> {
|
|
159
|
-
>
|
|
160
|
-
>
|
|
161
|
-
>
|
|
159
|
+
> "compaction": {
|
|
160
|
+
> "auto": false,
|
|
161
|
+
> },
|
|
162
162
|
> }
|
|
163
163
|
> ```
|
|
164
164
|
>
|
|
@@ -323,7 +323,7 @@ ACP 暴露六个可编辑的 prompt:
|
|
|
323
323
|
|
|
324
324
|
`commands` 和 `strategies` 中的 `protectedTools` 数组会添加到此默认列表。
|
|
325
325
|
|
|
326
|
-
对于 `compress` 工具,`compress.protectedTools`
|
|
326
|
+
对于 `compress` 工具,`compress.protectedTools` 确保特定工具的输出被**硬排除**在压缩范围之外(v1.10.0+)。当模型压缩包含受保护工具消息的范围时,该消息完整保留在可见上下文中 — 只有周围的非受保护消息被压缩。默认包含 `task`、`skill`、`todowrite`、`todoread` 和 `decompress`。
|
|
327
327
|
|
|
328
328
|
---
|
|
329
329
|
|
|
@@ -354,36 +354,38 @@ ACP 在首次启动时自动将配置从 `dcp.jsonc` 迁移到 `acp.jsonc`,将
|
|
|
354
354
|
---
|
|
355
355
|
|
|
356
356
|
<details>
|
|
357
|
-
<summary><strong>错误修复(共
|
|
358
|
-
|
|
359
|
-
| #
|
|
360
|
-
|
|
361
|
-
| 1
|
|
362
|
-
| 2
|
|
363
|
-
| 3
|
|
364
|
-
| 4
|
|
365
|
-
| 5
|
|
366
|
-
| 6
|
|
367
|
-
| 7
|
|
368
|
-
| 8
|
|
369
|
-
| 9
|
|
370
|
-
| 10
|
|
371
|
-
| 11
|
|
372
|
-
| 12
|
|
373
|
-
| 13
|
|
374
|
-
| 14
|
|
375
|
-
| 15
|
|
376
|
-
| 16
|
|
377
|
-
| 17
|
|
378
|
-
| 18
|
|
379
|
-
| 19
|
|
380
|
-
| 20
|
|
381
|
-
| 21
|
|
382
|
-
| 22
|
|
383
|
-
| 23--34 | 中
|
|
384
|
-
| 35
|
|
385
|
-
| 36
|
|
386
|
-
| 37
|
|
357
|
+
<summary><strong>错误修复(共 39 项)</strong> — 基于 DCP v3.1.11</summary>
|
|
358
|
+
|
|
359
|
+
| # | 严重程度 | 摘要 |
|
|
360
|
+
| ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
361
|
+
| 1 | 严重 | 状态在重启后未持久化 — messageIds、块停用、保存错误均静默丢失 |
|
|
362
|
+
| 2 | 严重 | resetOnCompaction() 清除所有压缩块 — 撤销所有剪枝工作 |
|
|
363
|
+
| 3 | 严重 | prune 静默丢弃摘要 — 当锚点前无用户消息时导致数据丢失 |
|
|
364
|
+
| 4 | 严重 | getCurrentTokenUsage 返回 0 — 导致 nudge 永远无法触发 |
|
|
365
|
+
| 5 | 高 | loadPruneMessagesState 重复 activeBlockIds + reasoning-strip 未定义保护缺失 |
|
|
366
|
+
| 6 | 高 | 合成摘要消息获得 mNNNN 引用但对边界查找不可见 |
|
|
367
|
+
| 7 | 高 | 状态在重启后未持久化 — messageIds、块停用和保存错误均静默丢失 |
|
|
368
|
+
| 8 | 高 | isMessageCompacted() 与压缩摘要消息处理不一致 |
|
|
369
|
+
| 9 | 高 | 已压缩的块摘要保留过时的 mNNNN 消息 ID 标签 — 模型复制过时 ID |
|
|
370
|
+
| 10 | 高 | 模型使用 nudge/摘要中的过时 mNNNN ID — compress 因 "startId not available" 失败 |
|
|
371
|
+
| 11 | 高 | 主 GC 跳过没有 generation 字段的旧块 — 过大的块永远不会被回收 |
|
|
372
|
+
| 12 | 高 | 基于百分比的阈值基于有效输入上下文而非完整模型上下文窗口计算 |
|
|
373
|
+
| 13 | 高 | 上下文窗口泄漏 — 压缩后的消息在 /compact 后重新出现 |
|
|
374
|
+
| 14 | 高 | 压缩通知将完整块摘要写入数据库 — 每条通知可达 150KB+ |
|
|
375
|
+
| 15 | 高 | npm 自动安装用上游包覆盖分支 |
|
|
376
|
+
| 16 | 高 | compress 输出中的摘要 mNNNN 引用 — 模型复制过时的消息 ID |
|
|
377
|
+
| 17 | 高 | 合成消息不在 messageIdToBlockId 中 — compress 无法找到它们 |
|
|
378
|
+
| 18 | 高 | compress 在压缩完成后阻止模型响应 |
|
|
379
|
+
| 19 | 高 | 动态块引导破坏 API 前缀缓存 |
|
|
380
|
+
| 20 | 高 | GC 从不停用旧块 — 死重无限累积 |
|
|
381
|
+
| 21 | 高 | Logger + tokenizer 每轮延迟 20-50 秒(268 倍减速) |
|
|
382
|
+
| 22 | 高 | compress 在块边界反转时抛出硬错误 — 模型放弃 |
|
|
383
|
+
| 23--34 | 中 | 去重、错误清除、schema 验证、hook 时序等方面的多项修复 |
|
|
384
|
+
| 35 | 高 | 在低上下文使用率(<50%)时显示老化警告 — 触发不必要的 compress,浪费 token |
|
|
385
|
+
| 36 | 高 | 压缩摘要作为独立的 user 消息插入在用户真实发言之前 — 模型把自己先前的 assistant 输出误读为用户输入,导致对话角色混乱 / 自问自答循环 |
|
|
386
|
+
| 37 | 高 | 消息转换管线对 OpenCode 隐藏的 title/summary/compaction agent 请求也运行 — 污染请求并破坏共享会话状态,导致会话标题生成失效 |
|
|
387
|
+
| 38 | 严重 | pruneToolOutputs/pruneToolInputs/pruneToolErrors 原地修改现有消息 — 破坏 LLM 前缀缓存,导致 89% 的新鲜输入 token 浪费在缓存失效的重发上 |
|
|
388
|
+
| 39 | 高 | 受保护工具输出(skill/task/todowrite)在压缩时仅软保护 — 追加到摘要后从上下文中剪枝,丧失语义权威且易被 GC 截断。v1.10.0 用硬排除修复 |
|
|
387
389
|
|
|
388
390
|
完整列表及根因分析,请参见 [Bug Tracker](https://github.com/ranxianglei/opencode-acp/issues)。
|
|
389
391
|
|
|
@@ -393,6 +395,47 @@ ACP 在首次启动时自动将配置从 `dcp.jsonc` 迁移到 `acp.jsonc`,将
|
|
|
393
395
|
|
|
394
396
|
## 更新日志
|
|
395
397
|
|
|
398
|
+
### v1.10.0 — 受保护工具硬排除、压缩提示词重写、后缀与部署修复
|
|
399
|
+
|
|
400
|
+
本次发布合并了 7 个 PR。核心变更是**受保护工具消息硬排除**;其余为同期合入的修复和提示词重写。
|
|
401
|
+
|
|
402
|
+
#### Bug 39 — 受保护工具硬排除(issue #16, PR #75)
|
|
403
|
+
|
|
404
|
+
**问题**:受保护工具消息(`skill`、`task`、`todowrite` 等)在压缩时只有*软保护*。当模型对包含 skill 输出的范围调用 `compress` 时,原始消息从可见上下文中被剪枝,其内容被追加到摘要块中。这导致两个问题:
|
|
405
|
+
|
|
406
|
+
1. **语义丢失**:skill 内容变成历史回顾元数据(`[ACP SYSTEM METADATA — recap...]`),不再是活跃指令。模型将其视为过去的产物,而非当前的指导。
|
|
407
|
+
2. **GC 数据丢失**:当块晋升为 old-gen 且摘要超过 `maxOldGenSummaryLength`(3000 字符)时,`runTruncateGC` 截断整个摘要 — 包括追加的 skill 内容。skill 输出(通常 2–10 KB)被静默销毁。
|
|
408
|
+
|
|
409
|
+
**修复**:受保护工具消息现在被**硬排除**在压缩范围之外。当模型对包含受保护工具输出的范围调用 `compress(startId, endId)` 时,这些消息在 `applyCompressionState` 运行*之前*就从选择中被过滤掉。受保护消息完整保留在可见上下文中;只有周围的非受保护消息被压缩。
|
|
410
|
+
|
|
411
|
+
过滤器在 range 模式(`lib/compress/range.ts`)和 message 模式(`lib/compress/message.ts`)中均生效。使用现有的 `compress.protectedTools` 配置(默认:`task`、`skill`、`todowrite`、`todoread`、`decompress`)和通用的 `isToolNameProtected` 匹配器。
|
|
412
|
+
|
|
413
|
+
**验证**:实时测试 — 加载 `git-master` skill,然后压缩覆盖 skill 输出的范围。skill 消息(m00170)在压缩后存活;范围内 22 条消息中只有 15 条被压缩(7 条受保护消息正确排除)。测试:`tests/compress-protected-exclusion.test.ts` 中 29 个专用测试。
|
|
414
|
+
|
|
415
|
+
**兼容性**:无配置变更,无持久化 state schema 变更。现有的 `appendProtectedTools` 软保护逻辑作为兜底保留。
|
|
416
|
+
|
|
417
|
+
#### 压缩格式提示词重写(issue #13, PR #72)
|
|
418
|
+
|
|
419
|
+
`compress` 工具的摘要格式指引在标题处说 "EXHAUSTIVE",下方又要求 "LEAN"——自相矛盾,让模型不确定该保留多少细节。替换为清晰的 **KEEP / DROP / PRIORITY** 分类法,每条规则映射到具体操作,消除歧义。
|
|
420
|
+
|
|
421
|
+
#### 丢弃后缀中的空合成用户消息(issue #12, PR #71)
|
|
422
|
+
|
|
423
|
+
`injectCompressNudges` 有时在空合成后缀用户消息(上下文状态元数据的载体)被合并到前一个块摘要后,仍将其转发给 LLM。模型因此看到一个没有内容的空 user 轮。现在在转发前将其拼出,并增加 `dropEmptyUserMessages` 兜底守卫。
|
|
424
|
+
|
|
425
|
+
#### 上下文过渡通知箭头间距(issue #68, PR #70)
|
|
426
|
+
|
|
427
|
+
`lib/ui/notification.ts` 中的 `formatContextTransition` 渲染 `141.9K→111K` 时箭头两侧没有空格。已添加间距:`141.9K → 111K`。
|
|
428
|
+
|
|
429
|
+
#### 占位符诊断路由到 logger(issue #67, PR #69)
|
|
430
|
+
|
|
431
|
+
`lib/compress/range-utils.ts` 中的 `validateSummaryPlaceholders` 使用 `console.warn` 输出占位符不匹配警告,泄漏到 stderr 并在聊天对话框中内联渲染。改为通过插件 logger 输出,仅记录到 ACP 调试日志。
|
|
432
|
+
|
|
433
|
+
#### Dev-Deploy 旧路径同步(issue #9, PR #64)
|
|
434
|
+
|
|
435
|
+
旧解析路径 `~/.cache/opencode/node_modules/opencode-acp/` 下的陈旧安装会遮蔽 `@latest` 部署路径 `~/.cache/opencode/packages/opencode-acp@latest/`。`scripts/dev-deploy.sh` 现在同时同步两个路径,防止陈旧的旧路径副本覆盖新构建的 bundle。
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
396
439
|
### v1.9.2 — 重启后正确持久化提醒基线(bug #60)
|
|
397
440
|
|
|
398
441
|
**问题**:当每条消息的提醒纯粹因为 token 增长而触发(周围没有 compress/decompress 操作)时,更新后的 `lastPerMessageNudgeTokens` 基线只写进了内存、**没有落盘** —— `saveSessionState()` 仅在 `anchorsChanged` 为 true 时执行,而增长提醒并不总是改变 anchor(turn/iteration anchor 集合一经播种就会饱和,或最后一轮是 assistant、没有可锚定的 user 轮)。OpenCode 重启后读到的还是陈旧基线,于是 `growth = currentTokens − 陈旧基线` 又超过阈值 → 提醒在**之后每一轮**都会重新触发,直到会话结束。
|
|
@@ -418,7 +461,7 @@ ACP 在首次启动时自动将配置从 `dcp.jsonc` 迁移到 `acp.jsonc`,将
|
|
|
418
461
|
|
|
419
462
|
**修复 1 — 不连续可见 ID 段**(PR #57):`injectVisibleIdRange` 不再输出一条"首到尾"span。改为按引用升序构建真正存活的不相交段,并在段数溢出时截断到最大的含工具 / 高 token 段(`compress.maxVisibleSegments`,默认 `50`,已通过 config defaults + merge + validation + schema 全链路接入)。suffix 现在形如 `[Visible (top 2 of 3 segments, 803 msgs): m00001–m00929, m00944–m00950 | +1 smaller segment (~1.2K tokens, 6 msgs) omitted]`,模型能精确看到哪些范围可压缩、绝不会被引导去打空洞。格式化逻辑抽取为纯函数并导出、可单测(`buildVisibleSegments`、`formatVisibleGuidance`)。
|
|
420
463
|
|
|
421
|
-
**修复 2 — 提醒措辞**(PR #58):增量压缩指引行(`💡 Compress incrementally: target the ranges above...`)移到 largest-ranges 列表**之后**,并改写以强调**仅凭大小不是压缩理由** —— 仍然需要完整保留的大范围必须保留。软性效率提醒(`growth` / `minLimit` 变体)现在前置一条明确说明
|
|
464
|
+
**修复 2 — 提醒措辞**(PR #58):增量压缩指引行(`💡 Compress incrementally: target the ranges above...`)移到 largest-ranges 列表**之后**,并改写以强调**仅凭大小不是压缩理由** —— 仍然需要完整保留的大范围必须保留。软性效率提醒(`growth` / `minLimit` 变体)现在前置一条明确说明 _"This is an efficiency nudge to compress early and keep context lean — not an overflow warning. A separate, stronger alert will appear if the context is actually full."_,使增长量不被误读为溢出警报。`maxLimit` 路径保留更强的告警,并有意排除在效率措辞之外。
|
|
422
465
|
|
|
423
466
|
**兼容性**:无持久化 state schema 变更。新增可选配置字段 `compress.maxVisibleSegments`(数字,默认 `50`);旧配置继续工作。
|
|
424
467
|
|
|
@@ -429,6 +472,7 @@ ACP 在首次启动时自动将配置从 `dcp.jsonc` 迁移到 `acp.jsonc`,将
|
|
|
429
472
|
**问题**:在大上下文模型(1M+)上,模型反复调用 `compress(startId=m00930, endId=m00943)`,而这些 ID 已被之前的块消费。模型对哪些 `mNNNNN` 引用仍可压缩没有稳定视图,失败错误不提供恢复信息,`acp_status` 工具已注册但从未在提示中提及,suffix nudge 只报告一个裸百分比,完全不说明 token 实际花在了哪里。
|
|
430
473
|
|
|
431
474
|
**系统提示重写**:
|
|
475
|
+
|
|
432
476
|
- 四个上下文工具(`compress`、`decompress`、`search_context`、`acp_status`)现在每个都带一行"何时使用"提示。
|
|
433
477
|
- 显式的"压缩 / 不压缩"场景替代了命令式的"promptly 压缩明显垃圾"措辞。
|
|
434
478
|
- 新增 **CONTEXT BREAKDOWN** 章节,解释 4 类后缀格式(`tool | summaries | code | text`)、最大范围候选项,以及"每次调用针对一个已消费的大范围"的增量策略。
|
|
@@ -436,6 +480,7 @@ ACP 在首次启动时自动将配置从 `dcp.jsonc` 迁移到 `acp.jsonc`,将
|
|
|
436
480
|
- 新增 **task-phase-end** 触发器:当一次 bug 排查 / 探索 / 研究冲刺结束时,主动压缩该阶段的冗余翻腾,同时保留关键发现、文件路径、决策依据。
|
|
437
481
|
|
|
438
482
|
**Nudge 频率**:
|
|
483
|
+
|
|
439
484
|
- 彻底移除 `contextPct >= 15%` 下限。频率现在纯粹是 5%-of-limit 增长,首轮建立基线(不再在第 1 轮强制触发)。
|
|
440
485
|
- 基线在压缩后 token 显著下降时自动重置,使下一次 nudge 在压缩后的水平触发,而不是等满一个完整增长周期。
|
|
441
486
|
- suffix nudge 新增 **3 类组成分解**(`tool | summaries | code | text`,不再对含 code 的消息双重计数),加上 tool 和 code 类别的**最大范围**列表 —— 具体的压缩目标,而不是裸百分比。
|
|
@@ -445,6 +490,7 @@ ACP 在首次启动时自动将配置从 `dcp.jsonc` 迁移到 `acp.jsonc`,将
|
|
|
445
490
|
**压缩失败恢复**:`resolveBoundaryIds` 失败现在返回当前可见范围(首/尾引用)、活跃块数,以及指向 `acp_status` 的指引。超出范围的 `endId` 猜测(未注册但解析值高于最后一条可见消息的引用)会被**钳制**到最后一条可见消息,而不是失败;已注册但已被消费的引用仍然失败并附恢复提示(钳制它们会静默重新压缩已摘要的内容)。
|
|
446
491
|
|
|
447
492
|
**加固**:
|
|
493
|
+
|
|
448
494
|
- `maxSummaryLengthHard` 默认值提升 `4000 → 8000 → 10000`;compress 工具 schema 现在从 config 取显示值,配置变更能传播。
|
|
449
495
|
- 移除陈旧的 `MODEL_CONTEXT_LIMITS` 38 项回退表 —— `modelContextLimit` 现在仅来自 host SDK 的 `input.model.limit.context`。省略该字段的 provider 会立即暴露 `undefined`,而不是从陈旧猜测中得到扭曲的百分比。
|
|
450
496
|
- 所有 fire-and-forget `saveSessionState` 调用添加 `.catch()`;移除了触发并发保存竞态的 baseline `anchorsChanged` 路径。
|
|
@@ -462,6 +508,7 @@ ACP 在首次启动时自动将配置从 `dcp.jsonc` 迁移到 `acp.jsonc`,将
|
|
|
462
508
|
**修复**:移除系统提示词 hook 的 `shouldInjectThisTurn` gate(`hooks.ts:108-112`)。系统提示词现在每轮都注入。Suffix 仍按 `nudgeGrowthTokens` 频率 gate。
|
|
463
509
|
|
|
464
510
|
**当前行为**:
|
|
511
|
+
|
|
465
512
|
- **系统提示词**(压缩哲学、工具意识):✅ 每轮
|
|
466
513
|
- **Suffix**(上下文水平、块列表、Tips):按 nudgeGrowthTokens 频率
|
|
467
514
|
|
|
@@ -472,31 +519,37 @@ ACP 在首次启动时自动将配置从 `dcp.jsonc` 迁移到 `acp.jsonc`,将
|
|
|
472
519
|
**问题**:大上下文模型(1M+)在 20-30% 上下文时过度压缩,因为 Tips 每 6K tokens(1M 的 0.6%)就触发一次。系统提示每轮注入增加了持续压力。
|
|
473
520
|
|
|
474
521
|
**自适应 nudgeGrowthTokens**:
|
|
522
|
+
|
|
475
523
|
- 默认值现在自适应:`modelContextLimit` 的 5%,限制在 [6000, 50000]
|
|
476
|
-
|
|
524
|
+
- 128K → 6.4K,200K → 10K,500K → 25K,1M → 50K,2M+ → 50K(上限)
|
|
477
525
|
- 用户仍可显式设置 `nudgeGrowthTokens` 覆盖
|
|
478
526
|
- 移除了 schema 默认值中的硬编码 `6000`(之前覆盖了自适应逻辑)
|
|
479
527
|
|
|
480
528
|
**系统提示门控**:
|
|
529
|
+
|
|
481
530
|
- SYSTEM 提示 + `<dcp-system-reminder>` 标签现在按 `nudgeGrowthTokens` 频率脉冲
|
|
482
531
|
- 两次提醒之间:系统提示**不注入任何内容** —— 零压缩噪音
|
|
483
532
|
- 第一轮(`undefined` 哨兵值):始终注入(建立基线)
|
|
484
533
|
|
|
485
534
|
**新工具:`acp_status`**:
|
|
535
|
+
|
|
486
536
|
- 按需查看所有压缩块(ID、token 数、年龄、主题)
|
|
487
537
|
- 用一行摘要替代 suffix 中的冗长块列表:`Compressed blocks: N (XK summary, last Ym ago). Use acp_status for details.`
|
|
488
538
|
|
|
489
539
|
**压缩通知改进**:
|
|
540
|
+
|
|
490
541
|
- 头部显示上下文前后水平:`▣ ACP | Context 251.2K→249.3K`
|
|
491
542
|
- 不显示百分比或上限(防止模型锚定天花板)
|
|
492
543
|
|
|
493
544
|
**Bug 修复**:
|
|
545
|
+
|
|
494
546
|
- `lastPerMessageNudgeTokens` 压缩后重置为 `0` 绕过了增长检查(反馈循环)
|
|
495
547
|
- Schema 默认值 `6000` 覆盖了 `resolveAdaptiveNudgeGrowth()` —— 自适应从未生效
|
|
496
548
|
- `applyAnchoredNudges` + `injectContextUsage` 重复注入上下文使用文本
|
|
497
549
|
- `lastNudgeTokens === 0` 哨兵值替换为 `undefined`(明确的"从未触发")
|
|
498
550
|
|
|
499
551
|
**工具链**:
|
|
552
|
+
|
|
500
553
|
- `scripts/dev-deploy.sh` —— 一键构建 + 部署(自动检测 node、类型检查、构建、部署)
|
|
501
554
|
- 压缩后状态转换集成测试(新增 3 个)
|
|
502
555
|
- `acp_status` 独立测试(新增 7 个)
|
|
@@ -508,21 +561,25 @@ ACP 在首次启动时自动将配置从 `dcp.jsonc` 迁移到 `acp.jsonc`,将
|
|
|
508
561
|
**理念**:用 4 条简洁原则替代冗长的上下文管理指导。模型现在看到的是*重要原则*而非*死板规则*。
|
|
509
562
|
|
|
510
563
|
**提示变更**:
|
|
564
|
+
|
|
511
565
|
- 4 条原则替代 CONTEXT PRESSURE LEVELS、7 项优先级列表、DO NOT RE-COMPRESS 规则
|
|
512
566
|
- 上下文显示简化:仅显示绝对 token 数,不显示百分比
|
|
513
567
|
- `<acp-context>` 标签包裹(向后兼容 `<dcp-context>`)
|
|
514
568
|
|
|
515
569
|
**混合 Tips 频率**:
|
|
570
|
+
|
|
516
571
|
- 💡 轻量提示(15-45%):每轮显示 — 不打扰
|
|
517
572
|
- ⚠️ 警告提示(45%+):仅关键节点 — 首次跨越或增长 10pp,防止过度压缩
|
|
518
573
|
|
|
519
574
|
**配置简化**:
|
|
575
|
+
|
|
520
576
|
- 移除 `hardNudgeContextPercent` — 合并到 `minContextLimit`/`maxContextLimit`
|
|
521
577
|
- 移除 `perMessageNudgeGrowthPercent` — 轻量提示每轮显示
|
|
522
578
|
- `maxSummaryLength` 默认值:200 → 2000
|
|
523
579
|
- `maxSummaryLengthHard` 默认值:3000 → 4000
|
|
524
580
|
|
|
525
581
|
**Bug 修复**:
|
|
582
|
+
|
|
526
583
|
- Windows 路径校验:`os.tmpdir()` + `path.relative()`(原硬编码 `/tmp/`)
|
|
527
584
|
- 压缩检测后:重置警告追踪
|
|
528
585
|
- 死代码清理:`shouldInjectPerMessageNudge`、空操作模板
|
package/dist/index.js
CHANGED
|
@@ -896,6 +896,7 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
896
896
|
"compress.nudgeFrequency",
|
|
897
897
|
"compress.minNudgeContextPercent",
|
|
898
898
|
"compress.nudgeGrowthTokens",
|
|
899
|
+
"compress.toolOutputNudgeThreshold",
|
|
899
900
|
"compress.iterationNudgeThreshold",
|
|
900
901
|
"compress.nudgeForce",
|
|
901
902
|
"compress.protectedTools",
|
|
@@ -1663,6 +1664,7 @@ function mergeCompress(base, override) {
|
|
|
1663
1664
|
nudgeFrequency: override.nudgeFrequency ?? base.nudgeFrequency,
|
|
1664
1665
|
minNudgeContextPercent: override.minNudgeContextPercent ?? base.minNudgeContextPercent,
|
|
1665
1666
|
nudgeGrowthTokens: override.nudgeGrowthTokens,
|
|
1667
|
+
toolOutputNudgeThreshold: override.toolOutputNudgeThreshold,
|
|
1666
1668
|
iterationNudgeThreshold: override.iterationNudgeThreshold ?? base.iterationNudgeThreshold,
|
|
1667
1669
|
nudgeForce: override.nudgeForce ?? base.nudgeForce,
|
|
1668
1670
|
protectedTools: [.../* @__PURE__ */ new Set([...base.protectedTools, ...override.protectedTools ?? []])],
|
|
@@ -3701,7 +3703,8 @@ async function saveSessionState(sessionState, logger, sessionName) {
|
|
|
3701
3703
|
byRef: Object.fromEntries(sessionState.messageIds.byRef),
|
|
3702
3704
|
nextRef: sessionState.messageIds.nextRef
|
|
3703
3705
|
},
|
|
3704
|
-
lastCompaction: sessionState.lastCompaction
|
|
3706
|
+
lastCompaction: sessionState.lastCompaction,
|
|
3707
|
+
modelContextLimit: sessionState.modelContextLimit
|
|
3705
3708
|
};
|
|
3706
3709
|
await writePersistedSessionState(sessionState.sessionId, state, logger);
|
|
3707
3710
|
}
|
|
@@ -4032,6 +4035,9 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
|
|
|
4032
4035
|
if (persistedAny._persistedLastCompaction !== void 0) {
|
|
4033
4036
|
state.lastCompaction = Math.max(state.lastCompaction, persistedAny._persistedLastCompaction);
|
|
4034
4037
|
}
|
|
4038
|
+
if (typeof persisted.modelContextLimit === "number" && persisted.modelContextLimit > 0) {
|
|
4039
|
+
state.modelContextLimit = persisted.modelContextLimit;
|
|
4040
|
+
}
|
|
4035
4041
|
const applied = applyPendingCompressionDurations(state);
|
|
4036
4042
|
if (applied > 0) {
|
|
4037
4043
|
await saveSessionState(state, logger);
|
|
@@ -6282,8 +6288,8 @@ function estimateContextComposition(messages, state) {
|
|
|
6282
6288
|
messageTokens,
|
|
6283
6289
|
textTokens: Math.max(0, messageTokens - codeTokens),
|
|
6284
6290
|
total: toolTokens + summaryTokens + messageTokens,
|
|
6285
|
-
largestRanges: perMessage.slice(0,
|
|
6286
|
-
largestToolRanges: perTool.slice(0,
|
|
6291
|
+
largestRanges: perMessage.slice(0, 15),
|
|
6292
|
+
largestToolRanges: perTool.slice(0, 15),
|
|
6287
6293
|
largestCodeRanges: perCode.slice(0, 5),
|
|
6288
6294
|
largestMessageRanges: perText.slice(0, 5)
|
|
6289
6295
|
};
|
|
@@ -6440,7 +6446,7 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
6440
6446
|
state.nudges.lastPerMessageNudgeTokens = currentTokens;
|
|
6441
6447
|
}
|
|
6442
6448
|
const composition = estimateContextComposition(messages, state);
|
|
6443
|
-
const toolOutputThreshold = config.compress?.toolOutputNudgeThreshold ??
|
|
6449
|
+
const toolOutputThreshold = config.compress?.toolOutputNudgeThreshold ?? nudgeGrowthTokens;
|
|
6444
6450
|
let toolOutputReminder = null;
|
|
6445
6451
|
if (composition.toolTokens > 0) {
|
|
6446
6452
|
if (state.nudges.lastToolOutputNudgeTokens === void 0) {
|
|
@@ -6449,7 +6455,7 @@ var injectCompressNudges = (state, config, logger, messages, prompts, compressio
|
|
|
6449
6455
|
const toolGrowth = composition.toolTokens - state.nudges.lastToolOutputNudgeTokens;
|
|
6450
6456
|
if (toolGrowth >= toolOutputThreshold) {
|
|
6451
6457
|
const fmt = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
|
|
6452
|
-
const topRanges = composition.largestRanges.slice(0,
|
|
6458
|
+
const topRanges = composition.largestRanges.slice(0, 15).map((r) => `${r.ref} (${fmt(r.tokens)})`).join(", ");
|
|
6453
6459
|
toolOutputReminder = `
|
|
6454
6460
|
|
|
6455
6461
|
\u26A0\uFE0F ${fmt(toolGrowth)} new tool outputs accumulated (${fmt(composition.toolTokens)} total). Largest: ${topRanges}. Use compress tool to compress these ranges now.`;
|