Skip to content

fix(v2): dual-mode default export so the bare npm ref loads under V2 - #84

Open
jss79 wants to merge 1 commit into
shekohex:mainfrom
jss79:fix/v2-dual-mode-default-export
Open

jss79 wants to merge 1 commit into
shekohex:mainfrom
jss79:fix/v2-dual-mode-default-export

Conversation

@jss79

@jss79 jss79 commented Oct 13, 2026

Copy link
Copy Markdown

Problem

Refs #67 — the plugin cannot be loaded on OpenCode V2 from its published package. There are two independent gaps:

  1. The documented opencode-pty/v2 subpath ref cannot install — the V2 loader parses subpath refs as npm install specs (NpmInstallFailedError). Loader-side; not fixable in this repo.
  2. The bare npm ref schema-fails on V2 — the . entrypoint (dist/index.js) exports only named symbols (PTYPlugin, server) and no default export at all, so the loader's bare-ref resolution fails validation: PluginModule.LoadError: Plugin must export a default definition with an id and an effect or setup function (cause: SchemaError(Missing key at ["default"])).

Since the loader resolves a bare npm ref to the . export (falling back to dist/index.js), gap 2 is fixable plugin-side — and fixing it also makes the subpath unnecessary.

Fix

Make the . entrypoint a dual-mode default export — the same pattern opencode-mem shipped in 2.27.0 (tickernelz/opencode-mem PR #311), which is published and verified working on V2:

export default {
  id: Plugin.id,        // 'opencode-pty' (from the V2 plugin)
  server: PTYPlugin,    // V1 (>= 1.18.29) resolves the object entrypoint and calls server()
  setup: Plugin.setup,  // V2 validates { id, setup } and calls setup()
}
  • The ./v2 subpath export is unchanged (stays valid for direct-import consumers).
  • No runtime behavior change on V1: object entrypoints with server are supported since 1.18.29.
  • README: the V2 example used the V1 plugin field (silently ignored by V2 — a V1-shaped array produces zero plugin loads) and recommended the un-installable subpath ref. Now documents the bare ref + the plugins object form, with notes on both failure modes.

Verification

  • bun run build:plugin + bun run typecheck + biome lint/format clean.
  • New unit test (test/dual-mode-default.test.ts) pins the default shape (id, server, setup) and the named V2 re-export.
  • Live A/B on opencode 2.0.28 (sandboxed HOME+XDG, bare npm ref resolving dist/index.js from the package cache):
    • Pre-patch dist: 3× SchemaError(Missing key at ["default"]) — plugin fails to load.
    • Patched dist: plugin listed by the server, zero plugin-load failures. Only the default-export change differs between the two runs.

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

The `.` entrypoint exported only named symbols — no default at all — so
the V2 loader's bare-ref resolution schema-failed with
`Missing key at ["default"]` (shekohex#67). The documented `opencode-pty/v2`
subpath ref cannot install either (the V2 loader parses subpath refs
as npm install specs), leaving no published-package path to load the
plugin on V2.

Add the dual-mode default export (the pattern opencode-mem shipped in
2.27.0 via PR #311): `{ id, server, setup }` — V1 (>= 1.18.29) calls
`server()`, V2 calls `setup()`. The `./v2` subpath export stays for
direct-import consumers. Correct the README's V2 example, which used
the V1 `plugin` field (silently ignored by V2) and recommended the
un-installable subpath ref.

Refs shekohex#67.
@Eric-GoodBoy-Tech

Copy link
Copy Markdown

Independent verification on 2.0.28 (third-party, isolated, non-destructive)

I reproduced this PR's fix and also checked the two things the PR description doesn't
cover: whether the tools actually register in the host, and whether a real PTY round-trips.
Short version: the fix works, and CI on this PR has never run.

Setup

  • opencode 2.0.28, macOS arm64, opencode-pty@0.4.0 from npm
  • Fully isolated: XDG_CONFIG_HOME / XDG_DATA_HOME / XDG_STATE_HOME / XDG_CACHE_HOME
    all under a temp root (verified with opencode debug paths), plus
    opencode service set port <unique> so the background service on 49374 is untouched
  • A/B at the same npm-cache path and same config — only the package contents differ:
variant load attempts failed to load plugin /api/command pty commands
npm 0.4.0 as published 1 1 (SchemaError(Missing key at ["default"])) []
0.4.0 + only this PR's dual-mode default 1 0 []
this PR's build (main + #61/#68) 1 0 ["pty-open-background-spy","pty-show-server-url"]

Reproduced with opencode run in the isolated env, reading
$XDG_DATA_HOME/opencode/log/opencode.log:

# published 0.4.0
msg="loading plugin" id=opencode-pty entrypoint=.../node_modules/opencode-pty/dist/index.js
message="failed to load plugin" cause="...Plugin must export a default definition with an id
and an effect or setup function. (cause: SchemaError(Missing key at [\"default\"]))"

# this PR
msg="loading plugin" id=opencode-pty entrypoint=.../node_modules/opencode-pty/dist/index.js
# no failure line

Beyond the PR description: tools + a real PTY

The PR verifies "plugin listed by the server, zero plugin-load failures". Loading a
setup() that registers nothing would also satisfy that, so I drove setup() with a
recording context and then exercised the registered tools:

  • registeredTools: pty_kill, pty_list, pty_read, pty_spawn, pty_write (5)
  • registeredCommands: pty-open-background-spy, pty-show-server-url (2)
  • real PTY: pty_spawn → pty_read → pty_write → pty_read → pty_list → pty_kill
    all succeed; the write is echoed back (WRITE_OK_42), and the session id appears in
    pty_list.

Host-side confirmation that setup() really ran: GET /api/command on the running
2.0.28 server lists both pty commands (see table).

One gap to note: there is no resize surface to test. PTYManager has no resize method and
the PTY is created at a fixed 120×40 (DEFAULT_TERMINAL_COLS/ROWS); the tools expose no
pty_resize. That's unrelated to this PR (and is what #82 is about).

CI on this PR has not run

$ gh pr checks 84
no checks reported on the 'fix/v2-dual-mode-default-export' branch

$ gh api "repos/shekohex/opencode-pty/actions/runs?head_sha=9417f764..."
{"id":38431626944,"name":"CI","event":"pull_request","conclusion":"action_required", ...}

conclusion=action_required on a fork PR means the run is waiting for approval — no step
has executed. That's also the source of the UNSTABLE merge state (there is no branch
protection or ruleset on this repo).

Since the workflow couldn't run, I ran the CI matrix locally in the workflow's own order
(bun ci → bun build:prod → bun <quality>):

step result
bun run build:prod pass
bun test 194 pass, 1 skip, 0 fail
bun run typecheck pass
bun run lint pass
bun run format pass

(main at b6c2067 gives 191 pass, 1 skip, 0 fail — 192 tests — so no regression from this
PR. test:e2e needs
Playwright browsers and was not run locally — that matrix entry is unverified.)

One thing worth flagging for the release

0.4.0 was published 2026-09-18T11:18:30Z, and #61 (register pty tools, slash commands and exit notifications via the V2 plugin SDK) merged 2026-09-19T17:41:40Z — after the
release. The published artifact confirms it: dist/src/v2/index.js in 0.4.0 never calls
ctx.tool.transform, and its registerV2Commands only calls draft.update(...) — no
draft.add anywhere in that build. Git agrees: the v0.4.0 tag commit is not a descendant
of #61's merge commit, so the release predates the fix structurally, not just by timestamp.

That is why the middle row above still shows zero pty commands: fixing the entrypoint
shape on the 0.4.0 code makes the plugin load, but it still registers no tools. This PR's
build is fine because it includes #61 — but npm i opencode-pty won't pick that up until a
new version is published.

So the sequence that actually unblocks V2 users looks like: approve this PR's CI run → merge
→ cut a release (the repo's own release.sh / the workflow_run-triggered Release workflow).
Flagging it because it's easy to read "plugin now loads" as "users are fixed".

Reproducing

All of the above is scripted and read-only:

  • verify-all.sh — builds the three variants in isolated envs and prints the table
  • harness.ts — V2 contract check + tool capture + real PTY round-trip
  • raw logs for every run

Happy to share the scripts/logs or re-run against a newer build if useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants