Skip to content

Wayfinder map: opencode v2 migration #70

Description

@frap129

Destination

opencode-rules@2.0.0: a clean-break major that retargets the plugin to the opencode v2 plugin API (no v1+v2 compatibility). Works on the current opencode v2 beta; fix forward rather than gating on GA. Published to a new npm dist-tag v2 (not beta/latest, so v1 users tracking beta don't get v2 builds). Sidebar included (ported to the v2 CLI-plugin slot API).

Notes

  • All work happens on the v2 branch (already exists; we are on it). main stays untouched for v1 users; no parallel v1 plugin line, no v1 maintenance branch.
  • Deps: devDeps track the npm beta dist-tag (@opencode-ai/plugin@beta, @opencode-ai/sdk@beta / @opencode-ai/client@beta, plus new transitive peers effect, zod); peer range targets the beta line, tightened at GA.
  • Skills to consult: crafting-rules (dogfood rule files), verification-before-completion before any "done" claim.
  • Verified v2 API facts (beta source 0.0.0-beta-18999 + https://opencode.ai/v2/docs/build/plugins + v2 API reference):
    • Module contract: default export { id, setup(ctx) }. Only the default export is loaded.
    • tool.execute.before/afterctx.tool.hook(...); after: { status, result?/error? } with mutable result, reject via Tool.Error.
    • session.synthetic = durable inbox admission ("schedule execution unless resume is false"); delivery: "steer" (default) runs at the next step boundary before queued prompts; id must satisfy Session.Message.ID (msg_ prefix); metadata rides message-level. Synthetic messages do NOT run prompt hooks.
    • ctx.session.hook("context") fires per model dispatch (tool continuations, generation, compaction; not title requests), mutable { system, messages, tools, generation, providerOptions }, never persisted.
    • ctx.session.hook("prompt") fires once during admission ({ sessionID, messageID, prompt: { text, files, agents, skills }, metadata, delivery }); model resolution happens after admission, so provider scoping is unavailable there.
    • session.prompt({ id, text, metadata, resume: false }) = durable admission without agent-loop execution (v1 noReply); idempotent by id (409 ConflictError otherwise).
    • No compaction hook; observe session.compaction.started/ended, session.revert.staged/cleared/committed events (payload data.sessionID) + ctx.session.context({ sessionID }) reads (returns SessionMessageInfo[] incl. type: "synthetic" messages with message-level metadata).
    • SessionContext.model is a Model.Ref { providerID, id, variant? } — there is no modelID key (see Port model/agent capture ticket).
    • TUI = "CLI plugins": default export { id, setup(ctx) } at ./tui; ctx.ui.slot(claim) with fixed slot tree; our sidebar_content maps to sidebar.content.
    • Config key pluginplugins; local discovery accepts .opencode/plugin/ and .opencode/plugins/.
  • Dogfooding: main + v1 opencode dogfooding untouched. V2 validation is deliberate: run opencode2 against v2-branch builds. This repo's own .opencode/rules/*.md are the v2 test corpus.

Decisions so far

  • Port server entry + orchestrator to v2 plugin contract: entry default-exports {id, setup(ctx)} (type Plugin.Plugin), __testOnly replaced by the createRuntime factory seam; tool hooks → ctx.tool.hook (Tool.Error blockers), durable delivery → session prompt hook + ctx.session.synthetic, admission → session.prompt({resume:false}), transient → session context hook mutation, history → session.context, tool IDs → context tools + mcp.list, events → session.revert.*/compaction events (projection rides next dispatch, placeholder for Redesign post-compaction ledger rebuild without a compaction hook #73); plugin devDep pinned 0.0.0-beta-18999 (exact; caret resolves wrong prerelease), schema/effect/zod still transitive for Update deps, versioning, and release workflow for v2 dist-tag #75.
  • Port delivery engine to session.synthetic + context hook: durable = session.synthetic({ delivery: "steer" }) (next step boundary, before queued prompts — closest to v1 append-before-dispatch; divergence: separate persisted synthetic message instead of a part on the user message); transient per-dispatch context hook OK — tool-continuation re-injection suppressed by turn-dedup keyed on latest real user message id; admission = session.prompt({ resume: false }) (= v1 noReply, idempotent by id); codec/history port kept, decodeRawHistory extended for message-level metadata on synthetic history messages; fixed synthetic id schema violation (prt_rules_msg_-prefixed id via syntheticMessageId()).
  • Port model/agent capture to v2 Model.Ref shape: capture reads Model.Ref.id ({ providerID, id, variant? } — no modelID key), internal field stays modelID; V2SessionContextInput.model retyped, hook-ref + history-assistant fixtures fixed (model: { id }); variant handled for free (Model.Ref.id is variant-free by construction, model#variant parses into its own key) — model: rules match the bare model id, no variant logic added; flat modelID message-info fields are v2 history shape, not matching inputs.
  • Redesign post-compaction ledger rebuild without a compaction hook: mechanism = event listener (session.compaction.started/ended carry data.sessionID on beta-18999; session.compacted is a different event) for invalidation + lazy revision-guarded ledger re-decode at the first post-compaction durable turn (pendingCompactionHeal flag; durable rules dropped by the summary re-append exactly once); Working-context projection is event-driven one-shot via a compacted flag on SessionState, riding the next context dispatch; v2 message parsing needed no further work (already landed via Port server entry + orchestrator to v2 plugin contract #71/Port delivery engine to session.synthetic + context hook #72).
  • Update deps, versioning, and release workflow for v2 dist-tag: version 2.0.0-beta.1; oc-plugin field dropped (v2 discovers via exports: . + ./tui); @opencode-ai/sdk removed (unused; plugin context is the client — no @opencode-ai/client migration needed); effect/zod/@opencode-ai/ai left as plugin transitive deps (no direct imports); plugin peer range >=0.0.0-beta-18999 <0.0.1 (literal 0.0.0-beta-* is an invalid semver range — satisfies nothing); devDep stays exact-pinned beta-18999; release-beta.yml publishes --tag v2 with containment dev|v2; stable release job also excludes -rc; README/AGENTS install + release docs updated to v2 dist-tag and the v2 plugins config key; files/exports unchanged (pack dry-run clean).
  • Port TUI sidebar to v2 CLI-plugin slot API: entry default-exports { id, setup(ctx) } typed Plugin.Definition (Plugin is a namespace export); claim = ctx.ui.slot({ append: 'sidebar.content', render }), input { sessionID }; matched-rule state still read by direct fs (no data/storage second source of truth); project dir via ctx.data.session.root(sessionID); theme is flat ResolvedTheme (text.default/text.subdued/text.feedback.success.default); refresh on ctx.data.on('session.message.content.updated' | 'session.status') (data.sessionID payloads); @opencode-ai/client + @opencode-ai/theme added as exact-pinned beta-18999 devDeps (real v2 types need them) and the vendored v1 type shim deleted; build emits { id, setup } shape.

Not yet specified

  • Post-GA cleanup: when opencode v2 stabilizes, tighten peer ranges and port any API drift that landed between now and GA (compaction behavior parity is now settled in Redesign post-compaction ledger rebuild without a compaction hook #73; only the degraded-then-healed window — tool-only continuations before the next durable turn see the deduped ledger — remains by design).

Out of scope

  • v1 + v2 dual support — clean break per destination; v1 users stay on 1.0.0 (main).
  • Behavioral parity chase for post-compaction re-injection — accepted degraded-then-healed posture (decided in charting); refinement deferred to post-GA.
  • v1 maintenance branch / parallel v1 plugin line — no v1 support beyond 1.0.0; main serves as passive reference.
  • Stable 2.0.0 release now — ship betas on v2 dist-tag; the stable flip waits for opencode v2 GA.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    v2Part of the opencode v2 migration effortwayfinder:mapCanonical wayfinding map

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions