Skip to content

0.9: Trail, one settings shape, setup as skills - #35

Merged
Codestz merged 90 commits into
mainfrom
feat/0.9
Oct 4, 2026
Merged

Codestz merged 90 commits into
mainfrom
feat/0.9

Conversation

@Codestz

@Codestz Codestz commented Oct 4, 2026 •

Copy link
Copy Markdown
Owner

0.9.0 — Trail, one settings shape, setup as skills. Merged with a merge commit to keep the history.

What 0.9 is

Trail — a new bay (@opencode-cockpit/trail). The agent records what a conversation creates or changes outside the repo — PRs, tickets, doc pages, deploys — with trail_add; Cockpit keeps, groups (by ticket, with for) and shows it: a sidebar block, /trail (this conversation / all conversations in the project, search, open, go to the conversation, copy, markdown), /link to add one by hand, and trail_list so any conversation can answer "what did we ship for COM-1736?". Guidance in the system prompt plus a one-line reminder when a command's output holds a PR link not yet recorded; the agent decides.

One settings shape. One JSONC loader (comments and trailing commas no longer drop the file), one section per bay, the same shared keys everywhere (enabled, keybinds, sidebar, sidebarRows, hideWhenEmpty), one sidebar order list. Old names are not read: each shows a ! notice pointing to /cockpit-setup.

