Skip to content

Changelog

Maintained in CHANGELOG.md and published with each release.

  • Trail — a new bay: what a conversation made. @opencode-cockpit/trail, also in the bundle (features.trail: false to switch it off), on OpenCode 1 and 2 alike. The pull requests, tickets, pages and deploys the agent created or changed, kept per conversation, grouped by the ticket they were for, and one click from the page.
    • No setup. No account, no token, no list of tools. The agent already knows what it just did, with whatever it uses — gh, an MCP server, a company CLI — so it records it with trail_add (title, a url or a ref, and free-text kind, action, for, note); the same link again updates the record (created → updated) instead of adding a row. It is told so on every request, subagents included, and what the conversation produced is rebuilt into its system prompt each time, so it still knows after a compaction.
    • A safety net, never automatic: when something the agent ran printed a PR or issue link it never recorded, its next request says so, as a choice. Links in files it read or pages it fetched are ignored, and nothing is added without the agent or you.
    • In the sidebar, on by default after Shells: each thing’s name, title, system and what this conversation last did — history, not a status, because a PR’s state belongs to GitHub. Click a row with ↗ to open the page; + N more · /trail opens the rest.
    • Its time says when: now, 12m ago, 2h ago — in the sidebar and /trail — because the column sits under Shells’ and Subagents’ durations, where a bare 2h read as two hours of work. A narrow sidebar drops the “ago” before it cuts the ref.
    • A record with no ref gives its title the ref’s column. Its kind used to stand there, cut (Confluenc… Release notes for…), and /trail said it again beside the Confluence chip.
    • /trail (ctrl+x f): this conversation, or every conversation in the project (tab), with the conversations that touched each thing under it. enter opens the page, g goes back to the conversation that made it, c copies the link, m the whole trail as markdown, x removes, / searches.
    • /link adds one yourself: it asks for the link, and a note if you like. A link you add shows in the sidebar at once.
    • The system comes from the link (GitHub, Jira, Confluence, Linear, Claude, else the domain); query parameters that look like secrets are dropped before anything is stored, and only http(s) links are opened. A deleted conversation keeps its records, under the title it had.
    • trail_list gives the agent the same facts and order as /trail. Kept append-only in ~/.local/share/opencode-cockpit/trail/, shared by every window on the project.
  • /cockpit-setup — the agent sets Cockpit up with you. Cockpit now ships a cockpit-setup skill and a cockpit_settings tool, so the command — or a plain “make my sidebar quieter” — has the agent read what is installed and written now (every value and where it came from, every name from before 0.9, OpenCode’s own sidebar blocks), fix the old names first, offer a starting point (everything visible, quiet, minimal, Status as a line), ask only what is left, one question at a time, write the smallest file that does it and check it reads back with no notices. It asks before touching OpenCode’s own files, and never suggests turning the Todo block off. From the home screen the command opens a conversation; while the agent is answering it waits its turn (1 queued) instead of cutting the reply off. In the palette as well, whichever Cockpit packages you installed. The skill’s settings reference is written from the code, and a test fails when the two disagree.
    • Then, if you want it: “tune it to how you work.” A tour of each bay you have on, with its real key and command, then your project’s conventions: which commands keep running (found in package.json scripts, a Makefile, a compose file or a Procfile) and belong in a background shell, your ticket prefix (offered from your branches and commits) so Trail groups by ticket, where PRs go, whether to explore in background subagents. Written, after you agree, as one ## Cockpit conventions section in this project’s AGENTS.md or OpenCode’s global one, by a cockpit_conventions tool that replaces that section in place on a rerun and keeps every other byte of the file. Conventions only: how to use each bay is already in every request.
    • Every OpenCode sidebar block, by its id, for the version you run: Context (suggested off when Status’s table is in the sidebar), MCP (neutral: Status’s table warns when a server fails), Footer, and on OpenCode 1 LSP and Files; Todo is never suggested off. status-setup lists the same.
  • Review shows images. A changed binary is read as bytes instead of being skipped or shown as U+FFFD. An image says what changed — PNG 2880×1800 · 807 KB → 789 KB (PNG, APNG, JPEG, GIF, WebP, BMP; any other binary its sizes) — and PNG and GIF of the same size get a pixel diff (how much changed, and where) and a preview, before and after side by side in half-block characters with the changes lit. o opens both versions in your system viewer. Decoding runs in slices off the draw path, so a pair of screenshots never freezes the window.
  • [?] Keys in Review and Subagents, like Trust’s: every key the pane takes, and esc back.
  • Every sidebar block says it is there. Shells, Subagents and Trail draw their heading and none yet before anything has run, in the row the first item will take, so a new user can tell they are installed and the blocks below do not jump. "hideWhenEmpty": true brings the silence back, per bay.
  • Status’s sidebar table is built in. title, in, out, cache, write, sep, spend, avail and git are built-in segments now (git counts what is uncommitted; "against": "branch" counts the branch against main), and the table leaves out the turn’s working clock while still showing a retry. context gets style solid and tokens style row, so the table needs no module. spend and avail read a proxy’s budget file and draw nothing without one; a hairline draws only between two rows.
  • Status’s override changes a row or two and keeps the rest. { "status": { "override": { "git": { "against": "branch" } } } } changes the table’s git row and leaves the other thirteen following the preset, where it used to take a copy of the whole list in segments. false drops a segment, a name swaps it in place, an object merges into its settings; a line in lines takes its own. A name that matches no segment is a ! row naming the closest (override "gti" … did you mean "git"?).
  • Status’s preview reads a file as OpenCode does. preview --config <file> goes through the plugin’s own loader and resolution — preset, sidebarRows, override and the ! rows — and stops on a file it cannot read or a flag it does not know rather than drawing other settings. --config - reads a candidate on stdin as the file it will become (--as global|project), so a change is seen before it is written anywhere — the status-setup skill previews this way, with no temporary file. The setup skills run the preview that came with your install — cockpit_settings names it under Previews — never bunx, which fetches another release. --surface sidebar|bottom draws there whatever the file says; the sidebar is 34 columns unless --width says otherwise. --debug names every row: ✓git drew, ✗spend drew nothing, ?gti is no segment at all — where ⟨?title⟩ and ⟨todo⟩ used to differ by one character in the same brackets.
  • New default keys, the same on OpenCode 1 and 2: Subagents ctrl+x d (was w), the shell console ctrl+x j (was i), Review’s placement ctrl+x k (was r). The old ones were OpenCode’s own — on OpenCode 2 ctrl+x w closes the tab and ctrl+x i shows image attachments, and ctrl+x r is redo on both. A test now holds every Cockpit default clear of OpenCode’s. To keep the old keys, set them in the bay’s keybinds, e.g. "subagents": { "keybinds": { "cockpit.subagents.open": "<leader>w" } }.
  • The agent knows where you see its work. One line per window, written from the bays you have on and your keys: shells, subagents and this conversation’s trail in the sidebar, review threads in Review — so it points you there instead of pasting the lists.
  • Shell’s guidance: the agent checks for a running dev server or watcher before starting one and reuses it, and gives shells short names you recognise (dev, test, build).
  • Trail’s guidance: records group under the ticket with for, and the agent calls trail_list before saying what the work produced instead of answering from memory.
  • On OpenCode 2, every bay’s guidance names tools as Code Mode calls them (tools.shell_start).
  • Review’s enabled: false turns off its agent side too — its tools and guidance.
  • Behaviour, measured: AGENT=1 smoke runs real turns that must end in the right tool — shell_start for a dev server and no second one, review_list/review_reply for a waiting comment, trail_add for a new PR — so a wording change that stops working cannot ship unnoticed. Trail’s passes on two of three turns (measure/agent.ts --runs 3 --pass 2): a free model misses about one in six.
  • Settings: one shape, one loader, one file for both halves. “Configure them in one file, read by both halves of the plugin and by every project” was true for Shell only; now it is true for every bay. Every bay reads ~/.config/opencode-cockpit/config.json and a project’s .cockpit.json through the same loader, one section per bay — status, subagents, shell, trail, trust, review, updater — with the same shared keys in each: enabled, keybinds, sidebar, sidebarRows, hideWhenEmpty. Time keys carry their unit. Configuration is the whole reference.
    • Subagents and Review read the files. Before, their settings existed only on the plugin entry — tui.json for the interface and opencode.json for the agent, two places for one bay.
    • Shell’s keys moved into a shell section, and ui.* with them: ui.historyMinutes is shell.hideFinishedAfterMinutes. Status’s section is status, not statusline. Subagents’ hideFinishedAfter / hideNestedAfter are hideFinishedAfterMinutes / hideNestedAfterSeconds. The Updater reads updater.updateCheck, not ui.updateCheck.
    • The old names are not read. Each one a file still carries is a ! row in its bay’s block — ! settings: "statusline" is no longer read — run /cockpit-setup — a line in doctor, and the first thing /cockpit-setup fixes. Detection only, removed in 0.10.
    • One order: the top-level "sidebar" list, default ["status", "subagents", "shell", "trail", "trust"], is the only one; each bay’s sidebarOrder is gone (Trust now sits below Shells by default). An entry that is not a bay asks whether you meant the closest one ("shells" → "shell"). On OpenCode 2 the bundle applies the list; separately installed packages draw in the order cli.json lists them.
    • Status’s keys at a file’s root are no longer read as Status’s (a root "enabled": false meant for something else used to turn the statusline off).
  • Status lives in the sidebar by default, as a table. The sidebar preset is now the budget table — headed Status, the window as one solid bar, the tokens in named rows with their share, a proxy’s spend and what is left, what is uncommitted — and it is what you get with no configuration. { "status": { "sidebar": false } } (or "surface": "bottom") puts the line under the prompt again. A preset that does not exist, or a config pointing at a removed example, gets a ! row naming the presets there are, never a blank column.
  • /statusline is /status-setup, and loads the status-setup skill that ships with Status: presets as starting points, every segment, the design rules, and the preview that came with your install before anything is called done. The old name works for one release and says the new one first. From the home screen it opens a conversation; behind a reply it waits its turn. Status gains an agent side for this (@opencode-cockpit/status/server), included in the bundle; installed on its own, its install line (opencode plugin @opencode-cockpit/status@… --global --force) now adds the agent-side entry too.
  • Every subagent says what it is. Each row names its agent, muted — general too — and a subagent with no title is named by its task’s first words.
    • The agents are one column, so the titles line up. As wide as the longest agent shown, eight cells at most, so general, explore and build read whole; each row used to shorten its own, so one title started at column 9 and the next at column 7.
    • A finished subagent says how long it ran, and nothing else (● general Plan login… 1m04s). 17 calls · 1m04s cut its title to twelve cells; the calls and rounds are in the pane.
  • Settings notices are drawn, not only logged. Status, Shell, Subagents, Trail and Trust draw theirs as ! rows in their blocks, wrapped to the sidebar’s width so the fix is not cut off; Review draws its in the pane. Status also draws the ones that belong to no bay: a file that is not valid JSON, a top-level name nothing reads.
  • ctrl+p works while Review is open: the review steps aside for the host’s palette.
  • A config file with a comment or a trailing comma was dropped whole, in silence, by Shell, Status, Trust and the sidebar order — while doctor called it fine. Every bay reads JSONC now, and a file that truly cannot be read is a ! row and a doctor line, and the defaults.
  • Review drew two edits more than 2,000 lines apart as a rewrite of everything between them. A long stretch is now split on lines unique to both sides and each piece aligned on its own.
  • Shell offered “show fewer” with one shell left after you had expanded the list. The toggle shows only while folding hides something, and an expansion the list outgrew folds itself back.
  • An empty Subagents block took a row of sidebar space with hideWhenEmpty on OpenCode 1.
  • The diagnostics segment flagged every MCP server on OpenCode 2 (#34). OpenCode 2 hands an MCP server’s status as a tagged object ({ status: "connected" }), which was read as text, so every connected server showed as broken. Both versions’ shapes are read now; a server that is disabled or still connecting is not an alarm, one that failed or needs auth is. diagnostics also joins the sidebar table: nothing while every server is healthy, ! name in red when one breaks, and a cut keeps the +N count.
  • OpenCode 2 kept running the old Cockpit after an update. Its background service loads plugins once, when it starts, so the agent kept the old tools and skills (no trail_add, no skills) while the windows drew the new ones. After updating on OpenCode 2, run opencode service restart. doctor now warns when the service started before the install, update and /plugins-update say to restart it, and the repository’s dev:install restarts it itself.
  • A window now says when OpenCode 2’s service runs an older Cockpit than it does: one toast, Cockpit was updated — OpenCode's background service still runs the old one. Run: opencode service restart (with both versions when they differ). Never restarted for you — that would cut every open window — and nothing is said when it cannot tell. The agent side records which install it loaded; doctor reads the same record, so it no longer guesses from clocks.
  • /cockpit-setup said “Notices: none” while the sidebar still warned. cockpit_settings and doctor saw only the loader’s notices, not each bay’s own: Status’s override "gti" matches no segment, Trust’s threshold: "3". Each bay’s notices now come from the bay — its keys’ kinds, and Status’s own check of presets, surfaces and overrides — so “none” there means no ! row anywhere.
  • The examples/sidebar.ts, sidebar-full.ts and sidebar-budget.ts Status modules: the sidebar preset is the table they drew.
  • The old sidebar preset, and each bay’s sidebarOrder.
  • Trust — a new bay: permissions that learn. @opencode-cockpit/trust, also in the bundle (features.trust: false to switch it off), on OpenCode 1 and 2 alike. Approve the exact same command three times in a row and Trust answers OpenCode’s prompt for you from then on, and records every answer.

    • Exact, not “similar”: docker compose -p cockpit up -d and docker compose -p prod down -v never share an approval, nor does one command in two directories. A line is answered only when every command in it is trusted or allowed by config; $(…), eval, sh -c, loops (while … do … done) and the like are always asked. A quoted '>' is an argument, so echo '>' x and echo > x, which writes a file, never share an approval. Counts are per project and per agent.
    • Dangerous costs more: rm, git push, --force, reset --hard, down -v, kubectl delete, terraform apply, DROP, npm publish, sudo… need threshold + dangerExtra (3 + 5) approvals in a row. A reject resets the count; trust unused for 30 days expires.
    • Your config wins: a specific "git push *": "ask" is never answered — only a catch-all ask is Trust’s to fill. Trust reads OpenCode’s config and never writes it.
    • Only your approvals count: Trust’s own answers, and any reply under 300ms (OpenCode’s --auto), are not counted.
    • Sidebar, when you want it: Trust 4 auto, what it answered and how often, and the request on screen with its count (2/3). Hidden by default — "trust": { "sidebar": true } shows it, and the palette’s “Show or hide Trust in the sidebar” flips it for the session; a failure shows either way. A log line per answer; no toasts.
    • Ledger: /trust or ctrl+x p — what it answered and what it has learned, revoke (x), copy a rule for opencode.json (c), pause in this project (p), and OpenCode’s own broad “Always” approvals under a warning. Kept append-only in ~/.local/share/opencode-cockpit/trust/, shared by every window on the project.
    • Families: the ledger groups rules by what they do — ls -la, ls -x are ls; git -C x status is git status; docker compose -p prod down -v is docker compose down; sudo ls stays its own. A command two agents earned is one row, each agent’s count on its card.
    • Exactly: a panel under the list shows the selected command with every argument quoted where a font could merge it (echo "---", not echo ──) and says what it answers and what still asks. An argument that is only punctuation is also said in words, there and beside its row (echo "---" 3 hyphens), since a ligature font merges --- even inside quotes.
    • w trusts a whole family, for one agent, only when you press it: any ls … is answered except dangerous commands, writes to a file through a redirection, find -exec/git -c, opaque lines and your config’s specific asks. Dangerous families can never be widened. w again or x on the family undoes it; answers through it say so (● ls -x · any ls).
  • The agent can read and wait on its subagents. subagents_read reads one subagent in full — why it stopped, its task, its whole answer, every call — paged with a cursor, and works on cancelled ones, which can still be continued. subagents_wait blocks until subagents finish, fail, are cancelled or need you, never past its timeout.

  • A subagent’s calls are drawn by what they are. A task names the subagent it launched and enter goes there; todos are a checklist; webfetch and websearch show the URL or the query; an MCP tool is titled server · tool; an argument is one row when short, markdown under its name when long, indented JSON when an object. Thinking is drawn as markdown, muted, and the pane’s markdown now has emphasis, links, strikes and a fence’s language on its first row.

  • Status says which clock it is. session.time takes of: "turn" is how long the last answer took, took 3m42s, and nothing while one is running; bare, or "session", it is still the conversation’s age. Every built-in line uses the turn, so a conversation reopened two days later no longer reads 2d 15h with no word beside it.

  • Review shows whitespace that changed. A line whose only change is spacing draws it — · a space, → a tab, ␍ a carriage return — and a hunk of nothing else says whitespace only.

  • Review says when a file is new, deleted or renamed — renamed from src/config/load.ts, muted, after the path. A file moved and not touched stays in the review, and a moved file’s diff shows the edit rather than the whole file as new.

  • bunx @opencode-cockpit/shell preview draws Shell’s sidebar block, dock and console — empty, running, failed, details, a filtered log, finished — from sample shells, with no OpenCode and no daemon. --part, --state, --width, --columns; plain text under NO_COLOR or into a pipe.

  • Doctor warns when OpenCode 1 will run every subagent in the foreground, with the line that fixes it (OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true).

  • Trust opens on what it did for you; the ledger is a tree of families with a card. /trust now leads with today: every answer Trust gave in the project, newest first, with why in a few words (“trusted since yesterday, 3 in a row”, “in a family you widened: ls”) and a sparkline of the week; then almost there — what is still learning, closest first, with a meter (▰▰▱ 2 of 3, red and longer for a dangerous command); OpenCode’s own “always” in a warning band of its own; and the project’s counts with a button into the ledger. enter shows why: the rule’s card. The ledger (l) is families as a tree you fold (←/→, space), each command once whatever its agents say, and a card for the selection always on screen — the command whole, each agent’s standing, what still asks, the approvals that earned it (✓ 9h ✓ 9h ✓ 9h → trusted), its family, when it expires — with x, w and c as buttons you can click or tab into. / filters; below 90 columns the card goes under the tree; esc steps back to the activity, and closes from there. Colour is back as signal: green for what answers, the warning for what is close, red for what is dangerous, agents as chips — tints of the user’s theme. The preview takes --view activity|ledger.
  • Every Cockpit command in ctrl+p sits under Cockpit · <bay>, titled by what it does. Typing “cockpit” lists them all on both OpenCodes (“Open or close the changes”, “Show or hide Trust in the sidebar”…). Slash names and keys are unchanged.
  • Every bay speaks one visual language. The bays had drifted: green meant running in Shell and done in Subagents a few rows apart, and brackets meant a key, a status or a checkbox depending on the screen. Now, everywhere:
    • Colour is for what asks something of you. Running wears the accent, needing you the warning, failed the error; done and stopped are quiet. A cancelled run is stopped — not cancelled, and not a failure.
    • Keys read [key] Label, lowercase keys and Title Case labels, and brackets mean a key or a checkbox ([✓]) and nothing else: a Review thread’s status is a bare word in its tone.
    • esc closes every screen, and a key row too narrow for its keys drops the least-wanted ones, says …, and never drops the way out.
    • One sidebar heading: the name on the left, every count on the right in its state’s tone (7 done · 1 stopped), keeping what needs you when the column is narrow — and a row of air under it, so the first row no longer reads as part of the heading.
    • One gauge: calm, the warning from 75%, the error from 90%; ! for a warning, never ⚠.
  • The Subagents sidebar puts what is working first, and lets what has ended be quiet.
    • Working subagents come before finished ones; a late runner no longer sits under a pile of done.
    • A finished subagent is one muted row with its calls and time; running, waiting, failed and stopped keep the row that says what.
    • Subagents with the same parent, agent and task are one entry with a count (×6).
    • A finished nested subagent — an advisor asked again and again — leaves the sidebar after hideNestedAfter seconds (30; negative keeps them). The heading still counts it and the pane still reaches it.
    • One held on a permission is drawn in the warning tone and counted apart (1 running · 1 needs you); the time on the right is always the whole run, as the pane’s header says it.
    • general is no longer named on every row; any other agent is, muted.
  • The Status default line keeps colour for what it signals. A format’s words are muted labels and its figures are text; colour is left to the bar’s level, +/- and a retry. A part that is zero (cache 0 on a provider with no prompt cache) is left out. working 1m02s counts from your prompt, and the sidebar preset now shows why a turn stalled (retry 2 in 5s).
  • Review’s file list fits more, and names what it is. A folder holding one file is one row with it (tui/index.tsx), and the list sizes itself to its names, 26 to 40 columns: at 180×30 it lists 25 files where it listed 13. “Read” is now “viewed”, matching the checkbox, and with nothing to review the header drops its zero counts and the footer offers only what can help.
  • The Updater’s list keeps its columns. Every column keeps its gap, so a long name no longer runs into its version; short of room the list gives up config, then running, but never the published version or the state (unreachable is never unreac…). Doctor says 23h ago, not an ISO stamp.
  • Shell’s console says a failure in full and keeps its rows. A failure’s reason wraps to three lines instead of being cut to one; details keep their top rows and count the rest (↓ N more [w] Full Screen); the empty console names its scope and offers [s] Whole Project. In the sidebar, the times, watches and exit codes end in one column, and a row says how long a shell ran rather than how long ago it ended.
  • /plugins-update is the only name for the plugins screen. /cockpit-update, kept as an alias since 0.5, is gone.
  • The agent is told about background only where its tool has it. On OpenCode 1 without the flag it launches independent subagents side by side in one message instead of failing on the field.
  • “Show or hide Trust in the sidebar” looked dead in a fresh window. The shown block now sums up the project (5 trusted · 1 counting) or says nothing learned yet, and the toggle confirms it.
  • “Toggle the changes full screen” did nothing while the changes were closed. It now opens them.
  • A subagent continued a day later read “stopped after 24h04m”. The time shown is now its last round’s — “done in 4m00s (round 2)” in the pane and tools, ”· 2 rounds” in the sidebar — with when it first started (“first started yesterday 22:17”).
  • The subagents a cancelled subagent launched kept spinning. Those still running are shown as stopped in the sidebar, the pane and the tools, unless OpenCode says they are still at work.
  • On OpenCode 1 a message about a shell could replace a subagent’s answer when it arrived as the subagent finished. A running subagent there is no longer messaged about its shells: a failure goes to the conversation when it finishes, and subagents are asked to check their shells before answering. OpenCode 2 is unchanged.
  • On OpenCode 2, subagents from before a restart could not be reached. subagents_read and subagents_wait take their id, and subagents_list says why they are missing until they act.
  • A subagent’s long arguments could not be read. A long ask_advisor question was cut at three rows folded and twenty open, and a reached only the output. Arguments now climb the same ladder: three rows folded, sixty open, up to two thousand with a, always saying … N more lines. A cut call says how to see the rest on its own row (… 2 more lines · [enter] Expand), with keys rather than a mouse-only “Click to expand”. Fixes #32.
  • A reopened subagent consulted once read done · 0s. Its run now ends when its last message finished, on both OpenCodes.
  • working 2d 15h a second after a question to an old conversation. session.status counted from the session’s creation; it now counts from the prompt.
  • A viewed file in Review hid the notes still going on it. A file stays open while any thread on it is unresolved, and folds on its own once every thread is resolved; z still folds it by hand.
  • Review drew tab-indented and CRLF files off the grid, and read the old path of a rename in uncommitted work as a file of its own. Tabs expand to their stop, carriage returns are dropped, and a rename is one entry.
  • The Updater’s ⚠ shoved every column after it in terminals that draw it two cells wide, and its local and built-in rows were dimmed to about 2:1 against the background. The mark is !, and those rows are muted without the extra dimming.
  • A subagent’s failed shell could vanish. A failure told to a subagent that is still working now also reaches the main conversation once that subagent finishes, naming the shells and how to hand the failure back.
  • subagents_list contradicted the screen. A subagent continued after OpenCode restarted listed the wrong task; times are now also given by the clock; the list, the pane and the sidebar state a run’s title, calls, state and duration from the same functions.
  • General subagents on OpenCode 1 were labelled with their launcher’s agent (“build”).
  • shell_list said “exited” for a crash the sidebar drew as failed, and now names the subagent that started each shell.
  • review_list spoke from the person’s side. It says which threads wait on the agent and which on you, and no longer signs your comments as the agent’s.
  • A subagent’s shell woke the main agent, and the subagent never heard. Exit and health notices went to the conversation a shell is shown in, not to the session that started it. They now go to the agent that asked: a subagent still at work gets them inside its turn (steered on OpenCode 2). Once it has finished, a failure goes to the main agent instead, naming the subagent and how to continue it with the error (task with task_id on OpenCode 1, subagent with its sessionID on OpenCode 2), so the agent that knows the work picks it up; a clean exit, or health coming back, stays quiet. Shells keep showing in the conversation as before, and a shell started by an older Cockpit is told about as it always was.
  • Review scrolls sideways. A line longer than the pane can be read to its end: swipe sideways or shift+wheel over the diff, or shift+← / shift+→ (H / L). The line numbers stay put; only the code moves, as far as the file’s longest line.
  • Review highlights more languages. Ruby (.rb, .rake, .gemspec, Gemfile, Rakefile…), Terraform and Terragrunt (.tf, .tfvars, .hcl, terragrunt.hcl, Nomad), Lua, Elixir, Haskell and Elm, Nix, GraphQL, Protobuf, Perl, R, Julia, Clojure and the Lisps, OCaml and F#, CMake (CMakeLists.txt), Prisma and Windows batch — and in the C family, Objective-C, Groovy and Gradle (Jenkinsfile), Solidity, Zig and Arduino. A file’s full name now beats its extension, so CMakeLists.txt is CMake rather than text.
  • A reply in Review opened a new thread above the one it answered. Threads are drawn where their code is now, but were looked up where they were first written — once the code moved (the agent answering usually moves it), c on a thread started a second one. The cursor now finds the thread that is drawn under it.
  • Shell’s full screen used half the window. A running program kept the 120 columns it was started with until you typed into it. It is now sized to the console showing it — opening it, w between the dialog and full screen, a resized window — once per change.
  • Reopened conversations showed finished subagents as running, with empty runs. On OpenCode 1 the interface’s store has not loaded a subagent’s messages yet when Cockpit reads them after a restart; an idle subagent with no history read as one about to start. Their history is now read from OpenCode’s server when the store has none, a stored run that went idle counts as finished, and on OpenCode 1 “no status” counts as idle when a quiet run is checked again. Measured on both versions: three subagents, OpenCode closed and reopened — all finished, with their runs.
  • x could “stop” a subagent that had already finished, telling the main agent about it. Stop now asks OpenCode first; a subagent that is not running is marked finished, and x again removes it.
  • Reopened subagents on OpenCode 2 showed how long ago they ran as their duration (“done in 1m09s” for a 5 s run). Their end is now their last recorded moment.
  • Doctor listed a warning for every OpenCode 2 event Subagents does not use. Those are expected (a plugin there hears every event in the app) and are now debug lines; a surprising shape inside an event it does use is still a warning.
  • Subagents — a new bay: what your subagents are doing, while they do it, and follow-ups that keep their context. @opencode-cockpit/subagents, also in the bundle, on OpenCode 1 and 2 alike.
    • Sidebar: every subagent of the conversation with what it is doing now (└ grep "session" src/auth/** 4 calls · 51s), its state at a glance, and how many rounds it has had.
    • Pane: a click — or ctrl+x w, or /subagents — opens its run on the right, half the window or all of it (w): model and launcher, the task, then the run the way OpenCode draws its own. Shell commands and file changes are boxes with their output (10 lines, 60 open, all with a); reads and searches are one quiet line; thinking folds (t, remembered); the answer is markdown. j/k and enter or a click move through it and open items; i shows details.
    • Follow-ups keep their context: the main agent is asked to continue the subagent that did the work (task_id / sessionID) instead of starting a new one, and has a subagents_list tool — each subagent’s id, task, state and last answer, including when one was cancelled or ended without a final answer.
    • Message one (m): a working subagent picks it up mid-run; a finished one answers you, and the exchange is added to the main conversation without starting a turn, so the main agent knows. A message it finished without reading comes back to the field.
    • Stop one (x twice) — the main agent is told you stopped it, so it does not relaunch it; x on a finished one removes it from the list, X clears every finished one; hideFinishedAfter takes them out after a few minutes.
    • Background: the main agent is asked to launch independent subagents in the background; b moves a blocking one there (OpenCode’s ctrl+b). Built into OpenCode 2; on OpenCode 1 with OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true.
    • Every behaviour measured on OpenCode 1.18.32 and 2.0.15. A paint of the pane takes 0.3 ms on a 200-call run.
  • The sidebar reads statusline, subagents, shells — and one list reorders it. { "sidebar": ["shell", "status", "subagents"] } in ~/.config/opencode-cockpit/config.json (or a project’s .cockpit.json); a bay’s own sidebarOrder still wins. OpenCode 2 now keeps the same order as OpenCode 1, and the statusline’s and Shell’s places at the foot of the window no longer move with their sidebar place.
  • Paste works in Cockpit’s text fields — Shell’s search and the shell you are typing into, as well as a subagent message. A paste arrives as one event, not keys, and went to OpenCode’s prompt.
  • A prompt dialog with a plain-text description stopped OpenCode 1 (“Orphan text error”). The shared host now hands plain text to OpenCode 1’s dialog as an element.
  • OpenCode 2’s event stream, once closed, stayed closed, so shells of sessions deleted afterwards stayed until restart. It is opened again, with a backoff.
  • doctor counts a daemon owned by another user as running; bun run dev:install leaves an install that npm install works in.
  • Cockpit runs on OpenCode 2. Every package now loads on OpenCode 1.18+ and 2.0.15+ from the same entry: the panels, console, Review, statusline and updater in the interface, and the shell_* and review_* tools, system guidance and exit notifications on the agent side. Each bay is written once against a host (@opencode-cockpit/client/host and /server) that each version supplies. On OpenCode 2, /plugins-update says to change the version in opencode.json: the updater edits OpenCode 1’s files only.

  • One log for everything Cockpit does inside OpenCode. Both halves of every bay write JSON lines to ~/.cache/opencode-cockpit/cockpit.log, beside the daemon’s cockpitd.log: which OpenCode (v1 or v2, and its version) loaded which entry, every error with its stack — including ones that used to be a toast and nothing else — and every tool that failed. COCKPIT_DEBUG=1 opencode adds the detail (console actions, each tool call and how long it took) and turns the daemon’s debug lines on too. The file moves to cockpit.log.1 past 5 MB.

  • npx opencode-cockpit@latest doctor. Checks a setup and prints the fix for anything wrong, for the OpenCode you have: its version; every Cockpit entry in opencode.json, tui.json and cli.json in either OpenCode’s spelling — a bay configured twice, a half missing on OpenCode 1, a pin older than the newest release, a checkout OpenCode 2 cannot load; what the log says last ran, and on which OpenCode; recent errors; the daemon; git and ps; settings files and statusline modules. Runs under Node outside OpenCode, so it works when Cockpit will not load. --json for an issue; exits 1 when something must be fixed.

  • createShellServer and createReviewServer return a feature for dualServer rather than a v1 plugin function.
  • Full-screen shell console and the Review pane were see-through on transparent themes. A theme that leaves its background transparent (OpenCode’s “system” theme, which shows the terminal’s own) painted the full-window surface with nothing, and the conversation showed through it. They now use the first opaque background the theme has, and a solid one when it has none.
  • Review reads like a pull request: every file in one scroll. Each changed file is a card with a heading you can fold — path, +/−, its notes, + note and [ ] viewed — and the heading of the file you are in stays pinned at the top. Marking a file viewed folds it and moves to the next unviewed one (below, or the nearest above — never back to the start); z or enter folds by hand; very large diffs start folded. Headings are clickable. Only what is on screen is drawn: a two-hundred-file review scrolls at under a millisecond a frame, and each diff is worked out once. The file list follows the diff as you scroll.
  • Review compares a stacked branch with the branch it grew from. On main ← feature ← X, branch mode used to compare X with main and mix in every commit of feature; a stale local main mixed in work already merged. It now finds the nearest parent — the branch X has the fewest commits beyond — the way a pull request from X into feature reads. B picks another base, remembered per branch.
  • Shell console full screen (w). The same console over the whole window — output, details, search, every key — remembered for next time; w again for the dialog.
  • Scroll back through a shell’s screen. j/k (or the wheel in full screen) scroll the screen view through the terminal’s history, not only the plain log; G follows the output again.
  • Finished agent shells clean themselves up. lifecycle.removeFinishedAfterMinutes (30 by default, 0 keeps them) removes a shell the agent started that long after it exits cleanly. Failed or killed shells stay until /shells-clear: those are the ones worth reading.
  • Watchers look like watchers. A watched shell says watch tsc … from the start — it used to look like any other shell until its first run finished — and /shells groups them under Watching.
  • /shells is a list: every shell in the project, grouped by what needs attention, with “New shell” first. The dock moved to /shells-dock (ctrl+x o unchanged); /shell still reopens the last console.
  • The pane without the cursor is dimmed less. It blended into the background far enough to read as disabled; it is now a gentle step back.
  • B is in Review’s key row, and the shell console dialog is centred on screen.
  • Clicks in the Review pane landed one row off, and + note on a heading closed its own dialog on mouse release.
  • Opening the shell console while it was already open left a dialog that esc could not close.
  • The Updater tells a program from a plugin. opencode-worktree@latest in a plugin list names a command-line tool on npm, with no plugin entry points; OpenCode refuses it, and the Updater used to propose pinning it and then fail with that same refusal. It now reads the installed manifest and applies OpenCode’s own rule — exports["./tui"], exports["./server"], main or oc-themes — and lists anything else as not a plugin, with the file to remove the entry from. What OpenCode already loaded is never questioned, and a manifest it cannot read proves nothing.
  • A pin says what it is for. The review showed 0.4.1 → 0.4.1, which read as a bug; it now reads 0.4.1 · pin, so latest cannot freeze again.
  • A watch sees what the run printed before it was attached. A shell that failed in its first milliseconds could print its failure before shell.watch arrived, and the run was judged without it. A new watcher is now caught up on the current run’s recent output — never an earlier run’s — and on its exit, if it has already ended.
  • Updater: every plugin you have installed, what it is really running, and an update checked against disk. OpenCode installs a plugin into a cache directory named after its spec and never resolves it again, so some-plugin@latest — or a bare name — means the release that was newest the day you first installed it: one person sat on 0.1.2 while 0.4.2 was published. /plugins-update lists every plugin with what is running beside what the config says and what is published; latest ⚠ marks a spec that will not move on its own. The review shows every file and cache directory that will change before anything is written. An update pins an exact version through OpenCode’s own opencode plugin -f, removes the stale cache, and reads every file back — the command prints “Installed” over entries it left alone, so disk is the only evidence. Whatever is still wrong comes with the exact command that fixes it. Docs
  • The rescue, for anyone too far behind to have it: npx opencode-cockpit@latest update (or bunx). It runs from npm rather than from the copy that is stuck, and both re-resolve @latest on every run — what OpenCode’s cache does not do. --dry-run, --only <name> and --yes.
  • A failed install says why, and what fixes it. npm’s cache holding files you do not own — an old sudo npm — fails every install that touches them; the result names it and puts sudo chown -R "$(whoami)" ~/.npm before the retry.
  • Every install line pins a version: opencode plugin opencode-cockpit@0.5.0 --global --force. The documented bare install was the command that created the frozen state; --force makes the same line the way to move to a newer release.
  • /cockpit-update opens the Updater, for every plugin rather than only this one. The daily notice moved with it and counts every plugin; ui.updateCheck: false still silences it.
  • The registry is the one npm uses (npm_config_registry), so a plugin on a private registry is asked where it lives.
  • The update check never got an answer. It asked npm for abbreviated metadata on /latest, which npm now refuses with a 406, so no update was ever announced and /cockpit-update offered a reinstall with no version. The test stubbed the request and asserted the header, so it stayed green.
  • The documented keys match the keys. Review’s table still offered a third source that 0.4.2 removed, the statusline’s git.diff was still described as counting the session, and the shell’s console table was missing d, shift+d, backspace, ctrl+] and everything that scrolls — along with the two keys that open the panel and the console in the first place. Every bay now documents what it actually binds, including the statusline, which binds nothing and says so.
  • The console names its keys the way Review does, and stops carrying all of them. i type · c ^C · r restart · tab screen · / search log was a sentence you had to parse before you could use it, and at nine keys there was no room left for the words. The row now holds only what acts on the shell in front of you — [i] Type [c] ^C [r] Restart [x] Stop — and [?] Details, which opens the panel where the rest are laid out in two columns, under the shell’s own facts. The bracket does the work colour would otherwise have to do, and colour stays with the shells.
  • Review reads two sources, not three, and the statusline counts git. “This conversation” is gone. It was built on OpenCode’s session.diff, which returns an empty list for a session whose own snapshots plainly differ — checked against three baselines, with the snapshot trees diffed by hand to confirm the changes were really there. A mode that cannot answer is worse than one that is missing, so it has been removed along with the dead FileChange.marked it was reserved for. [b] now toggles uncommitted and branch.

    The statusline’s session.diff segment rested on the same feed and made the same promise. It is now git.diff and reads git diff --shortstat HEAD — what is uncommitted, a number you can check by running the command yourself. The old name still resolves, the command runs only when a line actually carries the segment, and a line without it spawns nothing.

  • /cockpit-update no longer promises an update it cannot make. It cleared the cached copy and told you to restart — but if your config pins opencode-cockpit@0.4.1, the next start reinstalls 0.4.1 and the version never moves. It now reads both plugin lists (opencode.json and tui.json), and when a pin is in the way it says which entry to edit instead of clearing a cache for nothing. A path install and a tag are not pins, so neither is treated as one.

  • The handover tells the agent what it needs before it asks. The submit message now names the files the comments are in, so the first move is not a tool call spent finding out where the work is; says plainly that resolving is refused while a file still reads as it did, which is what review_reply actually does; and draws a boundary — these comments, no unrelated work, no commits, and say so in the reply when a comment turns out to be the tip of something bigger.

  • The panel no longer fails in silence. The change store set a notice in four places — “no conversation open”, every git error, “file too large” — and nothing ever read it, so each of those reached the screen as an empty pane with no explanation. It is now shown in the footer, and the empty state names the source it is empty for and points at the key that changes it.

  • [s] says how much there is to submit, and says the right thing when there is nothing. The key carries a live count and dims at zero, and the three ways to have nothing to hand over — no comments yet, all answered, all outdated — now read differently instead of all claiming every comment had been answered. Submit also counts outdated comments before offering, so the number it offers is the number it sends.

  • YOU and AGENT are readable on every theme. The badges on a comment — and the review badge in the header — printed their word in the theme’s background colour, which paints nothing at all when a theme leaves that colour transparent: the coloured block appeared, the word inside it did not. The ink is now chosen rather than assumed, taking the first theme colour that both paints and stands clear of the block it sits on; where a theme offers nothing that reads, the block is dropped and the badge is printed in its own colour instead.

  • A shell the agent starts from a subagent now belongs to the conversation you are in. A tool called inside a task runs in a child session, and the shell was stamped with that id — so the panel, which filters by the session on screen, showed the agent’s own shells only under “whole project”, and /shell listed shells that looked like they came from somewhere else. Ownership now resolves up parentID to the conversation that asked, and the guidance the agent reads resolves the same way, so “this session” means the same thing on both sides.

  • sidebarOrder puts the bays in the order you want. Shell and Statusline both draw in the sidebar, in the order they register — which was a constant nobody could reach. Set ui.sidebarOrder for Shell (default 150) or statusline.sidebarOrder (default 200); lower draws first.
  • A file containing @ before a bracket or a space no longer freezes the interface. The Review syntax scanner accepted @ as the start of a word but not as part of one, so it advanced by zero characters and looped forever — at 100% CPU, with no error, no stack and no way out but killing OpenCode. Ctrl+C did not help either: a synchronous loop never reaches a signal handler. Every branch of the scanner must now advance, and the test is every printable character in ten languages plus all 9,025 two-character pairs.
  • A daemon whose socket has been deleted now stops instead of stranding its shells. A unix socket is held by its inode rather than by its name, so removing ~/.cache/opencode-cockpit — where cleanup tools aim — left the daemon running and listening on a path that no longer existed. The next client found no socket, started a second daemon, and the first kept its shells alive where nothing could see or stop them: a dev server holding a port, findable only with ps. There is no way back from that state, since a client can only reach the daemon through the path, so it shuts down and lets its shells go rather than leaving them stranded for the rest of the session.
  • Review — a pull request in the terminal. Bay 02, @opencode-cockpit/review. The diff where the work happened, comments on the lines they are about, and an agent that can read them, answer them and mark them resolved. Comments live on the branch rather than in the chat, so they outlive the conversation; s hands the review over, and the notes travel as structured data through review_list rather than as prose the agent has to parse back out of a message. A resolve is checked against the file before it counts: an agent that claims “done” over an untouched file has its reply kept, the thread left open, and is told so plainly. It can open notes of its own with review_open, which appear in the panel beside yours. <leader>v opens it.
  • bun run clean:daemons stops cockpit daemons that nothing can reach any more — orphans whose home is gone, and daemons left behind by an interrupted test run. test and check run it first, because a leftover daemon does not fail a suite, it hangs one.
  • The line reports its own failures, on the line. A module that would not load draws a ⚠ row naming it, and a column that ran out of room draws a dim ↳ N more — raise maxRows. Both used to end as segments that were simply not there, which is indistinguishable from a segment that had nothing to say — the one place this bay’s silence rule is wrong. The overflow notice takes a row of its own, and is the first thing dropped if the column is smaller still.
  • preview --module <path> draws that module and nothing else, every segment it declares, with room for all of them. It used to be added to whatever the config already named while the config’s segments still decided what drew — so pointing the preview at a module whose segments the config does not list produced a confident picture of somebody else’s line. --with-config restores the old behaviour, which is what you want once the module is finished and you want to see it in place.
  • /statusline hands the agent in your session a brief instead of drawing a panel: which config file this project reads, what is drawing now, your modules and any that failed to load, the preset and segment names, and where the design skill lives. It ends by asking what you want it to show. Customising a line is an editing job, and the agent is already sitting there.
  • bunx @opencode-cockpit/status preview works. It was exiting 1 and printing nothing, for two reasons at once: the published entry had no shebang, so a shell read the JavaScript as a shell script, and bunx <package> looks for a bin named after the package’s last segment, which was never declared. Both are invisible from a checkout, where nobody runs the bin — so the command every page here recommends had never once worked from npm.
  • A statusline module outside a project loads again. The fallback that makes ~/.config/opencode-cockpit/modules/ work asked for ./authoring.ts, which a built copy does not have beside it, so it threw — and with it went every module belonging to the people that fallback exists for. It worked only from a checkout, which is where the tests run.
  • A missing ps no longer fails every shell. The daemon recorded a process’ start time by spawning ps from PATH, on the path that starts a shell, so a daemon that inherited an editor’s slimmer PATH turned every shell_start into ENOENT: no such file or directory, posix_spawn 'ps'. It is a best-effort guard against pid reuse and now behaves like one.
  • The design skill contradicted itself about the empty half of a bar, calling for panel in its rules and warning two paragraphs later that panel is the colour of the panel the bar sits on, and therefore invisible. The track is border — the same wrong tone was in the drawing page’s examples.
  • The six sample states are written down, with what each one catches: the wall of zeroes on fresh, the column widths only full reveals, the invented $0.00 on unpriced, the rows that vanish mid-turn on retrying.
  • Two glyph rules the last design pass earned: no end caps on a bar in a column, because ▕ and ▏ are eighth-blocks whose ink sits against one cell edge and indent the row out of alignment; and print a number once, because a bar and the labelled row below it were both reporting 43%.
  • preview — draw your statusline in a terminal, without restarting OpenCode. bunx @opencode-cockpit/status preview --watch redraws on every save, against six sample sessions: a fresh one before the first reply, a long one nearly out of room, one behind a proxy with nothing declared, a stalled one, and no session at all. Designing a statusline used to mean editing, restarting and squinting — one sidebar cost about twenty restarts, and three of the mistakes were glyph choices that read differently on screen than in a sentence.

  • "debug": true draws a placeholder where a segment said nothing, so the three reasons a segment can be absent stop looking identical: ⟨context⟩ it ran and had nothing to say, ⟨?contex⟩ nothing answers to that name, ⟨!name⟩ it threw.

  • A segment can return several rows. An array is a row each, which is how a gauge, a table or a row per service is drawn. Returning one used to be a silent no-op.

  • italic and underline on a run, drawn as markup. strikethrough and inverse are not offered: OpenTUI has no element for either, so they could never have reached the screen.

  • examples/gallery.ts — every technique the renderer offers in one column: six kinds of bar, braille, sparklines, rules, dots, chips, dividers, emphasis, every tone, and multi-row output. Run it through preview and copy the row you want.

  • A design skill, shipped with the package at skills/statusline-design/, carrying the rules this bay learned the expensive way — solid bars rather than dashes, words rather than colour alone, no headings above optional rows, look at it before shipping it.

  • Presets — a whole line by name, built-ins only: minimal, default, detailed, sidebar. Anything written beside one wins, so it is a starting point rather than a mode.

  • examples/sidebar-budget.ts — a sidebar drawn as a table, and the layout a user arrived at after five rejected iterations: a fixed six-column label gutter so every value lines up, one bar with no figure beside it, the tokens split into named rows, a budget read from whatever a proxy writes to ~/.cache/opencode-litellm-iap/spend.json, and the branch’s whole diff against its merge-base rather than what this session happened to touch. "demo": true fills in sample figures for the budget rows, so the column can be looked at before a proxy exists.

  • A statusline module that fails to load now says so in OpenCode’s log, not only in a toast that is gone in ten seconds. The entry names both the module and the directory resolution was attempted from, which is the pair that makes an import failure obvious instead of mysterious.
  • maxRows and the padding settings work at the top level of the config, not only inside a lines entry. Writing them there is the natural guess, and being quietly ignored cost exactly the rows they were meant to keep.
  • The design skill is documented on the site, at What you can draw — where it ships, how to point an agent at it, and what it actually carries. It existed in the package and was mentioned only in this file, which is no way to find anything.
  • Two glyph traps written down: ▕ and ▏ are eighth-blocks whose ink sits against one edge of the cell, so end caps indent a column’s bar out of alignment with its labels; and an empty track wants a solid █ in the border tone, because ░ reads as floating gaps and panel is the colour of the panel it sits on.
  • The worked-examples lists name all five modules. They still said two.
  • Statusline, bay 02. A line of live session state under the conversation, or a column of it in the sidebar. Fourteen built-in segments, two surfaces, and three ways to configure it: declarative segments in .cockpit.json, your own TypeScript module, or a shell command. opencode plugin @opencode-cockpit/status --global, or get it with the bundle.
  • Your Claude Code statusline works here. A command segment is fed the same JSON on stdin that Claude Code’s statusLine hook sends, including context_window and current_usage, so an existing script runs unchanged. Its colours survive too: the SGR escapes are parsed rather than stripped, with 24-bit and 256-colour values kept exactly and the basic sixteen mapped to theme tones. Multi-row scripts keep their rows.
  • Segments written in TypeScript, against @opencode-cockpit/status/segment. A module is handed the same snapshot the built-ins get and touches no OpenCode api, so a custom segment is as testable as a built-in — and because it is loaded once and called on every repaint, it can keep history, which is what makes a sparkline or a rate possible. A module can live in your config directory rather than inside a project; nothing needs installing beside it.
  • Honest behind a proxy. Tokens always work. Cost and the context percentage are computed from your model catalogue, so behind LiteLLM or a gateway they need declaring in provider.<id>.models — and where they are not declared, those segments stay silent rather than reporting $0.00 and 0%. The same rule runs through the bay: a segment with nothing to say says nothing.
  • Shell’s status marks are a coloured rule rather than a filled pill. A block of colour has to be as wide as the word inside it, and a column of them reads as a wall of colour competing with the shell names beside it. Same seven columns, so lists still line up.
  • Room under the Shells heading in the sidebar, so the title reads as a heading and not as the first item of the list.
  • The panel shows the conversation you are in. A project’s shells listed together stopped making sense as soon as two sessions were open. Switching conversations now changes what the panel lists, without stopping anything; s in the console widens it to the whole project and back, and shell_list already took session.
  • Shells end with the window that started them. lifecycle.onExit defaults to stopMine, so closing OpenCode stops its own shells — a window closing counts only once both halves of the plugin have gone, so quitting one of two open windows never touches the other’s. keep restores the old behaviour of leaving them for the next window.
  • lifecycle.orphanAfterMinutes (60 by default): a shell no window has been connected to for that long is stopped, so nothing runs for a week because everyone who knew about it has gone.
  • /shells-stop stops the shells in view; /shells-stop-all stops every shell in the project and confirms first when that reaches conversations you are not looking at. “Everything I can see” and “everything, including what I cannot” are different intentions.
  • A shell started by hand had no session recorded, so the session-scoped panel did not list it — and attaching then asked the daemon for output from an offset past the end, which crashed the handler and left the console empty. Manual shells now belong to the conversation they were started from, attaching looks in every shell rather than only the listed ones, and an offset past the end is read as “only what comes next” instead of an error.
  • watch on shell_start takes a rule object ({ done, fail, ok, idleSeconds }), not only a preset name. It read as documented before and was not. A rule that arrives as JSON text is parsed as a rule too — including the under-escaped JSON a model writes when the patterns are regexes ({"done": "\d+ passed"}) — instead of being passed on as a preset name nobody has.
  • Watching a command no preset matches no longer fails: the shell is watched for dying (preset exit), so sleep 300, a deploy script or any quiet process gets crash detection without patterns.
  • An ended shell says why it ended and who ended it, instead of “killed by SIGTERM”: “stopped: hit its time limit”, “stopped: no output for its idle limit”, “stopped by you, from the shells panel”, “stopped by the agent”, “crashed with exit code 3”. ShellInfo carries stopReason and stoppedBy (protocol 1.3), and the panel shows the short form.
  • Clearer watch errors: an unknown preset now says no watch preset named "x" and points at both shell.presets and custom rules.
  • Configuration. One file, read by both halves of the plugin: ~/.config/opencode-cockpit/config.json, then <project>/.cockpit.json, then plugin-entry options, merged key by key. Define your own shell kinds (regex → name, also filterable in shell_list), your own watch.presets, defaults applied to every shell the agent starts (watch, logFile, timeoutSeconds, idleTimeoutSeconds, notifyOnExit), what may interrupt the agent (notify), how much context the plugin spends (guidance, listRunningShells), and the interface (ui). watch.auto attaches a matching preset to every new shell; it is off by default. An invalid config file is ignored rather than fatal.
  • Watchers. shell_watch, or watch on shell_start, follows a never-ending process and messages the agent only when its health changes (“tsc: ok → fail” with the offending line), never while a run repeats the same result. A watched process that dies is reported as a failure, so a crashed dev server no longer goes unnoticed. The panel, sidebar and console show the health (tsc ✓, vitest ✗), and shell_list includes it.
  • A watch rule is three regexes — done, fail, ok (plus idleSeconds) — not a parser. About 35 presets ship for common tools (tsc, eslint, biome, prettier, mypy, ruff, vitest, jest, mocha, bun test, deno test, pytest, rspec, phpunit, playwright, cypress, vite, next, nuxt, astro, angular, webpack, esbuild, tsup, turbo, metro, storybook, cargo, go, dotnet, gradle, maven, docker compose, terraform), picked automatically from the command; anything else takes its own patterns.
  • An update notice: the plugin checks the registry at most once a day and offers /cockpit-update, which clears its cache entry so the next start installs the new version. OpenCode resolves an unpinned plugin spec only once, so installs never moved forward on their own.
  • bun run release <patch|minor|major|x.y.z> [--push] bumps every package, promotes the changelog and tags, so releases stop being a manual edit.
  • Log search in the console. / filters a shell’s scrollback to matching lines, keeping line numbers and highlighting matches; backspace clears the filter. Filtering runs in the daemon.
  • Colours. The panel and console paint the colours programs actually print, instead of stripping them.
  • Limits. idleTimeoutSeconds stops a shell after that much silence (never a default: healthy dev servers are idle), alongside the existing timeoutSeconds wall-clock limit. Both explain themselves in the shell’s summary.
  • Log files. logFile writes a shell’s clean log to ~/.cache/opencode-cockpit/logs/<id>.log, so history survives the in-memory buffer.
  • Shell kinds. Shells classify themselves from their command (server, tests, build, watcher, task); shell_list filters by kind, so “which servers are up?” is one call.
  • Package internals are grouped by role: core/ (pure logic shared by both halves), agent/ (the server plugin and one file per tool), tui/ (components/, state/, lib/), and the wire schemas split by concern. No behaviour change, but features now land in one obvious place.
  • Console keys now show only what applies: no sidebar-only “show all”, no “clear finished” without finished shells, and [ ] cycles every shell rather than just the unfolded ones.
  • The TUI half rendered one frozen frame when installed from npm: the panel, console and sidebar appeared but never updated, while keybinds, RPC calls and shells all worked. OpenCode compiles plugin JSX with OpenTUI’s Solid transform, whose Bun plugin skips every file under node_modules — where an installed plugin always lives — so published .tsx produced components with no reactive tracking. Every package now publishes JavaScript compiled with that same transform. A local checkout was never affected, which is why 0.1.3 and 0.1.4, which guessed at dependency layout, did not fix it.
  • Clicking a shell in the sidebar or panel needed the mouse button held down: the console opened on press, and the release landed on the dialog backdrop, which closes it. They open on release now.
  • solid-js and @opentui/* are no longer shipped with the plugin. The compiled code imports them by name and OpenCode rewrites those imports to its own instances, which is what keeps reactivity and the keymap shared with the host.
  • The sidebar shows at most 5 shells (sidebarRows), then ▸ N more; expanding caps at 12 and points to the console. The panel keeps its tabs to what fits the window. A hundred shells can no longer push the sidebar off screen.
  • Every package builds through one script (bun run build) and publishes dist/.
  • bun run smoke:tui drives a real OpenCode against the packed plugin and fails if the panel stops updating; bun run pack:check fails if a published TUI entry is not Solid-compiled or if solid-js/@opentui/* are installed with the plugin.
  • @opencode-cockpit/shell pinned solid-js to 1.9.15, one patch version above the 1.9.12 that @opentui/solid and @opentui/keymap require as a peer. A published install (plain npm install, as the opencode plugin installer runs) can’t satisfy both from one copy, so it nested a second private solid-js under @opencode-cockpit/shell. Solid’s reactivity is instance-local: the docked panel and console read signals from the nested copy while @opentui/solid’s render bridge tracked the hoisted one, so the panel painted once on open and then never updated again — keybinds and shell output all reached the daemon fine, nothing ever reappeared on screen. Pinning solid-js to the exact version the peer requires (1.9.12) collapses both back to one instance. Bun workspaces (bun install from source, bun run pack:check) tolerate the mismatch by deduping anyway, which is why this didn’t reproduce there — only a real npm install split it.
  • Shell tools accept a shell’s name instead of its id, e.g. shell_read name="DB Monitoring"; ambiguous names return the candidates.
  • shell_list filters by text, status and session, and shows which session (by title) or the user started each shell. The agent’s system prompt marks shells from other sessions.
  • opencode-cockpit no longer lists @opencode-cockpit/client as a runtime dependency (it comes through the features that use it).
  • @opencode-cockpit/shell declared @opentui/core, @opentui/keymap, @opentui/solid, and solid-js as peer dependencies, which the opencode plugin installer does not install. A fresh install of the published package silently dropped the TUI half (no docked panel, no keybinds), while the server half kept working. They are now real dependencies.
  • Features ship as separate plugins: install everything with opencode-cockpit, or only @opencode-cockpit/shell. opencode-cockpit accepts features to switch features off and per-feature options under the feature’s name (top-level Shell options from 0.1.x still work).
  • A feature configured twice (bundle and standalone) loads once, with a warning naming the entry to remove.
  • An outdated daemon is only replaced by clients running newer code, so plugins at different versions sharing one daemon no longer replace each other.
  • Packages published as 0.1.0 depended on internal package version 0.0.1, which does not exist, so opencode-cockpit@0.1.0 could not be installed. Use 0.1.1.
  • Releases now fail before publishing if a packed package pins an internal dependency to a version other than the one being released.
  • shell_wait / shell.wait with a pattern now matches a prompt that was already on screen (no trailing newline) before the wait started, instead of timing out.
  • Shell: background PTY shells hosted by cockpitd, shared across OpenCode windows and surviving restarts.
  • Agent tools shell_start, shell_wait, shell_read, shell_send, shell_list, shell_stop, shell_restart, with permission prompts through OpenCode’s bash rules.
  • Wait conditions: output pattern (including unfinished prompt lines), open port, idle output, exit.
  • Clean agent logs: escape sequences removed, redraws collapsed, repeated lines folded, cursor-based reads and grep.
  • Exit notifications to the session that started a shell.
  • Reuse of finished shells for repeated commands within a session.
  • TUI: docked shells panel, sidebar section, keyboard-first console with typing mode, log and details views, status badges, folding of finished shells, clear finished.
  • Daemon lifecycle: on-demand start, single instance under concurrent starts, idle shutdown, replacement of outdated idle daemons, orphan reaping after crashes.