Files
max-sixty__worktrunk/dev/opencode-plugin.ts
Worktrunk Bot 8e420cb8c4 Load the OpenCode plugin under OpenCode 2 (#4018)
## Problem

`wt config plugins opencode install` writes a plugin OpenCode 2 silently
ignores, reported in #4014. OpenCode 2's loader decodes a plugin
module's default export against a schema that accepts `{ id, effect }`
or `{ id, setup }` — a bare default-exported function, which is what
this plugin has always been, fails the decode and the load is dropped
without a message
([`packages/core/src/plugin/module.ts#L60`](https://github.com/sst/opencode/blob/b2cecc6350d377c382e1ec32ee66ec63ad68f715/packages/core/src/plugin/module.ts#L60)
on the `beta` branch, which is where the OpenCode 2 prereleases come
from — `dev` is the 1.18.x line). The plugin also ran `wt` through Bun's
`$`, which the host only supplies when it is itself running under Bun.

## Solution

One file, both runtimes: the default export is now `{ id, setup, server
}`, and `wt` is spawned with `node:child_process` instead of Bun's
shell. OpenCode 2 reads `setup` and subscribes to session events;
OpenCode 1.16+ reads `server` and gets exactly the hooks it has today.
Each version ignores the other's key, so the install path, filename, and
marker behavior are all unchanged.

Two details in the `setup` arm that differ from the port in the issue's
thread — both from reading the shipped `@opencode-ai/plugin@beta` types:
`session.status` carries `status.type ∈ {idle, busy, retry}` and
`session.idle` is annotated deprecated, so the status is read rather
than the event name; and `session.status` with an `idle` status sets 💬
rather than 🤖.

**The part worth a maintainer's call**: this keeps the OpenCode 1 shape
rather than switching cleanly to v2 alone. OpenCode 2 is still
prerelease (`npm dist-tags` for `@opencode-ai/plugin`: `latest` 1.18.29,
`beta` 0.0.0-beta-19192), so a v2-only file would make every current
stable install fail with `Plugin … must default export an object with
server()`. The cost is the `server` arm plus its two local types — say
the word and I will cut it to `{ id, setup }`. Either way OpenCode below
1.14.19 loses the plugin: that is where `readV1Plugin` — the function
that admits an object-shaped default export — first appears, and older
releases accept only the bare function. 1.16 is the floor worth
documenting, though, because it is where the host began filtering events
to each plugin instance's directory
([v1.16.0](https://github.com/sst/opencode/blob/6cb74317a6efacd656483cb0489d8e7e3701c12e/packages/opencode/src/plugin/index.ts#L258-L259));
[v1.15.0](https://github.com/sst/opencode/blob/2662a4f955e563fd22cd5c4873ca350d21745275/packages/opencode/src/plugin/index.ts#L246)
fans every bus event to every instance, so on 1.14.19–1.15.x the marker
follows the worktree the instance was created for rather than the
session that is active — the same behavior today's plugin has, not a
regression.

## Verification

`cargo test --test integration test_docs_are_in_sync`, the twelve
`opencode` integration tests, and `packaged_assets` pass. Beyond that I
ran the new file through both loaders' own logic under `node
--experimental-strip-types`, with a stub `wt` on `PATH` recording its
arguments.

<details><summary>Loader checks and recorded marker calls</summary>

Decoding the real module with the exact schema OpenCode 2 uses
(`effect@4.0.0-rc.112`, the version its plugin package depends on), and
with OpenCode 1's
[`readV1Plugin`](https://github.com/sst/opencode/blob/337fd144d2ba144743368f78d9579a99cce175bd/packages/opencode/src/plugin/shared.ts#L272)
detect-mode logic:

```
OpenCode 2 module decode : ok, id=worktrunk
OpenCode 1 readV1Plugin  : ok, id=worktrunk
v2 setup returned cleanup: true
v2 setup on a 1.18 context: bails, no throw
```

The last line covers a case worth calling out: OpenCode 1.18 also
carries a `{ id, setup }` loader, but the context it passes has neither
`location` nor `event`. `setup` returns early there instead of throwing,
and `server` drives the marker.

Feeding a fake event stream, the commands the plugin actually issued:

```
v2:  set 🤖   (status busy)
     set 🤖   (status retry)
     set 💬   (status idle)
     — skipped, event location is another worktree —
     set 💬   (session.idle)
     clear    (session.deleted)
     clear    (cleanup)

v1:  set 🤖 / set 💬 / clear   (unchanged from today)
```

What I could not check from CI is the plugin running inside a real
OpenCode 2 session — @pragmaticivan, if you are still up for testing,
`wt config plugins opencode install` off this branch should behave the
same as your local port.

Install location is unchanged and confirmed on `beta`: plugins are still
discovered as `.ts`/`.js` files under `plugin/`, `plugins/` in each
config directory
([`source-directory.ts#L7`](https://github.com/sst/opencode/blob/b2cecc6350d377c382e1ec32ee66ec63ad68f715/packages/core/src/plugin/source-directory.ts#L7)).

</details>

Closes #4014

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
2026-09-06 09:19:38 -07:00

4.8 KiB