fix(plugin): run the host metadata effect for custom tool context.metadata (REPL-27972)
Custom tools loaded through the plugin registry could never update their tool part's metadata mid-run: context.metadata() silently did nothing. This bridges it the same way ask already is, so running-state metadata (e.g. the Replo publish tool's pipeline phase markers) reaches part state and the event stream.
Root cause
The host Tool.Context.metadata returns a lazy Effect (tool/tool.ts), while the plugin-facing ToolContext.metadata is typed (input) => void (plugin/src/tool.ts). fromPlugin spreads the host context and bridges only ask through the EffectBridge โ so a plain-JS plugin tool calling context.metadata(...) receives the Effect-returning host function under a void signature, constructs the Effect, and drops it. It typechecks (the void return swallows the Effect object) and never runs, so processor.updateToolCall never fires and nothing reaches part state or message.part.updated events. Built-in tools are unaffected (they run inside the Effect runtime and yield* it).
Fix
One line in fromPlugin, mirroring the existing ask bridge:
metadata: (input) => {
void bridge.promise(toolCtx.metadata(input)).catch(() => {})
},
Fire-and-forget to match the plugin signature; failures are swallowed because part-metadata updates are display-only and the host logs its own update errors.
Test
New case in test/tool/registry.test.ts using the existing custom-tool fixture pattern: a .opencode/tools tool calls context.metadata({ metadata: { phase: 'build' } }) during execute, and the host context records whether its metadata Effect actually ran. Fails on dev (recorded calls: []), passes with the fix. Full registry suite: 13/13.
Validation
Verified end to end on a live Replo dev sandbox by hot-swapping a build of this branch: the publish tool's phase markers arrived as message.part.updated running-state events with 1โ7ms latency against the tool's own phase boundaries (previously: never), through all four phases including sub-second ones, and the downstream coordinator consumer picked them up unchanged. Context: replohq/andytown#22799 (REPL-27972), which ships the coordinator consumer for these events; per review it carries no interim transport, so sandboxes on older opencode builds degrade to time-based progress until this fix rolls out.
๐ค Generated with Claude Code
Summary by cubic
Run the host metadata effect when custom plugin tools call context.metadata() so running-state metadata reaches part state and message.part.updated events. Addresses REPL-27972 and restores live phase markers in the publish tool.
-
Bug Fixes
- Cause: host
Tool.Context.metadatareturns a lazy Effect; pluginToolContext.metadataisvoid, so plugins constructed and dropped the Effect. - Fix: bridge
metadatavia the Effect bridge likeask, run fire-and-forget, swallow errors. - Result: custom tools can update tool-part metadata mid-run; test now polls for the metadata call to avoid race conditions.
- Cause: host
-
Dependencies
- Reverted incidental
ghostty-weblockfile bump.
- Reverted incidental