Skip to content

feat: support opencode v2 (dual V1+V2) - #99

Merged
BacLuc merged 5 commits into
BacLuc:mainfrom
bacluc-agent:issue-390-migrate-opencode-v2
Oct 15, 2026
Merged

BacLuc merged 5 commits into
BacLuc:mainfrom
bacluc-agent:issue-390-migrate-opencode-v2

Conversation

@bacluc-agent

@bacluc-agent bacluc-agent commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Fixes https://github.com/bacluc-agent/agent-todo/issues/390

Management summary

Adds opencode V2 support while keeping V1 working, and fixes two correctness bugs on the V2 headless path.

Bugs fixed in this revision:

  • Default command never registered on headless opencode run (blocker). v2.0.29 never delivers session.created to plugin subscribers on opencode run (first real session event is session.inbox.enqueued), so the store stayed empty at session.execution.succeeded and the check never ran. src/v2.ts now resolves the default command lazily, once per session, on the first event carrying a data.sessionID (directory precedence: event location → event data location → ctx.location.directory), never clobbering an explicitly registered command, and guarded once-only before the first await. Decisive evidence: the new regression tests fail on the pre-fix source — npm test, step 6.
  • Wrong interrupt reasons re-prompted mid-conversation. The guard skipped only shutdown, so superseded and inactivity interruptions ran the check and re-prompted mid-conversation; the check now runs on interrupt only for reason === 'user'.

Carried-over fixes from earlier revisions are in the fold below.

No dependency changes; package.json / lockfile untouched.

Verification evidence

Both cited runs are manually dispatched workflow_dispatch runs, not the automatic push/pull_request runs.

Verifying run 38655090646 (job 116162843147) is completed / success on 962d0a6.

Action runs

Run Model What happened Result Failure reason
https://github.com/bacluc-agent-org/agent-runner/actions/runs/38643600614 opencode/big-pickle This run: V2 config, event-stream and correctness fixes; pushed dea4b5d, 962d0a6 in progress —
https://github.com/bacluc-agent/opencode-completion-check-command/actions/runs/38655090646 manual workflow_dispatch Target ci.yaml on 962d0a6: typecheck, 116 tests, format check success —
https://github.com/bacluc-agent/opencode-completion-check-command/actions/runs/38655245991 manual workflow_dispatch Target ci.yaml on pre-fix source + new tests (throwaway branch, deleted) failure expected: the 4 new regression tests
https://github.com/bacluc-agent/opencode-completion-check-command/actions/runs/38651104106 manual workflow_dispatch Target ci.yaml on dea4b5d: typecheck, 112 tests, format check success superseded by this revision
https://github.com/bacluc-agent/opencode-completion-check-command/actions/runs/38648775966 manual workflow_dispatch Earlier dispatch on cd2a127 success superseded by the config-key fix
https://github.com/bacluc-agent-org/agent-runner/actions/runs/38639226682 opencode/big-pickle branch agent-run/390-38639226682 failed coordinator exit failure
Fixes carried over from earlier revisions and design decisions

Fixes from earlier revisions on this branch:

  • Config keys (blocker, fixed in dea4b5d): opencode.json and the README V2 snippet use the V2-native plural plugins key with { package, options } objects; the V1 snippet keeps the legacy singular plugin tuple. The binary reads config.plugins / config.commands, so the plural keys are the only ones that keep the object/commands shape.
  • V2 command-override limitation (fixed in dea4b5d): the sentence names the V2-native commands key, not the legacy singular command.
  • Event stream (fixed in dea4b5d): src/v2.ts handles events with void handleCompletionCheckEvent(...).catch(...) so a running executeCommand no longer serializes the plugin event stream. Errors are still caught and logged, and the per-session processing guard is meaningful (synchronous add/has before the first await).

Design decisions:

  • Dual module in src/server.ts: spreads Plugin.define({ id, setup }) (V2) with server: CompletionCheckCommandPlugin (V1), so one entrypoint serves both majors; @opencode/plugin@2.0.29 added, @opencode-ai/plugin@1.18.33 kept for V1.
  • Trigger events are session.execution.succeeded/.failed/.interrupted — the session-terminal events v2.0.29 actually publishes (session.idle/session.status are defined but never published).
  • opencode.json omits a command.completion-check-command block because a same-named config command replaces the plugin registration and loses the capture step.
  • Delivery target: BacLuc/opencode-completion-check-command is owned by BacLuc, so the outsider fork/PR policy does not apply. The branch lives in the bacluc-agent fork (only the fork has push) and the PR targets upstream main, matching the repo's own convention.
  • Earlier review findings addressed on this branch: singular plugin key with object form → plugins; README V2 snippet → plugins, V1 snippet unchanged; README override sentence now names commands; the event handler is fire-and-forget so processing is a real guard; the verification section cites self-triggered workflow_dispatch runs.
Out of scope

Upstream doc bug, not this PR: services/www/src/docs/content/build/plugins/migrate-v1.mdx shows a V2 example using session.idle, which v2.0.29 never publishes.

opencode V2 core publishes session.execution.succeeded|failed|interrupted
at the end of a turn (packages/core/src/session/execution.ts). session.idle
and session.status are only defined in @opencode/schema and are never
published at runtime, so triggering on them made the v2 completion check
dead code.

- Run the check on session.execution.succeeded, .failed and .interrupted,
  skipping interrupted with reason "shutdown".
- Detect the usage limit from session.execution.failed's structured error
  instead of re-reading the session context.
- Drop the redundant user-facing commands.completion-check-command from the
  dev opencode.json: config commands are registered after external plugins
  and would override the plugin's own command, losing the command capture.
- Document the v2 command override limitation in the README.

Tests updated to cover the new trigger and the shutdown/usage-limit cases.
@bacluc-agent

Copy link
Copy Markdown
Contributor Author

Review verdict: request changes — verified against the installed opencode v2.0.29 and its source.

Read: agent-runner/AGENTS.md, /tmp/occc/AGENTS.md, /tmp/occc/README.md.

Blocker — config keys (commit cd2a127 is backwards)

  • opencode.json now uses "plugin": [{ "package": ".", "options": {} }]. In v2.0.29 the singular plugin key is the V1-legacy key and accepts only a string or a [package, options] tuple; the object form is invalid and is silently dropped. Empirically with opencode debug config: singular+object → plugins: []; plugins+object → kept; plugin+tuple → kept.
  • Source: packages/core/src/config/normalize.ts:185-190; packages/schema/src/config/plugin.ts (Plugin = Union([String, Entry])); packages/core/src/v1/config/plugin.ts (Spec = Union([String, Tuple([String, Options])])).
  • The pre-cd2a127 "plugins" value was correct — this commit regresses the repo's own dev config so the plugin no longer loads.
  • Minimal fix: "plugins": [{ "package": ".", "options": {} }].
  • README V2 snippet (README.md:~49) has the same bug and must stay plural. The changed limitation sentence (README.md:~78) swapping commands→command also names the legacy key; the V2-native key is commands.
  • PR text claim "opencode v2.0.29 honours the singular plugin/command keys" is false for the object shape.

Blocker — test evidence

  • Linked run bacluc-agent/opencode-completion-check-command run 38648775966 is the repo's own ci.yaml, which fires on push/pull_request (automatic CI, visible in commit status) — per AGENTS.md it must not be cited as own testing. Step anchors #step:4..7 do exist and match, but it is automatic-CI-only, and the description itself notes no V2 runtime path is exercised ("no opencode V2 server runs in CI").
  • Section heading is ## Verification evidence, not ## Test evidence.

Non-blocking

  • src/v2.ts processing Set (27, 54-55, 82) is dead: the for await … await handleCompletionCheckEvent(...) loop (112-114) serializes handlers, so processing.has() can never be true. Same await also blocks the whole plugin event stream for all sessions while executeCommand runs (V1 hook was fire-and-forget). If concurrency is intended, use void handleCompletionCheckEvent(...), which also makes processing meaningful.
  • PR body calls the run "Target-repo own CI"; it lives in the bacluc-agent fork (imprecise, not wrong).

Verified correct — no change needed

  • V2 trigger claim in 48c2ecd holds: session.execution.succeeded|failed|interrupted are published (packages/core/src/session/execution.ts); session.status/session.idle are defined but never published in v2.0.29 (packages/schema/src/session-status-event.ts, Idle marked // deprecated).
  • src/server.ts dual export satisfies V2 Module (id+setup) and V1 PluginModule ({id, server}; base already used this shape). Caveat: V1 loading could not be exercised here (only v2.0.29 installed), so V1 is unverified in this environment.
  • isUsageLimitError generalization + the 4 new V2 rows (tests/plugin.test.ts:518-556) are sound; dropping the name === 'APIError' guard is fine (429 = rate/usage limit).
  • Local: 112 tests pass, tsc --noEmit clean, prettier --check . clean.

Out of scope (upstream doc bug, not this PR)

services/www/src/docs/content/build/plugins/migrate-v1.mdx:305 shows a V2 example using session.idle, which v2.0.29 never publishes — worth a separate upstream issue.

opencode v2.0.29 reads config.plugins (plural) and config.commands
(plural); the singular keys are the V1-legacy forms and silently drop
the object/tuple entries. Revert the singular dev config and README V2
snippet accordingly, and name commands in the V2 override limitation.

Handle plugin events without awaiting the handler so a running
completion check no longer serializes the whole event stream for all
sessions; errors are still caught and logged and the per-session
processing guard remains effective.
@BacLuc BacLuc added bump:minor enhancement New feature or request labels Oct 15, 2026
@BacLuc
BacLuc merged commit 31cf2aa into BacLuc:main Oct 15, 2026
2 of 4 checks passed
@BacLuc
BacLuc deleted the issue-390-migrate-opencode-v2 branch October 15, 2026 06:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bump:minor enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants