Skip to content

Tag injection into part.state.output corrupts tool result rendering in OpenCode UI #488

@drewington

Description

@drewington

Summary

When compress.permission: \"allow\" is set, DCP injects <dcp-message-id>mNNNN</dcp-message-id> tags into part.state.output of completed tool results (via appendToAllToolParts in lib/messages/inject/inject.ts). OpenCode's XML renderer processes tool result content using XML-like <parameter> tags. When the injected DCP tag ends up inside tool result output, the renderer partially parses it and leaves a residual fragment visible in the rendered response.

Observed artifact

At the end of assistant responses that terminate with tool calls, the following appears in the UI:

```
m0031
```

The </parameter> is OpenCode's XML renderer garbling the </dcp-message-id> closing tag from inside the tool output string.

Root cause

In lib/messages/inject/inject.ts, the injection priority for assistant messages is:

  1. appendToAllToolParts — appends tag directly into part.state.output (string)
  2. appendToLastTextPart — fallback to last text part
  3. Synthetic text part inserted before first tool

Because appendToAllToolParts succeeds when tool parts have status: \"completed\" with string output, the DCP tag is embedded in the tool result text that OpenCode's renderer subsequently processes as XML.

Environment

  • Plugin version: @tarquinen/opencode-dcp@3.1.7
  • OpenCode version: latest
  • pruneNotificationType: \"toast\" (already changed from \"chat\")

Suggested fix

Instead of injecting into part.state.output, consider:

  1. Using a separate metadata field on the part (e.g., part.metadata.dcpMessageId) that the renderer never displays
  2. Skipping tool parts entirely and always falling through to appendToLastTextPart or the synthetic text part (inserted before the first tool part), which won't be processed by the tool XML renderer
  3. Injecting into a non-rendered field of the message object itself rather than any content-bearing part

Option 2 is the minimal change: remove or bypass appendToAllToolParts in the assistant message injection path, letting the synthetic text part (inserted before the first tool) carry the ID instead. This text part is not rendered through the tool XML pipeline.

Workaround

Setting compress.permission: \"deny\" stops tag injection but also disables DCP system prompt, all /dcp CLI commands, nudges, and breaks the compress tool entirely — too destructive to be a viable workaround.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions