opencode-acp 1.13.8-dev.1 → 1.13.9-dev.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 CHANGED
@@ -475,6 +475,14 @@ For the complete list with root cause analysis, see the [bug tracker](https://gi
475
475
 
476
476
  ## Changelog
477
477
 
478
+ ### v1.13.9-dev.1 — Remove Subagent History Rewriting (PR #180)
479
+
480
+ **Problem**: `injectExtendedSubAgentResults` rewrote historical `<task_result>` tool outputs in the parent agent's message history on every transform run when `experimental.allowSubAgents: true`. The `subAgentResultCache` was cleared on every parent↔child session switch and was never persisted, so each transform run re-fetched the subagent session and produced a new historical message body — invalidating the provider prefix cache (observed: ~56% hit rate vs healthy 96–98%, prefix frozen at ~22K tokens).
481
+
482
+ **Fix**: PR #180 — Removed `injectExtendedSubAgentResults` from the message-transform pipeline and `appendProtectedTools`. Deleted `lib/messages/inject/subagent-results.ts` (82 lines) and `lib/subagents/subagent-results.ts` (74 lines). Dropped `subAgentResultCache` field from `SessionState`. The rewrite was redundant: OpenCode natively appends a `state="completed"` message with the full subagent result immediately after the `task` call completes. `experimental.allowSubAgents` still controls whether ACP runs inside subagent sessions — only the parent-history rewriting is gone. Dual-agent reviewed (both APPROVE).
483
+
484
+ Files: `lib/hooks.ts`, `lib/compress/protected-content.ts`, `lib/compress/{message,range}.ts`, `lib/state/{state,types}.ts`, `lib/messages/index.ts`, `AGENTS.md`. 851 tests pass.
485
+
478
486
  ### v1.13.8-dev.1 — Dev Prerelease Sync (master @ v1.13.7)
479
487
 
480
488
  **Purpose**: Sync the `dev` npm tag with v1.13.7 stable. Content is identical to v1.13.7 — no new code changes. This brings `opencode-acp@dev` up to parity with `opencode-acp@latest` (1.13.7).
package/README.zh-CN.md CHANGED
@@ -443,6 +443,14 @@ ACP 在首次启动时自动将配置从 `dcp.jsonc` 迁移到 `acp.jsonc`,将
443
443
 
444
444
  ## 更新日志
445
445
 
446
+ ### v1.13.9-dev.1 — 移除子代理历史重写(PR #180)
447
+
448
+ **问题**:`injectExtendedSubAgentResults` 在 `experimental.allowSubAgents: true` 时,每次消息变换都会重写父代理历史中的 `<task_result>` 工具输出。`subAgentResultCache` 在每次父↔子会话切换时被清空且从不持久化,导致每次变换都重新获取子代理会话并生成新的历史消息体 —— 使 provider prefix cache 失效(观察到的命中率约 56%,健康水平为 96-98%,prefix 冻结在约 22K tokens)。
449
+
450
+ **修复**:PR #180 —— 从消息变换管道和 `appendProtectedTools` 中移除 `injectExtendedSubAgentResults`。删除 `lib/messages/inject/subagent-results.ts`(82 行)和 `lib/subagents/subagent-results.ts`(74 行)。从 `SessionState` 中移除 `subAgentResultCache` 字段。重写是冗余的:OpenCode 原生在 `task` 调用完成后立即追加一条 `state="completed"` 消息(含完整子代理结果)。`experimental.allowSubAgents` 仍然控制 ACP 是否在子代理会话中运行 —— 只是移除了父历史重写。经双 Agent 审查(均 APPROVE)。
451
+
452
+ 文件:`lib/hooks.ts`、`lib/compress/protected-content.ts`、`lib/compress/{message,range}.ts`、`lib/state/{state,types}.ts`、`lib/messages/index.ts`、`AGENTS.md`。851 项测试通过。
453
+
446
454
  ### v1.13.8-dev.1 — Dev 预发布同步(master @ v1.13.7)
447
455
 
448
456
  **目的**:将 npm `dev` 标签同步到 v1.13.7 稳定版。内容与 v1.13.7 完全相同 —— 无新代码变更。使 `opencode-acp@dev` 与 `opencode-acp@latest`(1.13.7)保持一致。
package/dist/index.js CHANGED
@@ -2851,64 +2851,6 @@ function isToolNameProtected(toolName, patterns) {
2851
2851
  return globPatterns.some((pattern) => matchesGlob(toolName, pattern));
2852
2852
  }
2853
2853
 
2854
- // lib/subagents/subagent-results.ts
2855
- var SUB_AGENT_RESULT_BLOCK_REGEX = /(<task_result>\s*)([\s\S]*?)(\s*<\/task_result>)/i;
2856
- function getSubAgentId(part) {
2857
- const sessionId = part?.state?.metadata?.sessionId;
2858
- if (typeof sessionId !== "string") {
2859
- return null;
2860
- }
2861
- const value = sessionId.trim();
2862
- return value.length > 0 ? value : null;
2863
- }
2864
- function buildSubagentResultText(messages) {
2865
- const assistantMessages = messages.filter((message) => message.info.role === "assistant");
2866
- if (assistantMessages.length === 0) {
2867
- return "";
2868
- }
2869
- const lastAssistant = assistantMessages[assistantMessages.length - 1];
2870
- const lastText = getLastTextPart(lastAssistant);
2871
- if (assistantMessages.length < 2) {
2872
- return lastText;
2873
- }
2874
- const secondToLastAssistant = assistantMessages[assistantMessages.length - 2];
2875
- if (!assistantMessageHasCompressTool(secondToLastAssistant)) {
2876
- return lastText;
2877
- }
2878
- const secondToLastText = getLastTextPart(secondToLastAssistant);
2879
- return [secondToLastText, lastText].filter((text) => text.length > 0).join("\n\n");
2880
- }
2881
- function mergeSubagentResult(output, subAgentResultText) {
2882
- if (!subAgentResultText || typeof output !== "string") {
2883
- return output;
2884
- }
2885
- return output.replace(
2886
- SUB_AGENT_RESULT_BLOCK_REGEX,
2887
- (_match, openTag, _body, closeTag) => `${openTag}${subAgentResultText}${closeTag}`
2888
- );
2889
- }
2890
- function getLastTextPart(message) {
2891
- const parts = Array.isArray(message.parts) ? message.parts : [];
2892
- for (let index = parts.length - 1; index >= 0; index--) {
2893
- const part = parts[index];
2894
- if (part.type !== "text" || typeof part.text !== "string") {
2895
- continue;
2896
- }
2897
- const text = part.text.trim();
2898
- if (!text) {
2899
- continue;
2900
- }
2901
- return text;
2902
- }
2903
- return "";
2904
- }
2905
- function assistantMessageHasCompressTool(message) {
2906
- const parts = Array.isArray(message.parts) ? message.parts : [];
2907
- return parts.some(
2908
- (part) => part.type === "tool" && part.tool === "compress" && part.state?.status === "completed"
2909
- );
2910
- }
2911
-
2912
2854
  // lib/compress/protected-content.ts
2913
2855
  function appendProtectedUserMessages(summary, selection, searchContext, state, enabled) {
2914
2856
  if (!enabled) return summary;
@@ -2975,7 +2917,7 @@ function extractProtectedPromptInfo(text) {
2975
2917
  }
2976
2918
  return protectedTexts;
2977
2919
  }
2978
- async function appendProtectedTools(client, state, allowSubAgents, summary, selection, searchContext, protectedTools, protectedFilePatterns = []) {
2920
+ async function appendProtectedTools(client, state, summary, selection, searchContext, protectedTools, protectedFilePatterns = []) {
2979
2921
  const protectedOutputs = [];
2980
2922
  for (const messageId of selection.messageIds) {
2981
2923
  const existingCompressionEntry = state.prune.messages.byMessageId.get(messageId);
@@ -3000,38 +2942,6 @@ async function appendProtectedTools(client, state, allowSubAgents, summary, sele
3000
2942
  if (part.state?.status === "completed" && part.state?.output) {
3001
2943
  output = typeof part.state.output === "string" ? part.state.output : JSON.stringify(part.state.output);
3002
2944
  }
3003
- if (allowSubAgents && part.tool === "task" && part.state?.status === "completed" && typeof part.state?.output === "string") {
3004
- const cachedSubAgentResult = state.subAgentResultCache.get(part.callID);
3005
- if (cachedSubAgentResult !== void 0) {
3006
- if (cachedSubAgentResult) {
3007
- output = mergeSubagentResult(
3008
- part.state.output,
3009
- cachedSubAgentResult
3010
- );
3011
- }
3012
- } else {
3013
- const subAgentSessionId = getSubAgentId(part);
3014
- if (subAgentSessionId) {
3015
- let subAgentResultText = "";
3016
- try {
3017
- const subAgentMessages = await fetchSessionMessages(
3018
- client,
3019
- subAgentSessionId
3020
- );
3021
- subAgentResultText = buildSubagentResultText(subAgentMessages);
3022
- } catch {
3023
- subAgentResultText = "";
3024
- }
3025
- if (subAgentResultText) {
3026
- state.subAgentResultCache.set(part.callID, subAgentResultText);
3027
- output = mergeSubagentResult(
3028
- part.state.output,
3029
- subAgentResultText
3030
- );
3031
- }
3032
- }
3033
- }
3034
- }
3035
2945
  if (output) {
3036
2946
  protectedOutputs.push(`
3037
2947
  ### ${title}
@@ -4487,7 +4397,6 @@ function createSessionState() {
4487
4397
  pendingByCallId: /* @__PURE__ */ new Map()
4488
4398
  },
4489
4399
  toolParameters: /* @__PURE__ */ new Map(),
4490
- subAgentResultCache: /* @__PURE__ */ new Map(),
4491
4400
  toolIdList: [],
4492
4401
  messageIds: {
4493
4402
  byRawId: /* @__PURE__ */ new Map(),
@@ -4527,7 +4436,6 @@ function resetSessionState(state) {
4527
4436
  totalPruneTokens: 0
4528
4437
  };
4529
4438
  state.toolParameters.clear();
4530
- state.subAgentResultCache.clear();
4531
4439
  state.toolIdList = [];
4532
4440
  state.messageIds = {
4533
4441
  byRawId: /* @__PURE__ */ new Map(),
@@ -6535,7 +6443,6 @@ function createCompressMessageTool(factoryCtx) {
6535
6443
  const summaryWithTools = await appendProtectedTools(
6536
6444
  ctx.client,
6537
6445
  ctx.state,
6538
- ctx.config.experimental.allowSubAgents,
6539
6446
  summaryWithPromptInfo,
6540
6447
  plan.selection,
6541
6448
  searchContext,
@@ -6780,7 +6687,6 @@ function createCompressRangeTool(factoryCtx) {
6780
6687
  const summaryWithTools = await appendProtectedTools(
6781
6688
  ctx.client,
6782
6689
  ctx.state,
6783
- ctx.config.experimental.allowSubAgents,
6784
6690
  summaryWithPromptInfo,
6785
6691
  plan.selection,
6786
6692
  searchContext,
@@ -8608,65 +8514,6 @@ var injectMessageIds = (state, config, messages, compressionPriorities) => {
8608
8514
  }
8609
8515
  };
8610
8516
 
8611
- // lib/messages/inject/subagent-results.ts
8612
- async function fetchSubAgentMessages(client, sessionId) {
8613
- const response = await client.session.messages({
8614
- path: { id: sessionId }
8615
- });
8616
- return filterMessages(response?.data || response);
8617
- }
8618
- var injectExtendedSubAgentResults = async (client, state, logger, messages, allowSubAgents) => {
8619
- if (!allowSubAgents) {
8620
- return;
8621
- }
8622
- for (const message of messages) {
8623
- const parts = Array.isArray(message.parts) ? message.parts : [];
8624
- for (const part of parts) {
8625
- if (part.type !== "tool" || part.tool !== "task" || !part.callID) {
8626
- continue;
8627
- }
8628
- if (state.prune.tools.has(part.callID)) {
8629
- continue;
8630
- }
8631
- if (part.state?.status !== "completed" || typeof part.state.output !== "string") {
8632
- continue;
8633
- }
8634
- const cachedResult = state.subAgentResultCache.get(part.callID);
8635
- if (cachedResult !== void 0) {
8636
- if (cachedResult) {
8637
- part.state.output = stripHallucinationsFromString(
8638
- mergeSubagentResult(part.state.output, cachedResult)
8639
- );
8640
- }
8641
- continue;
8642
- }
8643
- const subAgentSessionId = getSubAgentId(part);
8644
- if (!subAgentSessionId) {
8645
- continue;
8646
- }
8647
- let subAgentMessages = [];
8648
- try {
8649
- subAgentMessages = await fetchSubAgentMessages(client, subAgentSessionId);
8650
- } catch (error) {
8651
- logger.warn("Failed to fetch subagent session for output expansion", {
8652
- subAgentSessionId,
8653
- callID: part.callID,
8654
- error: error instanceof Error ? error.message : String(error)
8655
- });
8656
- continue;
8657
- }
8658
- const subAgentResultText = buildSubagentResultText(subAgentMessages);
8659
- if (!subAgentResultText) {
8660
- continue;
8661
- }
8662
- state.subAgentResultCache.set(part.callID, subAgentResultText);
8663
- part.state.output = stripHallucinationsFromString(
8664
- mergeSubagentResult(part.state.output, subAgentResultText)
8665
- );
8666
- }
8667
- }
8668
- };
8669
-
8670
8517
  // lib/messages/reasoning-strip.ts
8671
8518
  function stripStaleMetadata(messages) {
8672
8519
  const lastUserMessage = getLastUserMessage(messages);
@@ -11777,13 +11624,6 @@ function createChatMessageTransformHandler(client, registry3, logger, config, pr
11777
11624
  const prePruneTokens = getCurrentTokenUsage(state, output.messages);
11778
11625
  prune(state, logger, config, output.messages);
11779
11626
  assignMessageRefs(state, output.messages);
11780
- await injectExtendedSubAgentResults(
11781
- client,
11782
- state,
11783
- logger,
11784
- output.messages,
11785
- config.experimental.allowSubAgents
11786
- );
11787
11627
  const compressionPriorities = buildPriorityMap(config, state, output.messages);
11788
11628
  prompts.reload();
11789
11629
  injectCompressNudges(