Repository navigation
fix(opencode): 0.28.1 — remote URL toasts on OpenCode 1, one turn per feedback, notice ordering - #1697
Conversation
On the embedded runtime /plannotator-review, /plannotator-annotate and /plannotator-last only wrote the session URL to app.log (OpenCode's log file) and stderr, so a remote/SSH reviewer never saw it. Route their ready hooks through the same toast plan review uses, now a shared announceSessionUrl helper, in local and remote mode alike. Co-Authored-By: Claude <noreply@anthropic.com>
OpenCode 1 always runs a model turn for a command's own message after command.execute.before returns, and the hook cannot ask it not to. The plugin emptied that message and sent the feedback as a separate session.prompt, so the feedback ran one turn and the empty command message ran a second, in which the model answered the same review again. Record the feedback the handlers would have prompted with and put it in the command's own parts instead, so OpenCode runs exactly one turn for it. The agent the feedback names (the review UI's agent switch, or the writer of an annotated message, #1612) is applied to that message in chat.message, together with that agent's configured model, as the separate prompt did. Co-Authored-By: Claude <noreply@anthropic.com>
…n OpenCode 2 The Plannotator session ready notice is a pending steer, and steers are promoted at the next step boundary. During plan review that boundary is right after the submit_plan tool result, so the next request ended with the notice and a model could answer the URL line instead of the decision (seen live on 2.0.22). The plugin API cannot withdraw a pending inbox row, but every model request passes through the session context hook, so the notices are dropped there for every workflow. A lone trailing notice after the model's own reply is kept so a request never ends on an assistant message. Co-Authored-By: Claude <noreply@anthropic.com>
bun install in this monorepo runs workspace postinstalls, and the OpenCode plugin's copied its command stubs and knowledge skill into the developer's real ~/.config/opencode. Run it only when the package sits under a node_modules directory, which is where npm and pnpm install it for end users; anywhere else it exits 0 without writing. Co-Authored-By: Claude <noreply@anthropic.com>
…prompt did Moving the feedback into the command's own message must change only the turn count. The retarget now reproduces what createUserMessage did for the old separate prompt (agent named or not, no model, no variant): - agent: the named one, else OpenCode's default agent (default_agent, else the first primary non-hidden agent) instead of the command's agent; - model: the agent's configured model, else the session's model read before the command's message exists, also when the routed agent is the command's own agent (the TUI-picked model no longer wins); - variant: the agent's variant when its model offers it (/config/providers), never the TUI's. Tests pin new-vs-old equivalence against a transcription of createUserMessage for the review agent switch, the #1612 writer, an agent without a model, no agent, default_agent, and the routed agent being the current one. Co-Authored-By: Claude <noreply@anthropic.com>
OpenCode 1.18.34 also runs the V2 server.js setup, with a context that has no session or tool domain, so the planning context hook (and the submit_plan tool transform) threw during setup. Probe both like the notice filter does. Co-Authored-By: Claude <noreply@anthropic.com>
|
Review follow-ups, pushed as two commits:
Results:
|
…order - OpenCode 1.1.31 to 1.3.17 keep a user message's variant top-level (info.variant, the TUI pick for a command). The retarget now replaces or deletes that key instead of writing model.variant, and picks the variant by the running version's rule (scanned over every v1.x tag): none before 1.1.54, the agent's variant when the message's model offers it up to 1.3.13, and only on the agent's own model from 1.3.14. The version comes from /global/health when the client has it, else the session's. - With no default_agent, OpenCode takes the first visible primary agent in its registration order (build, plan, general, explore, then config agents), not GET /agent's name order. Prefer that order. Co-Authored-By: Claude <noreply@anthropic.com>
|
Re-review fixes, pushed as bcef447:
Results: |
…rsion from the session OpenCode 1.1.49 to 1.1.53 applied the agent's variant whenever the message ran on the agent's own configured model, without checking that the model offers it; agentVariantRule returned "none" there. Add that band, with tests at 1.1.49 and 1.1.53. OpenCode 1 plugins get the v1 SDK, whose global domain has only event(), so the /global/health probe could never answer: drop it and say plainly that the version is the session's own. Co-Authored-By: Claude <noreply@anthropic.com>
|
Final fix, pushed as 6be125c:
Results: |
…script shell can't run case/esac and it broke Windows dependency installs
|
Note: the postinstall monorepo guard (fix 4) was dropped before merge (commit a723641): Bun's Windows script shell can't run |
Four OpenCode bugs found by the 0.28.0 smoke tests. None of them are regressions. There is one commit per fix.
1. OpenCode 1: slash commands now show the session URL (remote mode)
On the embedded runtime,
/plannotator-review,/plannotator-annotateand/plannotator-lastwrote the URL only toapp.log(OpenCode's log file) and stderr. A remote or SSH reviewer never saw it. Plan review already showed the URL in a toast. The three commands now use the same toast, through a shared helper (announceSessionUrlinsession-url.ts). As with plan review, the toast shows in local and remote mode. The CLI runtime already showed a toast and is unchanged. OpenCode 2 already shows the URL as a transcript notice and is unchanged.Tests:
commands.test.ts, "embedded commands toast the session URL". The test calls each command's ready hook in remote mode. All three cases fail without the fix.Live (1.18.34,
PLANNOTATOR_REMOTE=1): for annotate, last and review, the/eventstream carriestui.toast.show "Open annotation UI: http://localhost:19432"and"Open code review: …".2. OpenCode 1: one model turn per command, not two
After
command.execute.before, OpenCode 1 always runs a turn on the command's own message, and the hook cannot stop it (SessionPrompt.command@ v1.18.32). The plugin emptied that message and sent the feedback as a separatesession.prompt. The result was two turns: one for the feedback, then one on the empty message, where the model answered the same review again.The command's own message now carries the feedback:
session.promptto the invoking session is recorded, not sent (command-turn.ts).output.parts, so OpenCode runs exactly one turn.chat.message, together with that agent's configured model, as the separate prompt did.Tests:
command-turn.test.tscovers recording, pass-through and retargeting.command-interception.test.tsruns a fake CLI end to end: the parts carry the feedback,session.promptis never called, andchat.messageretargets once. It fails without the fix.Live (1.18.34, fake model): with the fix, annotate, review and last each make exactly 1 model request after the feedback, in local, remote and
runtime: "cli". The 0.28.0 smoke made 2. The transcript shows one user message with the feedback and one reply, with no empty message./plannotator-laston a message written byplan, run while the command defaulted tobuild, was answered byplan(agent: "plan",mode: "plan").Known limit: a command that ends with nothing to send (Close, or Done without notes) still runs one turn on an empty message, as before. OpenCode 1 gives a command hook no way to skip that turn.
3. OpenCode 2: the URL notice is no longer the last thing the model reads after a plan decision
The
Plannotator session ready: <url>notice is a pending steer (#1515). Pending steers are promoted at the next step boundary, and during plan review that boundary comes right after thesubmit_plantool result. In the 0.28.0 smoke the model answeredECHO: Plannotator session ready: …instead of the denial. The plugin API at 2.0.22 has no inbox member (SessionDomainis a fixed Pick), so the plugin cannot cancel the pending row. Every model request does pass through thecontexthook, so the plugin now drops these notices there, for every workflow (dropSessionUrlNotices). The transcript row and its description stay visible to the user. One exception: when a notice is the only message after the model's own reply, it is kept, so a request never ends on an assistant message. The hook is registered only whenctx.session.hookexists. OpenCode 1.18 also runs this setup, with a context that has no session hooks.Tests:
server.test.ts, "session-URL notices stay out of the model context". This covers the unit cases and checks that the plugin filters requests inplan-agentandmanual. The plugin cases fail without the fix.Live (2.0.22, fake model): after a denial the request ends
[tool: YOUR PLAN WAS NOT APPROVED…, user: <plan-mode reminder>]with no notice. Approval works the same way. The synthetic notice row is still in the transcript. A native/plannotator-annotatestill runs one turn, with the feedback last and the notice still shown to the user.4. Dev environment:
bun installno longer writes into the real~/.config/opencodeA workspace
bun installran the plugin'spostinstall, which copied the command stubs and the skill into the developer's real config. The script now exits 0 unless it runs from a path undernode_modules, where npm and pnpm put an installed package. OpenCode itself installs plugins without running scripts, and the install script writes the stubs.Tests:
package-boundary.test.tsruns the real script with a sandboxedHOME/XDG_CONFIG_HOME, from a workspace layout and from anode_moduleslayout. It fails without the fix.Live:
bun run --cwd apps/opencode-plugin postinstallleft~/.config/opencodemtimes unchanged. From the installed package in the smoke cache, the same script still wrote the stubs and the skill into a sandbox.Checks
bun test apps/opencode-plugin: 242 passbun run typecheck: cleanbun run --cwd apps/review build && bun run build:hook && bun run build:opencode: okLive runs used OpenCode 1.18.34 and 2.0.22 from temp prefixes, a fake OpenAI-compatible model and a throwaway registry serving this branch packed as
0.28.1-dev. Every run used a sandboxed HOME, XDG andPLANNOTATOR_DATA_DIR.Side observation, not changed here: OpenCode 1.18.34 also runs the V2
server.jssetup, with a partial context.🤖 Generated with Claude Code