You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Plugin tools should respect metadata title in TUI display #18585
Plugin tools always display as toolname [args] or toolname JSON.stringify(input) in the TUI, even when the tool sets a custom title via context.metadata({ title: "..." }).
Expected Behavior
When a plugin tool calls context.metadata({ title: "custom title" }), the TUI should display the custom title instead of the raw tool name + args.
For example, a go-task plugin tool named task_run that sets:
context.metadata({title: "test"})
Should display as:
task_run test
Instead of:
task_run [task=test]
Or ideally, allow the title to fully replace the display:
task test
Current Behavior
The TUI ignores the state.title set by context.metadata() and always shows the tool name + JSON-stringified input args.
Context
Looking at the source:
packages/opencode/src/cli/cmd/run.ts has a fallback() function that checks state.title
However, this appears to be for CLI mode, not the interactive TUI
Built-in tools like bash, read, glob have custom rendering that shows clean output
Plugin tools fall through to generic rendering
Proposed Solution
In the TUI, check state.title for plugin tools and use it when present
Consider allowing plugins to fully override the display (just show title, not toolname title)
Environment
OpenCode version: 1.2.27
Platform: Linux
Use Case
Building a go-task plugin that wraps task commands. Want task_run to display as task test rather than task_run [task=test] for a cleaner UX that matches how the underlying command looks.
This issue might be a duplicate of existing issues. Please check:
fromPlugin wrapper discards custom metadata set by plugin tools via context.metadata() #12527: fromPlugin wrapper discards custom metadata (including title) set by plugin tools via context.metadata() — this covers the same root cause where the fromPlugin wrapper hardcodes the returned metadata, discarding any title set during execution, which explains why the TUI never shows the custom title.
Additional context: there's an asymmetry in how state.title is consumed between the parent session view and the subagent's own view.
The Task component in the TUI (packages/opencode/src/cli/cmd/tui/routes/session/index.tsx) does read state.title from the subagent's tool parts and renders it as a live subtitle:
But if you navigate into the subagent session, the same ci_watch tool part renders via GenericTool → InlineTool, which ignores state.title entirely and just shows the static input args:
So the data is there and flowing correctly — context.metadata({ title }) writes to state.title, the message.part.updated event fires, and the reactive store updates. The Task component reads it. But GenericTool/InlineTool don't.
The fix would be in GenericTool (or InlineTool more broadly) to check for state.title on running parts and display it — either replacing or supplementing the raw input display.
Hit this same wall building a workflow orchestrator plugin that fans out subagents via session.create({ parentID }). Confirming it's broader than the title — the whole renderer is gated on the tool name, in both frontends:
TUI (cli/cmd/tui/feature-plugins/system/session-v2.tsx): a <Switch> on props.part.name (task → the rich <Task> card, everything else → <GenericTool>), and GenericTool reads neither state.title nor state.metadata.
Web (packages/ui/src/components/message-part.tsx): ToolRegistry.render(part().tool) plus three if (part().tool !== "task") return guards.
Since a plugin tool's name is fixed to its Hooks.tool key, nothing it emits can reach the task renderer. ctx.metadata already reaches the running part (session/tools.ts:50-63), so this is purely a renderer-selection gate, not a data gap.
Minimal fix that also resolves #20184: select the subagent renderer by a metadata marker rather than the literal name — route any tool part whose state.metadata carries sessionId (or kind:"subagent") to the existing task widget, with task setting the marker itself. No new render API, task behaviour unchanged, and any plugin opts in via the existing ctx.metadata channel. (A sessionIds[] variant would let one part represent a fan-out of several children.)
Activity
github-actions commentedon Mar 22, 2026
This issue might be a duplicate of existing issues. Please check:
fromPluginwrapper discards custom metadata set by plugin tools viacontext.metadata()#12527:fromPluginwrapper discards custom metadata (includingtitle) set by plugin tools viacontext.metadata()— this covers the same root cause where thefromPluginwrapper hardcodes the returned metadata, discarding any title set during execution, which explains why the TUI never shows the custom title.gotgenes commentedon Mar 30, 2026
Additional context: there's an asymmetry in how
state.titleis consumed between the parent session view and the subagent's own view.The
Taskcomponent in the TUI (packages/opencode/src/cli/cmd/tui/routes/session/index.tsx) does readstate.titlefrom the subagent's tool parts and renders it as a live subtitle:But if you navigate into the subagent session, the same
ci_watchtool part renders viaGenericTool→InlineTool, which ignoresstate.titleentirely and just shows the static input args:So the data is there and flowing correctly —
context.metadata({ title })writes tostate.title, themessage.part.updatedevent fires, and the reactive store updates. TheTaskcomponent reads it. ButGenericTool/InlineTooldon't.The fix would be in
GenericTool(orInlineToolmore broadly) to check forstate.titleon running parts and display it — either replacing or supplementing the raw input display.malhashemi commentedon May 29, 2026
Hit this same wall building a
workfloworchestrator plugin that fans out subagents viasession.create({ parentID }). Confirming it's broader than the title — the whole renderer is gated on the tool name, in both frontends:cli/cmd/tui/feature-plugins/system/session-v2.tsx): a<Switch>onprops.part.name(task→ the rich<Task>card, everything else →<GenericTool>), andGenericToolreads neitherstate.titlenorstate.metadata.packages/ui/src/components/message-part.tsx):ToolRegistry.render(part().tool)plus threeif (part().tool !== "task") returnguards.Since a plugin tool's name is fixed to its
Hooks.toolkey, nothing it emits can reach thetaskrenderer.ctx.metadataalready reaches the running part (session/tools.ts:50-63), so this is purely a renderer-selection gate, not a data gap.Minimal fix that also resolves #20184: select the subagent renderer by a metadata marker rather than the literal name — route any tool part whose
state.metadatacarriessessionId(orkind:"subagent") to the existing task widget, withtasksetting the marker itself. No new render API,taskbehaviour unchanged, and any plugin opts in via the existingctx.metadatachannel. (AsessionIds[]variant would let one part represent a fan-out of several children.)github-actions commentedon Jul 29, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.