A multi-pane, multi-model dashboard for OpenCode. Open the app instead of a terminal: manage many agent sessions concurrently, watch them stream side-by-side, and review every file change inline.
A standalone Next.js app that talks to a running opencode serve instance.
Three big capabilities work together:
- Multi-pane session manager — every active session is its own live chat pane in a responsive grid. Run several agents at once and watch them all.
- In-pane model orchestration — the router classifies each task and picks a model tier (heavy / mid / light / free), with automatic fallback through only the providers you actually have configured.
- Live file diff viewer — every
write/edit/patchtool call from any pane streams into a right-hand diff panel with accept / reject controls. - Per-model subagents —
.opencode/agents/<name>.mdfiles (and a/api/agents/syncroute that materializes more from whatever models the server has) so the primary agent can actually route to a specific model. SeeAGENTS.mdfor the contract.
# 1. Start an OpenCode server in one terminal
opencode serve --port 4096
# 2. Install + run the orchestrator in another
npm install
npm run dev # → http://localhost:3737If your server requires a password, set:
export OPENCODE_URL=http://127.0.0.1:4096
export OPENCODE_SERVER_PASSWORD=...
npm run devThe app polls /api/ping every 2s while the server is down and auto-connects
the moment it comes up. The global SSE stream (/api/stream → upstream
/event) handles live updates for all sessions at once.
- One global SSE, fanned out client-side. OpenCode's
/eventis a single firehose, not per-session. The store demuxes events into per-session state. - Next.js API routes proxy everything to the OpenCode HTTP API. This keeps the server password out of the browser bundle and lets us add a single auth header in one place.
- Send is fire-and-forget.
POST /session/:id/messageblocks until the assistant finishes; we kick it off in the route handler and let the response stream back through SSE. - File diffs are reconstructed client-side from
message.part.updatedtool events (read→ cache,write/edit/patch→ diff against cache). OpenCode also emits asession.diffevent which is ingested when present.
.opencode/agents/ per-model subagent files (minimax, flash, pro, …)
AGENTS.md primary-agent delegation contract
app/ Next.js App Router
api/ HTTP proxy to opencode
ping/ liveness check
sessions/[id]/ list + create + delete
message/ fire-and-forget user message
abort/ interrupt a session (or all)
stream/ SSE proxy → opencode /event
models/ flatten /config/providers
agents/ list subagents (with bound model)
agents/sync/ write .opencode/agents/*.md from live models
layout.tsx, page.tsx root UI
components/
layout/ Sidebar, StatusBar, MainArea, ConnectionBanner, NewSessionModal
panes/ PaneGrid, AgentPane, PaneHeader, MessageBubble, ToolCallBlock
diff/ DiffPanel, DiffCard, DiffViewer
models/ ModelBadge, ModelSelector, RouterConfigPanel
shared/ StatusDot, TokenCounter
hooks/
useBootstrap.ts ping/load on connect
useGlobalEventStream.ts one SSE → store
lib/
opencode.ts typed HTTP client (server-only)
router.ts tier classifier + fallback chains
modelAgents.ts model→named-subagent specs (single source of truth)
diff.ts unified-patch / line-count helpers
store.ts Zustand store (the brain)
types/index.ts
OpenCode's Task tool spawns subagents by name, not by model id. Passing
agent in POST /session/:id/message makes opencode run on that agent's
bound model, overriding any provider/modelID in the body. So to route a
specific task to a specific model, the model has to be a named subagent.
.opencode/agents/minimax.md binds @minimax to
opencode/minimax-m2.5-free. The primary build agent sees @minimax in
its Task tool listing and can spawn it directly. The user can also type
@minimax <task> in the composer or pin it from the agent picker
(Pin to model group). All three paths end up on minimax.
/api/agents/sync runs at orchestrator startup and writes a markdown file
for every available model (curated short names first, derived names after).
Add a provider via opencode auth and the next reconnect picks it up. The
sync is idempotent — it only rewrites files when frontmatter actually drifts.
- Subagent spawn detection is heuristic. OpenCode doesn't emit a
subagent_spawnedevent; we detect new sessions whoseparentIDmatches an open one. If your subagents don't carry that link, panes won't auto-open (you can open them from the sidebar). - The model fallback chain includes speculative IDs (e.g. various Claude 4.x slugs). The availability check filters them out at runtime, so unknown IDs simply won't be picked.
- Cost numbers are estimates computed from the per-model rates that
OpenCode reports. Models with no rate show
~$0.