opencode-acp 1.13.1 → 1.13.2

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
@@ -235,7 +235,7 @@ Each level overrides the previous, so project settings take priority over global
235
235
  // Enable debug logging to ~/.config/opencode/logs/acp/
236
236
  "debug": false,
237
237
  // Notification display: "off", "minimal", or "detailed"
238
- "pruneNotification": "detailed",
238
+ "pruneNotification": "off",
239
239
  // Notification type: "chat" (deprecated, falls back to toast) or "toast" (system toast)
240
240
  "pruneNotificationType": "toast",
241
241
  // Slash commands configuration
@@ -471,6 +471,12 @@ For the complete list with root cause analysis, see the [bug tracker](https://gi
471
471
 
472
472
  ## Changelog
473
473
 
474
+ ### v1.13.2 — Preserve Last User Msg + Config Defaults Tuning (PR #169)
475
+
476
+ **Problem**: Two issues remained after v1.13.1's notification freeze fix. (1) When the model compressed a range that covered all visible user messages, the next API call had zero user-role messages — zhipuai-lb rejected this with the same HTTP 400 code 1214 (`isRetryable: false`), freezing the session. This was the second path to the same freeze that v1.13.1's empty-notification fix addressed. (2) The default `pruneNotification: "detailed"` fired a toast on every compress call (10–30 per session is typical), which was over-intrusive for a routine background operation. Additionally, `compress.maxSummaryLengthHard: 10000` rejected ~25% of information-dense useful summaries in real sessions.
477
+
478
+ **Fix**: (1) `lib/messages/prune.ts` — `filterCompressedRanges` rewritten as a two-pass filter: pass 1 computes survivors, pass 2 builds the result; if no user-role message would survive, the most recent pruned user message is restored to keep the API request shape valid. The restore is transform-time only — `byMessageId` still records the message as compressed. (2) `lib/config.ts` — default `pruneNotification` changed `"detailed"` → `"off"`; compression events still log to `~/.config/opencode/logs/acp/` via a new always-log path in `lib/ui/notification.ts` (lossless observability without UI noise). (3) `lib/config.ts` — default `compress.maxSummaryLengthHard` raised `10000` → `20000` (aligns with observed good-summary lengths). (4) `dcp.schema.json` — 4 stale defaults synced. Files: `lib/messages/prune.ts`, `lib/config.ts`, `lib/ui/notification.ts`, `dcp.schema.json`, `README.md`. Tests: 803 pass (5 new regression tests for the preserve-last-user fix).
479
+
474
480
  ### v1.13.1 — cc-alg Extraction + Compress Notification Freeze Fix (PRs #167, #168)
475
481
 
476
482
  **Problem (compress notification freeze, #167)**: After every successful `compress` tool call, ACP injected a user-role notification message with a single `ignored: true` text part. opencode strips `ignored` parts before sending to the LLM, leaving an empty user message. The provider (zhipuai-lb / glm-5.2) rejects this with HTTP 400 code 1214 (`"messages 参数非法"`), `isRetryable: false` — opencode does not retry, and the session freezes until external recovery. 113 total occurrences across active sessions (8 in a single 3,156-message session).
package/README.zh-CN.md CHANGED
@@ -439,6 +439,12 @@ ACP 在首次启动时自动将配置从 `dcp.jsonc` 迁移到 `acp.jsonc`,将
439
439
 
440
440
  ## 更新日志
441
441
 
442
+ ### v1.13.2 — 保留最近用户消息 + 配置默认值调优(PR #169)
443
+
444
+ **问题**:v1.13.1 的通知冻结修复之后还剩两个问题。(1)当模型压缩的范围覆盖了所有可见的 user 消息时,下一次 API 调用中 user 角色消息数量为零——zhipuai-lb 以同样的 HTTP 400 code 1214(`isRetryable: false`)拒绝,会话冻结。这是 v1.13.1 修复的空通知路径之外,通往同一冻结 bug 的第二条路径。(2)默认 `pruneNotification: "detailed"` 每次压缩都弹 toast(典型会话 10–30 次),对例行后台操作来说过于打扰。另外 `compress.maxSummaryLengthHard: 10000` 在真实会话中拒绝了约 25% 信息密度高的有用摘要。
445
+
446
+ **修复**:(1)`lib/messages/prune.ts`——`filterCompressedRanges` 重写为两段过滤:第一段计算存活消息,第二段构建结果;如果没有 user 角色消息存活,恢复最近一条被压缩的 user 消息以保证 API 请求格式合法。恢复仅发生在 transform 阶段——`byMessageId` 仍记录该消息为已压缩。(2)`lib/config.ts`——默认 `pruneNotification` 改为 `"off"`;压缩事件仍通过 `lib/ui/notification.ts` 新增的 always-log 路径记录到 `~/.config/opencode/logs/acp/`(无损失可观测性,无 UI 噪音)。(3)`lib/config.ts`——默认 `compress.maxSummaryLengthHard` 从 `10000` 提升到 `20000`(与真实会话中观察到的优质摘要长度对齐)。(4)`dcp.schema.json`——同步 4 个过时默认值。文件:`lib/messages/prune.ts`、`lib/config.ts`、`lib/ui/notification.ts`、`dcp.schema.json`、`README.md`。测试:803 pass(5 个新的 preserve-last-user 回归测试)。
447
+
442
448
  ### v1.13.1 — cc-alg 抽取 + 压缩通知冻结修复(PR #167, #168)
443
449
 
444
450
  **问题(压缩通知冻结,#167)**:每次 `compress` 工具调用成功后,ACP 会注入一条 user 角色通知消息,其中只包含一个带 `ignored: true` 标记的 text part。opencode 在发送给 LLM 前会剥离 ignored parts,于是这条消息变成空 user 消息。Provider(zhipuai-lb / glm-5.2)会以 HTTP 400 code 1214(`"messages 参数非法"`)拒绝,且 `isRetryable: false`——opencode 不会重试,会话冻结,直到外部恢复。所有活跃会话累计发生 113 次(单个 3,156 条消息的会话出现 8 次)。
package/dist/index.js CHANGED
@@ -1557,7 +1557,7 @@ var defaultConfig = {
1557
1557
  enabled: true,
1558
1558
  autoUpdate: true,
1559
1559
  debug: false,
1560
- pruneNotification: "detailed",
1560
+ pruneNotification: "off",
1561
1561
  // [FIX #20] Default to toast — chat-mode notifications inject an empty
1562
1562
  // user message that freezes the session on providers that reject empty
1563
1563
  // messages (zhipuai-lb code 1214). See lib/ui/notification.ts.
@@ -1593,7 +1593,7 @@ var defaultConfig = {
1593
1593
  protectedTools: [...COMPRESS_DEFAULT_PROTECTED_TOOLS],
1594
1594
  protectTags: false,
1595
1595
  protectUserMessages: false,
1596
- maxSummaryLengthHard: 1e4,
1596
+ maxSummaryLengthHard: 2e4,
1597
1597
  minCompressRange: 5e3,
1598
1598
  minNudgeGrowthRatio: 0.45,
1599
1599
  minNudgeGrowthFloor: 5e3,
@@ -5078,10 +5078,25 @@ function formatContextTransition(tokensBefore, tokensAfter) {
5078
5078
  return `Context ${beforeStr} \u2192 ${afterStr}`;
5079
5079
  }
5080
5080
  async function sendCompressNotification(client, logger, config, state, sessionId, entries, batchTopic, sessionMessageIds, params, contextTokensBefore) {
5081
- if (config.pruneNotification === "off") {
5081
+ if (entries.length === 0) {
5082
5082
  return false;
5083
5083
  }
5084
- if (entries.length === 0) {
5084
+ const logBlockIds = entries.map((e) => e.blockId);
5085
+ const logTopics = entries.map((e) => state.prune.messages.blocksById.get(e.blockId)?.topic ?? "?");
5086
+ const logCompressedTokens = entries.reduce((sum, e) => {
5087
+ const block = state.prune.messages.blocksById.get(e.blockId);
5088
+ return sum + (block?.compressedTokens ?? 0);
5089
+ }, 0);
5090
+ const logSummaryTokens = entries.reduce((sum, e) => sum + e.summaryTokens, 0);
5091
+ logger.info("Compression completed", {
5092
+ sessionId,
5093
+ blockIds: logBlockIds,
5094
+ topics: logTopics,
5095
+ compressedTokens: logCompressedTokens,
5096
+ summaryTokens: logSummaryTokens,
5097
+ contextTokensBefore
5098
+ });
5099
+ if (config.pruneNotification === "off") {
5085
5100
  return false;
5086
5101
  }
5087
5102
  let message;
@@ -6669,15 +6684,29 @@ var filterCompressedRanges = (state, messages) => {
6669
6684
  if (state.prune.messages.byMessageId.size === 0) {
6670
6685
  return;
6671
6686
  }
6687
+ const survive = messages.map((msg) => {
6688
+ const pruneEntry = state.prune.messages.byMessageId.get(msg.info.id);
6689
+ if (!pruneEntry || pruneEntry.activeBlockIds.length === 0) {
6690
+ return true;
6691
+ }
6692
+ return false;
6693
+ });
6694
+ const anyUserSurvives = messages.some(
6695
+ (msg, i) => survive[i] && msg.info.role === "user"
6696
+ );
6697
+ if (!anyUserSurvives) {
6698
+ for (let i = messages.length - 1; i >= 0; i--) {
6699
+ if (messages[i].info.role === "user" && !survive[i]) {
6700
+ survive[i] = true;
6701
+ break;
6702
+ }
6703
+ }
6704
+ }
6672
6705
  const result = [];
6673
6706
  for (let i = 0; i < messages.length; i++) {
6674
- const msg = messages[i];
6675
- const msgId = msg.info.id;
6676
- const pruneEntry = state.prune.messages.byMessageId.get(msgId);
6677
- if (pruneEntry && pruneEntry.activeBlockIds.length > 0) {
6678
- continue;
6707
+ if (survive[i]) {
6708
+ result.push(messages[i]);
6679
6709
  }
6680
- result.push(msg);
6681
6710
  }
6682
6711
  messages.length = 0;
6683
6712
  messages.push(...result);