Setup as skills. /cockpit-setup and /status-setup load shipped skills; cockpit_settings reads the live state (bays, values and where they came from, notices, OpenCode's own sidebar blocks per version); an opt-in second phase tunes Cockpit to the project and writes one marked ## Cockpit conventions section in AGENTS.md (cockpit_conventions).

Status. The sidebar table is the default (spend/avail/git built in); override changes a row by name without copying the preset; git counts uncommitted work; diagnostics in the table; preview reads a config on stdin.

Fixes and the rest. #34 (diagnostics flagged every MCP server on OpenCode 2); Review renders image diffs (metadata, pixel diff, half-block preview, o opens both); blocks say none yet when empty; subagent rows line up and name their agent; Shell's "show fewer"; OpenCode 2's background service keeps old plugin code — dev:install restarts it, doctor and update say so; per-bay guidance tightened and measured with real agent turns (AGENT=1 smoke).

Full list: CHANGELOG.md, Unreleased.

Also in

  • Trail without automatic tracking: the agent records, /link adds by hand, a reminder line nudges.
  • Default keys that no longer shadow OpenCode's own (ctrl+x d/j/k), the same on both versions.
  • A toast when OpenCode 2's background service runs an older Cockpit than the window; setup's final check includes each bay's own validation.
  • Refactors (client/src/setup.ts 925 → 51 lines + setup/; Trail's interface split; one JSONC parser, one bay list, one link opener), checked against a byte-for-byte baseline of 60 preview renders.
  • Docs synced (README, site, package READMEs); a real Trail recording; Dependabot Bump @opencode-ai/plugin from 1.18.31 to 1.18.33 in the opencode group across 1 directory #28–Bump @babel/core from 7.28.0 to 7.29.7 #30 applied.

Checks so far

bun run check (2170 pass), pack:check (Trail included), AGENT=1 bun run smoke:tui on OpenCode 1.18.32 and 2.0.18 (behaviour measured with real agent turns), CI on macOS and Linux, and a live check by the maintainer on the final install.

🤖 Generated with Claude Code

Codestz and others added 30 commits October 3, 2026 09:14
Shared keys (enabled, keybinds, sidebar, sidebarRows, hideWhenEmpty) spelled
once, defaults per bay, type-checked with a notice. The top-level sidebar list
is the only order (default status, subagents, shell, trail, trust at 110-150,
between OpenCode 1's Context and MCP). Old pre-0.9 names are detected, not
read: each is a notice saying to run /cockpit-setup. Unknown sidebar entries
name the closest bay. Never throws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Presence over silence: the muted row sits in the first item's slot, so empty
to one item keeps the height; hideWhenEmpty draws nothing. EmptyRow element,
and a preview of empty blocks and settings notices.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v1 tool.execute.after with MCP output read from content[]; v2
tool.hook("execute.after") from content[].text, skipping Code Mode's outer
execute call. A listener's failure is logged and never reaches the call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Doctor prints every settings notice as a fix line, and a file it calls fine
is one the bays can use. Trust takes its section and order from the loader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent reads

A new package, @opencode-cockpit/trail, version-agnostic and with no OpenCode adapter yet:
an append-only events.ndjson per project outside the repo (Trust's scheme: torn last line,
several writers, reconcile on load), one record per thing merged by url else ref, deleted
conversations marked, links cleaned of secrets, the system read from the host, grouping by
what a record is for, and the tool descriptions, guidance and per-request lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pure rows on the design system, drawn by `bun packages/trail/src/cli/preview.ts` for every
sample (empty, one, busy, long, a project with a deleted conversation) at any width: the
sidebar's heading, none-yet slot, ↗ and + N more; /trail's two tabs, found-not-recorded rows,
search, a window that keeps the cursor in view, and the agreed footer with esc last.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ened

Binary detection by git's rule (a NUL in the first 8000 bytes); both sides read as bytes
(cat-file blob, never through UTF-8), with a 32 MB binary cap beside the 400 KB text cap, so a
screenshot is in the review instead of skipped or shown as U+FFFD.

- Header readers for PNG/APNG, JPEG, GIF, WebP (VP8/VP8L/VP8X), BMP: `PNG 2880×1800 · 807 KB → 789 KB`;
  other binaries `binary · 12.3 KB → 14.0 KB`.
- PNG and GIF decoded in slices off the draw path (async inflate, then generators that yield
  every ~256K pixels); pixel diff for same-size pairs with % changed, bounding box and a mask.
- Half-block preview, before and after side by side, changes lit; drawn through the row pool with
  a new exact `background` on Run. Thumbs and diffs cached by content hash.
- `o` opens both versions in the system viewer: old bytes into a temp file, Bun.which on the
  spawn's PATH, detached spawn with its error reported in the footer; temp dir cleaned on close.
- `[?] Keys` screen; `ctrl+p` (the host palette's key) makes the review step aside.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…write

The trim only removed what was identical at the very ends, so edits at lines 40 and 2,900 left a
2,860-line middle past the alignment table's cap, drawn as every line out and back in. A stretch
past the cap is now split on lines unique to both sides (patience diff's anchors, longest run in
order) and each piece aligned on its own; only a stretch with no such line is drawn as a rewrite.
Below the cap nothing changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… shows

Shrink and compare yield twice as often: the longest event-loop stall analysing a 2880×1800 pair
went from 13 ms to 9 ms (the pair blocked 114–124 ms inside OpenCode before slicing). README:
`o`, `?`, ctrl+p while the review is open, and the images section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eps its fix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, and /status-setup

- Settings through baySettings("status"): the `status` section only. `statusline`, root keys,
  a bay-level `maxRows` and `sidebarOrder` are old names — `!` rows, not read. `sidebarRows` is
  the column's cap; `sidebar: false` means the bottom; order from the top-level list.
- The `sidebar` preset is the budget table, and the default surface is the sidebar. Its rows are
  built-ins now: title, in, out, cache, write, sep, spend, avail, git, plus `context` style
  `solid` and `tokens` style `row`. spend/avail read a proxy's file and draw nothing without
  one; a hairline draws only between two rows.
- examples/sidebar.ts, sidebar-full.ts, sidebar-budget.ts removed; a config pointing at one, or
  naming a preset that does not exist, gets a `!` row rather than a blank column.
- Status draws cockpitNotices (unreadable file, unknown top-level name, unknown sidebar entry)
  and its own, wrapped by warnRows to the measured sidebar width.
- /statusline is /status-setup; /statusline stays one release as a twin that toasts the new
  name and still briefs the agent. The brief speaks of the `status` section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eCheck is an old name

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ow fewer with one shell

Shell reads through baySettings: the file's shell section and the plugin entry,
JSONC, the order from the top-level sidebar list. Root keys and ui.* are no
longer read; each is a ! row in the Shells block. ui.historyMinutes is
hideFinishedAfterMinutes. With no shells the block draws its heading and
none yet unless hideWhenEmpty. The fold toggle shows only while folding hides
something, and an expansion the list outgrew folds itself back (and is no
longer kept across restarts).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both halves read the subagents section through baySettings (sidebarRows,
hideWhenEmpty, hideFinishedAfterMinutes, hideNestedAfterSeconds, guidance,
keybinds); old names are ! rows in the block. The block is present when
empty. Every row names its agent, muted, general too; the agent shortens
before the title, and a run with no title is named by its task's first
words. The pane has a [?] Keys screen like Review's and Trust's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
variant and source are read through baySettings from the files and the
plugin entry; an unknown value is the default and a notice, said once as a
toast since Review has no sidebar block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seen on OpenCode 1 with hideWhenEmpty: the block's empty box still got the
host's gap between blocks, so the sidebar had a blank row for nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…till shows the fix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ty net; the sidebar, /trail and /link

The agent half (dualServer): the two tools, GUIDANCE on every request with the
conversation's produced line and the links seen in what it ran, rebuilt from
events.ndjson every time; subagents' records go to the root session and name the
subagent; each record keeps the conversation's title. The interface half: the
sidebar block at the settings' order (client emptyBlock, warnRows for notices),
rows that open the page or /trail, /trail on both versions, /link, and the
conversation's stored history read for finds, offered and never added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st end in trail_add

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ols, settings, team conventions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…turn is about the PR

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e settings, the old names and OpenCode's own blocks

Registered by the first Cockpit entry in a window (dualTui), listing the bays that claimed
themselves there; defaults come from what each bay hands baySettings. From home on OpenCode 2 it
opens a conversation for the brief; while the agent is busy it queues rather than steers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er's rule

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…it opens a conversation, behind a reply it queues

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codestz and others added 25 commits October 3, 2026 21:00
…each palette step waits for the last one's toast to go

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ong; Trail's time says when; Trail measured two of three

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ist, its history scan and `a` are gone; the agent's reminder stays; /link is taught as /link, then the link

The per-request "seen in output — record it if you created or changed it" line, fed by toolAfter
on shell and MCP output, is kept: the agent decides. /link opens its prompt; the one-line
`/link <url> note` form was OpenCode sending unknown slash text to the model, so no doc teaches it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent ctrl+x k — the same on OpenCode 1 and 2, and none of them OpenCode's own

OpenCode 2 binds <leader>w (close tab) and <leader>i (image attachments), and both versions bind
<leader>r (redo). A test holds every Cockpit default unique, bound by its bay as the catalog says,
and clear of OpenCode's default leader letters. keybinds restores the old keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and build read whole

One width per block — the longest agent shown, cut with … past eight — and no column when it would
leave the titles fewer than six cells.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lder Cockpit

The agent side records which install it loaded (agents/<pid>.json in Cockpit's home: version and
when its package.json was written, read once per process). A window reads the record of the pid in
OpenCode's service.json and, when the service's install is older, shows 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 restarts it; says nothing when it cannot tell (no
service, no record, a record from another pid). Doctor reads the same record instead of comparing
the service's start time with the install's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ne" means no ! row

The audit: after /cockpit-setup the agent said "Notices now: none", and after a restart Status
still warned that override "gti" matches no segment and Trust that threshold "3" is a string.
cockpit_settings and doctor saw only the loader's notices. Now each bay's come from the bay
(client checks.ts): its keys' kinds through the loader with its defaults, and a bay's own check
where it has one — Status offers statusNotices (presets, surfaces, overrides, each naming the file
its key is in) from its agent side, and the bundle's doctor loads it too, guarded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ded Status and counts what is uncommitted

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l Trail and Subagents rows, Review and Updater settings, eight Shell tools

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ase, keys, OpenCode's own blocks per version, doctor's Service check, Review images, recounted tools

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mples follow the release

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ient/brief

setup.ts (925 lines, eight concerns) becomes the ./setup entry that gathers
setup/{names,previews,installs,host-blocks,report,text,tune,server,palette}.ts;
every name it exported is still exported. briefAgent, a general "send this line
to the agent" helper, is client/src/brief.ts with its own ./brief subpath, and
Status's interface imports it from there.

host.ts imports only setup/palette.ts and server.ts only setup/server.ts, so an
interface entry no longer loads the settings report, AGENTS.md writes and git
scans of the agent side. No behaviour change: every preview renders the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
updater/src/core/jsonc.ts was a byte-for-byte copy of client/src/jsonc.ts. Client
exports it as @opencode-cockpit/client/jsonc; the Updater's config reader and
doctor import it, updater/core still re-exports parseJsonc, and its tests move
to client with the parser.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le and doctor

client/settings' BAYS is the list: the bundle's FEATURES is it, and doctor's
copy is gone. Doctor's SERVER_BAYS stays its own (which bays it checks have an
agent half) but is typed with client's Bay, and a test checks each publishes
./server.

client/src/plugin-entries.ts (./plugin-entries) reads Cockpit's entries in
OpenCode's plugin lists for /cockpit-setup and doctor alike: pluginEntries
replaces doctor's specsIn, bayOf moves from doctor, and baysOfEntry is written on
bayOf. Same answers as before for every entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… no copy

Shell, Review, Subagents, Trail and Trust each kept their own DEFAULT_KEYS beside
the catalog's, and a test compared the two by reading the bays' source. Each now
reads defaultKeys("<bay>") from @opencode-cockpit/client/catalog; why each key is
free on both OpenCodes moves to the catalog with the keys, and the test now checks
every bay reads the catalog and holds no copy. Same keys as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ockpitNotices, lastStep, refusedText

No production caller of any of them (checked with rg across packages and
scripts): client/sidebar's deprecated sidebarOrder and sidebarList (bays take
order from baySettings; their tests go with them, settings.test covers the
order), Status's loadStatusConfig, Status's private copy of cockpitNotices (it
uses client/settings' now), Trail's lastStep and refusedText, and the key Trail's
addByYou returned to a caller that never read it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tui/index.tsx (599 lines, one closure) keeps the wiring: settings, the
conversation on screen, the palette's commands, the sidebar slot and the tick.
paint.ts paints both surfaces into their signals (draw folds a turn's changes
into one paint), actions.ts reads and appends the trail file and does what a
person asks (open, copy, /link, copy as markdown), and dialog.tsx is /trail —
opening it, its keys, the search intercept. What they share is one Live object
(state, root, trouble, inSidebar, titles) and the dialog's state. Same calls in
the same order; every preview renders the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Recognising the pre-0.9 spellings (statusline, Shell's root keys and ui group,
Status's root keys, <bay>.sidebarOrder, the renamed time keys) is one file now,
so removing it in 0.10 is deleting that file and its two calls in settings.ts
(oldInSection in a bay's section, oldAtTop at a file's top level). OLD_NAMES is
still exported from client/settings. Same notices, same order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ages

Review (core/viewer.ts) and Trail (core/open.ts) each decided which program
opens a thing, and had drifted. client/src/opener.ts (./opener) is the one now,
with its argv unit-tested per platform; Trail's core/open.ts is gone (unreleased).

Deliberate changes, as the review asked:
- Review now tries macOS's /usr/bin/open before PATH and honours COCKPIT_OPENER,
  as Trail did. A live check stubs Review's `o` with COCKPIT_OPENER now: a PATH
  shim is passed over on macOS.
- Windows: start's title is an empty argument (Review's form); Trail passed '""',
  which Node quotes as \"\" and cmd does not read as an empty title. & is escaped
  as ^& only where Node leaves the argument unquoted (no space, tab or quote).
- Windows falls back to a bare `cmd` when PATH has none (Trail's), so Review no
  longer says "cmd is not on PATH" there.
Trail still hands over only http(s); its openUrl is unchanged otherwise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…set-typescript 7.29.7

The bumps Dependabot proposed in #28, #29 and #30, applied on feat/0.9 (which they could not merge
into: Trail's package is not on main).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bay without its dist

build empties every dist/ before compiling; v1 and v2 smoke in parallel packed Trust in that window,
so OpenCode 2 said "1 plugin failed" and Trust's palette commands were missing. The harness, not
the product: every lone run passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le characters

OpenCode 2 refuses packages/opencode from the checkout (a second OpenTUI and Solid), so a take on
it packs this checkout and installs it under the take's own directory, the way the smoke does, and
starts a private server instead of attaching to the background service. Plugins go under
`plugins` in opencode.json and cli.json there.

Each PTY chunk was decoded on its own, so a character split across two chunks recorded as a pair
of U+FFFD — a spinner frame, a `›`. One streaming decoder now. The shells' daemon the take
started is stopped with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rail leads back to it

tapes/trail.ts on OpenCode 2 with the free Zen model: a ticket's fix, a commit, gh pr create
(a stub on PATH), and trail_add without the prompt mentioning Trail; then /trail, a second
conversation answering from trail_list, All conversations and g. media/trail.png is now a still
from the take rather than the preview.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…plays the cast, the still is the /trail dialog

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Codestz
Codestz marked this pull request as ready for review October 4, 2026 04:50
@Codestz
Codestz merged commit a5176db into main Oct 4, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant