opencode-acp 1.14.8-dev.2 → 1.14.8

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 CHANGED
@@ -492,6 +492,33 @@ For the complete list with root cause analysis, see the [bug tracker](https://gi
492
492
 
493
493
  ## Changelog
494
494
 
495
+ ### v1.14.8 — Stable Release (10 PRs since v1.14.7)
496
+
497
+ Stable release promoted from dev prereleases v1.14.8-dev.1 through v1.14.8-dev.3. Includes all fixes tested in the dev channel.
498
+
499
+ **Highlights**:
500
+
501
+ - **System token visibility** (#241): `acp_status` and nudge breakdown now show system prompt token proportion (5-15% of context). Consolidated 4 duplicate estimation algorithms into 1 shared `estimateSystemPromptTokens()` using the real Anthropic tokenizer.
502
+ - **HOW_TO_COMPRESS_RULES re-added to nudge** (#245): v1.14.7 over-removed the compression rules from all nudge locations. In long sessions (8,000+ messages), the rules in the system prompt degrade due to "lost in the middle" effect — re-added to nudge for high-attention guidance when compression triggers.
503
+ - **Tool pair integrity** (#248): Compression ranges that split tool_use/tool_result pairs caused API rejections. New `adjustBoundariesForToolPairs` auto-extends boundaries by callID matching.
504
+ - **Pluggable message filters** (#239, #242): `keepLastOnly` dedup mechanism + 4 OMO builtin filters for cleaning up third-party (e.g. OMO) injected messages that accumulate duplicates.
505
+ - **Orphan message splicing** (#240): When `hideConsumedCompressCalls` leaves only structural parts (step-finish, reasoning), the message is now spliced out instead of surviving as a 500-2000 token orphan.
506
+ - **E2E hardening** (#238): Observation recording, T2 cadence regression scenario, consumed-call hiding scenario, auxiliary call filtering. 12 E2E scenarios (was 8).
507
+ - **acp_status consumed compress hiding** (#244): Hides consumed compress calls from the PROTECTED list.
508
+
509
+ **All PRs included**: #238 (E2E hardening), #232 (remove dead turnProtection/DCP migration), #234 (re-add /acp stats), #239 (pluggable message filter), #240 (orphan splice), #241 (system tokens), #242 (keepLastOnly + OMO filters), #244 (acp_status consumed compress), #245 (HOW_TO_COMPRESS_RULES re-add), #248 (tool pair integrity).
510
+
511
+ **Install**: `opencode plugin opencode-acp@stable --global`
512
+
513
+ ### v1.14.8-dev.3 — Dev Prerelease (1 PR since v1.14.8-dev.2)
514
+
515
+ Dev prerelease covering PR #248. Published to `dev` npm tag for early testing.
516
+
517
+ **PRs included**:
518
+ - **#248** — Fix: auto-extend compression ranges to prevent splitting tool_use/tool_result pairs. When the model selects a compression boundary that falls between a tool call and its result, the orphaned tool_result references a non-existent tool_use_id → API rejection. New `adjustBoundariesForToolPairs` in `compress/search.ts` scans forward/backward (up to 20 messages) to include the matching pair. Excludes `compress` tool (force-protected) and only extends message boundaries (never block boundaries) to avoid tier misclassification. 11 tests.
519
+
520
+ **Install**: `opencode plugin opencode-acp@dev --global`
521
+
495
522
  ### v1.14.8-dev.2 — Dev Prerelease (2 PRs since v1.14.8-dev.1)
496
523
 
497
524
  Dev prerelease covering PRs #244–#245. Published to `dev` npm tag for early testing.
package/README.zh-CN.md CHANGED
@@ -446,6 +446,15 @@ ACP 是 DCP 的直接替代品。迁移步骤:
446
446
 
447
447
  ## 更新日志
448
448
 
449
+ ### v1.14.8-dev.3 — Dev 预发布(v1.14.8-dev.2 以来 1 个 PR)
450
+
451
+ Dev 预发布,涵盖 PR #248。发布到 `dev` npm tag 供早期测试。
452
+
453
+ **包含的 PR**:
454
+ - **#248** — 修复:自动扩展压缩范围以防止拆分 tool_use/tool_result 配对。当模型选择的压缩边界落在 tool 调用与其结果之间时,孤立的 tool_result 引用不存在的 tool_use_id → API 拒绝。新增 `adjustBoundariesForToolPairs`(`compress/search.ts`)向前/向后扫描(最多 20 条消息)以包含匹配的配对。排除 `compress` 工具(强制保护)并仅扩展消息边界(从不扩展 block 边界)以避免层级错误分类。11 个测试。
455
+
456
+ **安装**:`opencode plugin opencode-acp@dev --global`
457
+
449
458
  ### v1.14.8-dev.2 — Dev 预发布(v1.14.8-dev.1 以来 2 个 PR)
450
459
 
451
460
  Dev 预发布,涵盖 PR #244–#245。发布到 `dev` npm tag 供早期测试。
@@ -471,6 +480,24 @@ Dev 预发布,涵盖 PR #238–#242。发布到 `dev` npm tag 供早期测试
471
480
 
472
481
  **安装**:`opencode plugin opencode-acp@dev --global`
473
482
 
483
+ ### v1.14.8 — 正式版(v1.14.7 以来 10 个 PR)
484
+
485
+ 正式版,从 dev 预发布 v1.14.8-dev.1 至 v1.14.8-dev.3 提升而来。包含所有在 dev 通道测试过的修复。
486
+
487
+ **亮点**:
488
+
489
+ - **系统 token 可见性**(#241):`acp_status` 和 nudge breakdown 现在显示系统 prompt token 占比(上下文的 5-15%)。将 4 处重复的估算算法整合为 1 个共享 `estimateSystemPromptTokens()`,使用真正的 Anthropic 分词器。
490
+ - **HOW_TO_COMPRESS_RULES 重新加入 nudge**(#245):v1.14.7 过度删除了所有 nudge 位置的压缩规则。在长 session(8000+ 消息)中,system prompt 中的规则因"lost in the middle"效应衰减——重新加入 nudge 以在压缩触发时提供高注意力区指导。
491
+ - **工具配对完整性**(#248):压缩范围拆分 tool_use/tool_result 配对会导致 API 拒绝。新增 `adjustBoundariesForToolPairs` 按 callID 匹配自动扩展边界。
492
+ - **可插拔消息过滤器**(#239, #242):`keepLastOnly` 去重机制 + 4 个 OMO 内置过滤器,用于清理第三方注入消息的重复累积。
493
+ - **孤儿消息清理**(#240):`hideConsumedCompressCalls` 仅剩结构化部分时(step-finish、reasoning),消息现在被 splice 掉,不再作为 500-2000 token 孤儿存活。
494
+ - **E2E 强化**(#238):观察记录、T2 节奏回归场景、consumed-call 隐藏场景、辅助调用过滤。E2E 场景 8→12 个。
495
+ - **acp_status 隐藏已消耗 compress**(#244):从 PROTECTED 列表中隐藏已消耗的 compress 调用。
496
+
497
+ **包含的所有 PR**:#238(E2E 强化)、#232(移除死代码 turnProtection/DCP 迁移)、#234(重新添加 /acp stats)、#239(可插拔消息过滤器)、#240(孤儿消息清理)、#241(系统 token)、#242(keepLastOnly + OMO 过滤器)、#244(acp_status 已消耗 compress)、#245(HOW_TO_COMPRESS_RULES 重新加入)、#248(工具配对完整性)。
498
+
499
+ **安装**:`opencode plugin opencode-acp@stable --global`
500
+
474
501
  ### v1.14.7 — 去重 HOW_TO_COMPRESS_RULES(PR #228)
475
502
 
476
503
  **问题**:`HOW_TO_COMPRESS_RULES`(~1.2K tokens)每次 nudge 注入重复 3-4 次——系统提示词 1 次、nudge 模板 1-2 次、breakdown 块 1 次。非 maxLimit 场景下 suffix 消息单独就包含 2-3 份规则。每次 nudge 浪费 2.4-3.6K tokens。v1.14.6 让 debug 模式持久化 nudge 文本到聊天界面后,重复变得可见。
package/dist/index.js CHANGED
@@ -3040,8 +3040,79 @@ ${hint}` : body);
3040
3040
  if (startReference.rawIndex > endReference.rawIndex) {
3041
3041
  [startReference, endReference] = [endReference, startReference];
3042
3042
  }
3043
+ const adjusted = adjustBoundariesForToolPairs(
3044
+ context,
3045
+ startReference.rawIndex,
3046
+ endReference.rawIndex
3047
+ );
3048
+ if (adjusted.startIndex < startReference.rawIndex && startReference.kind === "message") {
3049
+ const msg = context.rawMessages[adjusted.startIndex];
3050
+ if (msg) {
3051
+ startReference = {
3052
+ kind: "message",
3053
+ rawIndex: adjusted.startIndex,
3054
+ messageId: msg.info.id
3055
+ };
3056
+ }
3057
+ }
3058
+ if (adjusted.endIndex > endReference.rawIndex && endReference.kind === "message") {
3059
+ const msg = context.rawMessages[adjusted.endIndex];
3060
+ if (msg) {
3061
+ endReference = {
3062
+ kind: "message",
3063
+ rawIndex: adjusted.endIndex,
3064
+ messageId: msg.info.id
3065
+ };
3066
+ }
3067
+ }
3043
3068
  return { startReference, endReference };
3044
3069
  }
3070
+ function adjustBoundariesForToolPairs(context, startIdx, endIdx) {
3071
+ const messages = context.rawMessages;
3072
+ const callIdsInRange = /* @__PURE__ */ new Set();
3073
+ for (let i = startIdx; i <= endIdx; i++) {
3074
+ const msg = messages[i];
3075
+ if (!msg) continue;
3076
+ const parts = Array.isArray(msg.parts) ? msg.parts : [];
3077
+ for (const part of parts) {
3078
+ if (part.type !== "tool" || !part.callID) continue;
3079
+ if (part.tool === "compress") continue;
3080
+ callIdsInRange.add(part.callID);
3081
+ }
3082
+ }
3083
+ if (callIdsInRange.size === 0) {
3084
+ return { startIndex: startIdx, endIndex: endIdx };
3085
+ }
3086
+ let newEndIdx = endIdx;
3087
+ for (let i = endIdx + 1; i < messages.length && i <= endIdx + 20; i++) {
3088
+ const msg = messages[i];
3089
+ if (!msg) break;
3090
+ const parts = Array.isArray(msg.parts) ? msg.parts : [];
3091
+ const hasMatch = parts.some(
3092
+ (part) => part.type === "tool" && part.callID && callIdsInRange.has(part.callID)
3093
+ );
3094
+ if (hasMatch) {
3095
+ newEndIdx = i;
3096
+ } else if (newEndIdx > endIdx) {
3097
+ break;
3098
+ }
3099
+ }
3100
+ let newStartIdx = startIdx;
3101
+ for (let i = startIdx - 1; i >= 0 && i >= startIdx - 20; i--) {
3102
+ const msg = messages[i];
3103
+ if (!msg) break;
3104
+ const parts = Array.isArray(msg.parts) ? msg.parts : [];
3105
+ const hasMatch = parts.some(
3106
+ (part) => part.type === "tool" && part.callID && callIdsInRange.has(part.callID)
3107
+ );
3108
+ if (hasMatch) {
3109
+ newStartIdx = i;
3110
+ } else if (newStartIdx < startIdx) {
3111
+ break;
3112
+ }
3113
+ }
3114
+ return { startIndex: newStartIdx, endIndex: newEndIdx };
3115
+ }
3045
3116
  function buildBoundaryRecoveryHint(context, state) {
3046
3117
  const visibleRefs = [];
3047
3118
  for (const [messageRef, messageId] of state.messageIds.byRef) {
@@ -8821,7 +8892,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
8821
8892
  import { join as join3 } from "path";
8822
8893
  import { existsSync as existsSync3 } from "fs";
8823
8894
  import { homedir as homedir3 } from "os";
8824
- var LOG_VERSION = true ? "1.14.8-dev.2" : "dev";
8895
+ var LOG_VERSION = true ? "1.14.8" : "dev";
8825
8896
  var Logger = class {
8826
8897
  logDir;
8827
8898
  enabled;