Files
max-sixty__worktrunk/dev/opencode-plugin.ts
T
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

36 lines
1.4 KiB
TypeScript

// Worktrunk activity tracking plugin for OpenCode.
//
// Tracks OpenCode session activity per branch, showing status markers in `wt list`:
// 🤖 — agent is working
// 💬 — agent is waiting for input
//
// Installed globally via: wt config plugins opencode install
// Or manually: copy to ~/.config/opencode/plugins/worktrunk.ts
import type { Plugin } from "@opencode-ai/plugin";
export default (async ({ $, directory }) => {
return {
event: async ({ event }) => {
// `$` is the host's process-global Bun shell, shared by every plugin in
// the process. The promise-level `.cwd(directory)` on each command below
// is load-bearing: it pins the command to the directory this plugin
// instance was created for. Without it, the command runs in whatever the
// process-wide cwd happens to be at spawn time. Do not "simplify" to the
// instance-level `$.cwd(...)` — that mutates the shared default for every
// plugin in the host process.
switch (event.type) {
case "session.status":
await $`wt config state marker set ${'🤖'} || true`.cwd(directory).quiet();
break;
case "session.idle":
await $`wt config state marker set ${'💬'} || true`.cwd(directory).quiet();
break;
case "session.deleted":
await $`wt config state marker clear || true`.cwd(directory).quiet();
break;
}
},
};
}) satisfies Plugin;