Repository navigation
Description
Proposal: attach to the shared OpenCode service instead of spawning one server per vault
Context
With the OpenCode 2.x fix in #74/#75 the plugin works again, but it still does
what it did in 1.x: it spawns a private opencode serve --port … per vault and
embeds that server's web app.
OpenCode 2 changed that model. serve --service starts one long-lived background
daemon that owns the database, the MCP servers and the per-directory sessions;
the web app is a client of it, and opencode pair / opencode service … exist
to manage and connect to that daemon. Everything the CLI, the TUI and the desktop
app use is served from that one process.
That makes the plugin's private server redundant:
- Same database anyway. A private
serveopened withcwd=<vault>writes to
the sameopencode.dbas the service (SQLite in WAL mode). It is not an
isolated instance, just a second process on the same data. - Duplicated runtime. Each private server starts its own copy of every
configured MCP server and its own watchers, for as long as Obsidian is open. - Port and password churn. The plugin invents a port (
14096by default) and,
since 2.x, its ownOPENCODE_SERVER_PASSWORD; those collide with nothing only
as long as the user does not run their own server on that port. - CORS flag dependency. In own-server mode the plugin has to pass
--cors app://obsidian.mdso the renderer may call the API. A service started
by the CLI does not have that origin configured, which is why plugin-side API
calls in service mode have to go throughnode:httpinstead offetch.
Proposal
Add a server mode to the plugin settings, default service:
service (proposed default) - attach to the daemon:
- URL from
opencode service status, password from~/.config/opencode/service.json
(fallback:opencode service get password). opencode service startif nothing is running (the command is idempotent and
prints the URL).- "Stop" in the panel only detaches; it must never stop the daemon, because
the user's terminal sessions live in it. - Sessions are created with
location.directory = <vault root>, so file tree,
@-mentions, terminal and git stay scoped to the vault.
own - keep today's behaviour (private opencode serve, port/hostname/custom
command settings as they are) for users who deliberately want a separate client,
e.g. with their own OPENCODE_DB.
Vault scoping in the embedded UI
Worth stating explicitly, because it surprised me: the web app is a global
client. Its home screen lists every project known to the server, and there is no
API-level way to scope it to one directory. I checked:
- the router only knows
/,/settings,/connect,/components,
/new-session,/server/<serverKey>/session/<id>- no project-scoped route; - the app's own store (
opencode.global.dat:server) has ahiddenmap, but
populating it does nothing to the project list.
So the plugin's approach would be: always open a session URL (never the home
screen), and optionally hide the project switcher in the session header via a
single CSS rule on a data-slot hook ("vault focus"). That is cosmetic, not a
sandbox - if you would rather not ship a CSS hack, the alternative is to leave
the switcher visible and live with it.
Open questions
- Should
servicebe the default, or opt-in with a one-time notice? - Is "Stop = detach" acceptable semantics, or should the button disappear in
service mode? - Vault focus as a CSS rule: acceptable, or out of scope?
- Anything the plugin must not do while the daemon is shared (e.g. triggering a
restart, writing config)?
Implementation status
A working implementation exists locally (service discovery via
opencode service status/start, password resolution from service.json with CLI
fallback, detach-only stop, serverMode + vaultFocus settings, tests using a
fake service CLI). It is intentionally not opened as a PR yet - it changes the
default behaviour, so I would like a direction first. Happy to open it as a draft
or to drop parts of it.
Activity
romor001 commentedon Nov 4, 2026
Fixed in a fork, installable via BRAT: romor001/opencode-obsidian (release v0.3.0, OpenCode 2.x only). PR upstream: #. Root cause was the removed / route plus the new mandatory auth.