Commit Graph

4 Commits

Author SHA1 Message Date
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
4i3n6 b00e6b612a fix(plugin): scope OpenCode marker commands to the plugin's directory (#3554)
Follow-up to #3552, as invited there.

The OpenCode host injects the process-global Bun shell as `$` — a single
instance shared by every plugin loaded in the process. The
activity-marker plugin issued its commands unscoped, so each one ran in
whatever the process-wide cwd was at spawn time; under concurrent
parallel-agent sessions that means a marker write can land in a
directory other than the one the event came from.

This pins every command with the promise-level `.cwd(directory)`, per
the review notes on the issue:

- `directory` pulled from `PluginInput` (the factory now destructures `{
$, directory }`), and `session.deleted` scopes the same way as `set`.
- Promise-level `.cwd(directory)` only — the instance-level `$.cwd(...)`
would mutate the shared default for every plugin in the host process, so
the call-site comment documents why the per-command form is
load-bearing.

Scope note, consistent with the issue discussion: this closes the "runs
`wt` in the wrong directory" mechanism on the plugin side; it does not
claim to explain the parent-directory `rename(2)` from the original
report. The host-level question (injecting a scoped shell instead of the
global one) belongs upstream with opencode — happy to raise it there as
discussed.

Co-authored-by: 4i3n6 <4i3n6@users.noreply.github.com>
2026-07-24 19:23:36 -07:00
Axel H. a5c7fa76f1 fix(opencode): fix broken unicode escaping depending on Bun version (#1935)
A small fix on the OpenCode plugin: depending on the Bun version,
unicode can be broken ([escaped while it
shouldn't](https://github.com/bikeshaving/crank/issues/342)).

This fix ensures it works whatever the Bun version used in OpenCode (I
already use this fix locally).
2026-04-06 14:27:14 -07:00
Axel H. 4ef64c47ad feat(opencode): add OpenCode integration (activity tracking, plugin, config) (#1807)
This PR adds OpenCode integration: activity tracking markers in `wt
list`, plugin installation via CLI, `wt config show` diagnostics, and
LLM commit generation detection.

Continues the work started in #1295 and #1533 which added OpenCode to
the docs and example config.

## What's included

- **Activity tracking plugin** (`dev/opencode-plugin.ts`): maps
`session.status`/`session.idle`/`session.deleted` to branch markers,
same pattern as Claude Code's plugin
- **`wt config plugins opencode install/uninstall`**: installs the
plugin to `~/.config/opencode/plugins/worktrunk.ts` — source embedded
via `include_str!()`, no npm needed. Sits under `wt config plugins`
alongside Claude Code's `wt config plugins claude`.
- **`wt config show` OPENCODE section**: shows plugin install status
with actionable hints (only when `opencode` is on PATH)
- **`LlmTool::OpenCode` variant**: detected via PATH for commit
generation auto-config

## Docs approach

Kept deliberately low-profile — no dedicated docs page, no README
mention. OpenCode is discoverable via `wt config show` and a mention in
tips-patterns. If it becomes popular, docs prominence can increase.

> _This was written by Claude Code on behalf of @max-sixty_

---------

Co-authored-by: Maximilian Roos <m@maxroos.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-05 18:44:41 -07:00