Skip to content

fix(core): preserve SDK plugins across location eviction - #35725

Merged
kitlangton merged 4 commits into
v2from
sdk-store-lifetime
Jul 7, 2026
Merged

kitlangton merged 4 commits into
v2from
sdk-store-lifetime

Conversation

@kitlangton

@kitlangton kitlangton commented Jul 7, 2026 •

Copy link
Copy Markdown
Contributor

Issue for this PR

Related to #35556, but fixes a separate SDK application-lifetime defect discovered while investigating it.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

An embedded OpenCode instance previously built one graph for its SDK plugin registrar and another graph for its HTTP routes and lazy Locations. A raw mutable store bridged those graphs. Every layer wrapping that store installed a finalizer that cleared it, so evicting one Location permanently removed the host's SDK plugins.

This replaces the bridge with one scoped application layer:

  • SdkPlugins.layer owns a private registry; raw Store, makeStore, and layerWithStore are removed.
  • createRoutes() now owns the global application services, router, and lazy Location map together.
  • Embedded OpenCode uses a ManagedRuntime so one application instance has one memo map and one disposal scope; lazy Locations reuse the same host-global plugin registry.
  • Standalone serving uses the same application layer and its already-built router.
  • Request middleware receives only the global services explicitly required at request execution.
  • The in-process Fetch client binds its transport in an isolated client layer, so embedded hosts remain independent.
  • Adds an experimental DELETE /api/debug/location endpoint (debug.evictLocation on the generated client) that disposes a Location's cached services, both as a debug tool and as the public eviction trigger the regression test needs.

How did you verify your code works?

  • Added an end-to-end regression through the public embedded SDK: OpenCode.create() -> opencode.plugin(...) -> boot a Location via plugin.list -> evict it via the new debug endpoint -> await the rebooted Location's committed plugin generation (plugin.updated event) -> assert the SDK plugin is still listed. Verified this test fails on the pre-fix base (origin/v2 plus only the debug-endpoint plumbing backported): the rebooted generation contains internal plugins but not the SDK plugin. It passes deterministically on this branch (3/3 runs).
  • Kept a faster core-level check that a host-registered SDK plugin remains visible across LocationServiceMap invalidation. Note this one does not reproduce the original two-graph defect on its own; the sdk-next test above is the authoritative regression.
  • cd packages/core && bun run test test/location-layer.test.ts test/config/plugin.test.ts (18 pass)
  • cd packages/sdk-next && bun run test test/embedded.test.ts (7 pass)
  • bun turbo typecheck --concurrency=3 (31 tasks pass), plus regenerated the client via bun run generate after the protocol change
  • Started the real standalone server with an in-memory database and verified its authenticated readiness request returned HTTP 200.
  • Ran the simplify review workflow; narrowed per-request context and corrected standalone layer composition.

Notes

This intentionally does not fix #35556's transient initial-generation readiness race; that remains separate.

@kitlangton
kitlangton merged commit 8deb0e5 into v2 Jul 7, 2026
10 checks passed
@kitlangton
kitlangton deleted the sdk-store-lifetime branch July 7, 2026 15:43
jinhuang712 pushed a commit to jinhuang712/opencode that referenced this pull request Sep 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant