Repository navigation
Merge/upstream v0.2.27 - #7
Merged
Merged
Conversation
…lity fix(ui): stabilize panel resizing and shell alignment
…-reasoning-layout Fix live-follow transcript anchoring and refine message metadata
…ation Preserve transcript viewport across chat switches
AgentEvent::ReasoningDelta was dropped at the doc fold for every harness — no client ever rendered thinking. It now folds into a new 'reasoning' doc part and renders as a collapsible Thinking block on desktop: - doc: MessagePart::Reasoning rides a dedicated 'reasoning' LoroText field (never 'text' — old desktop readers' unknown-kind fallback renders 'text' as prose, so thinking on this field degrades to an invisible empty part; old iOS drops unknown kinds). Streaming appends RLE-merge like text, the WatchDocMessages hot path carries reasoning growth as TextAppend deltas, oversized thinking splits at the continuation cap, and salvage recovers the field. - ui: a Thinking row (tool-group header recipe: chevron tile + quiet label) auto-opens while it is the live tail of a streaming reply and collapses once settled; a user toggle overrides either way. Body renders dimmed along the guide rail. - edge TS mirror: 'reasoning' kind in the doc part codec + split gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
opencode's own ACP layer settles a prompt on the FIRST session idle it
observes — uncorrelated with the submitted turn — and ignores
session.error and session.idle entirely (packages/opencode/src/acp/event.ts),
which is exactly the 'says it's done when it's not done' bug; it also
drops all subagent traffic. The desktop app doesn't use ACP, and now
neither do we.
OpencodeHarness speaks the same v1 wire the opencode desktop app does:
spawn `opencode serve --port <free>` with a per-run Basic password, drive
POST /session/{id}/prompt_async, and read the /global/event SSE bus.
- Done is truthful by construction: only session.status{idle} for OUR
session settles the turn (deterministic_turn_end retires the engine's
quiesce watchdog). No quiet-settle heuristics.
- Thinking streams: reasoning parts/deltas map to ReasoningDelta.
- Provider failures are visible (the zeronsh#169 class): retry status surfaces an
error chip at attempt 3 and aborts at 8 instead of retrying forever; a
turn with no sign of life within ZERON_OPENCODE_STALL_MS (60s) errors
out with the provider hint.
- Subagents ride the same bus natively: task parts register chips, child
sessions bind by spawn metadata (title-match fallback), traffic streams
tagged, the completing task part settles the chip. Child permission asks
auto-approve too (the ACP layer silently dropped them and subagents hung).
- Live model discovery off GET /provider ({providerID}/{modelID} ids;
effort ladder maps to the model's advertised reasoning variants and rides
prompts as `variant`). Slash commands from GET /command route through
the command endpoint. Resume reuses the durable server-side session.
The ACP opencode spec, the /event sidecar tracker, and the per-run --port
plumbing are gone; grok keeps its own tracker. Covered by a scripted
in-test HTTP/SSE fake (tests/opencode.rs) where the fixtures own the bus
clock — including the foreign-session-idle regression.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live finding (1.18.18): during opencode serve's boot window the socket ACCEPTS but the request is parked — and one parked early enough is never answered at all, so an unbounded send() wedged startup past the whole 300s budget. Health probes now time out at 2s and re-poll; ordinary calls get a 60s bound. The synchronous per-turn command endpoint deliberately stays unbounded (its response can take the whole turn and is ignored — the bus carries the real events), and the interrupt-path abort gets its own 5s bound so a wedged server can't block the event loop. Live-validated end to end against real opencode 1.18.18 + a scripted OpenAI-compatible provider streaming reasoning_content: parent and subagent thinking stream as ReasoningDelta, the task chip binds/settles, and the turn completes only on its own session's idle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live finding on fresh 1.18.21: the v1 bus has NO replay, and a
fast-failing turn (a bad model id errors in ~200ms) can emit its whole
busy → session.error → idle lifecycle before a late /global/event
subscription exists — the driver then saw nothing and fell back to the
60s stall watchdog instead of surfacing the real error. The bus reader
now reports each stream (re)connect after its first frame; the first
prompt waits (bounded 15s) for the subscription to be live, and a
RECONNECT during an active turn re-syncs from GET /session/status so a
gap can no longer swallow the idle that ends the turn.
Re-validated against opencode 1.18.21 installed via the official
installer: the mock subagent+thinking scenario, the bad-model fast
failure (error chip + Done{Errored} in 2s, previously 60s), and a REAL
anonymous OpenCode Zen model (opencode/mimo-v2.5-free) end to end —
reasoning streamed, a real bash tool call executed, usage reported,
truthful Completed settle.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…l-and-thinking opencode: native desktop-app protocol (truthful done) + thinking rendered end-to-end
Field report (v0.2.21): the model picker was slow to open, slow to
scroll, and full of models the user can't run. GET /provider's `all` is
the entire models.dev catalog — measured live at 194 providers / 7,203
models, nearly all needing an API key the user hasn't set; picking one
just fails the run with "Model not found". The response's `connected`
list names exactly the usable set (credentialed + config-declared + the
anonymous Zen tier), so discovery now filters to it (falling back to the
full catalog only when the field is absent/empty — older servers). On
this rig that's 7 models instead of 7,203; the lag was the list.
Also from the same report: opencode emits each provider failure TWICE on
the bus (once bare, once wrapped in an exception-name/stack prefix), so
every failed turn rendered two identical error chips. Consecutive
session.error events whose first lines contain each other now dedupe to
one chip.
Plus a live models probe example (cursor precedent):
cargo run -p zeron-harness --example opencode_models_probe
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nected opencode: picker offers only connected providers' models; dedupe doubled error chips
…provider Field follow-up on the opencode picker work: the picker must handle LARGE lists (not just rely on the connected filter keeping them small), and identically-named models need visible provenance — 'GLM-5.2' exists under 64 providers in the catalog, so a user with several connected providers saw indistinguishable duplicate rows. - The model list is now a virtualized uniform_list: only the visible slice renders, so a 7,203-row catalog opens and scrolls as smoothly as seven (validated live against the full unfiltered models.dev catalog through the real pipeline). The floating scrollbar reads the UniformListScrollHandle's base handle; keyboard nav scrolls via scroll_to_item(Nearest). - The flattened row list is cached per (query, rail, harness, lock, catalog revision): it was rebuilt — with per-row Model clones, ranked and sorted — 2-3x per keystroke and again per render. Favorite lookups use a set instead of a per-row Vec scan. - Rows keep the harness subline and append the model's description when it adds information: opencode ships the provider display name there, so duplicate names read 'OpenCode · HPC-AI' vs 'OpenCode · Nvidia'. The search haystack includes the description, so typing a provider name filters to it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Model picker: virtualized list scales to 7k-model catalogs; rows attribute their provider
Field follow-up on the thinking feature: the standalone 'Thought process'
row didn't animate like the other transcript accordions (its body
mounted at intrinsic height — no analytic height, no tween), and wing
wants thinking INSIDE the combined 'Ran N commands' fold rather than as
its own row between groups.
Reasoning parts now become thought chips in the tool group itself:
- rows_for_entry routes MessagePart::Reasoning into the pending group
(agent/spawn groups stay pure, same as the tool genus rule), so
thought → tool → thought → tool folds into ONE accordion row.
- A thought chip is a synthesized ToolItem ('Thought process' header,
chat bubble icon) whose text is an output-style detail: soft-wrapped
at word boundaries into lines, capped like tool outputs (a STREAMING
thought keeps the tail — the fresh thinking is the signal). Line-based
details have ANALYTIC heights, so the group's existing 200ms fold
tween covers thoughts with zero new animation code — the smoothness
ask is satisfied by construction.
- A live thought chip defaults open (the group auto-opens as the live
tail already); it settles closed once text or a tool follows. User
toggles override either way via the existing per-chip fold state.
- The collapsed summary names the thinking: 'Thought process' for a
thought-only group, 'Thought 3 times · Ran 2 commands' when mixed
(thought chips are UI-synthesized, so the shared view summary never
sees them and the terminal viewport is untouched).
- RowKind::Thinking, its renderer, and the special top-gap case are
deleted — one fewer bespoke row kind.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…oups Thinking rides the tool-group accordion (same fold, same tween)
Field report (multi-device): new sessions never appeared in a work
laptop's sidebar while old sessions kept updating, and on iOS opencode
sessions wore the claude mark and opened showing claude/Fable.
Two independent gaps, one cause — the fleet learned a harness id
('opencode', v0.2.10) that older surfaces don't know:
- Desktop: the typed row decode (read_kind → RawChat) SKIPPED any row it
couldn't parse, and ChatConfig.harness is a closed enum — so a client
older than the newest harness id drops every such chat row wholesale.
Exactly 'old rows update, new rows invisible'. The config field now
decodes leniently: an unknown harness/reasoning/sandbox value costs
the CONFIG (row renders with generic defaults), never the row. Same
rule as transcript salvage: a missing field costs at most what it
carried. Regression-tested with a future-harness row.
- iOS: HarnessCatalog and BrandMark had no 'opencode' entry, so the
default arms answered with the claude mark and the claude model list.
Added the label, the mark (opencode-mark.svg's frame path), and a
static model fallback (the anonymous Zen tier; a reachable host still
answers listModels with live discovery).
The laptop itself needs an app update to pick this up — the tolerance
protects every FUTURE skew, it cannot reach builds that predate it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Chat rows survive configs from newer peers; iOS learns opencode
Field incident, root-caused for real this time (the earlier stale-version theory was wrong — the affected laptop ran v0.2.24): new sessions created on one device never appeared in another device's sidebar, while updates to already-known rows kept flowing. The hole is deterministic on the HTTPS transport (used exactly when a network strips WebSocket upgrades — zeronsh#168's dual transport, v0.2.12): one sync cycle pushes before it pulls, the push ack set the cursor to OUR batch's seq, and the pull then fetched 'since' that jumped cursor — permanently skipping every batch other devices wrote since the last sync. A single local write (a seen-marker sufficed) while behind made peers' new chats invisible forever; rows touched later self-healed, which is why statuses kept updating. - ack_batch no longer advances the cursor (the ack's seq is our own batch; the cursor only moves with actual row payloads — re-pulling our own batch is an idempotent LWW no-op). - apply_rows guards contiguity: a broadcast past a seq gap (hibernating DO, half-dead socket) applies its rows but HOLDS the cursor and the client reconnects — the fresh hello backfills the window. - Persisted snapshots gain a resync epoch: cursors written before this fix may already have jumped past invisible rows, so the first load after upgrading zeroes the cursor once and pulls full state (rows are kept; local-only rows re-seed through the existing full-state path). - iOS mirrors all three (its WS ordering has masked the ack bug so far; gap-hold converges on the next natural reconnect). Verified against production data: the 'missing' chats and their space rows are complete and correct in the registry — the failure was purely the reader's cursor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Registry cursor can no longer jump over unapplied rows (missing-sessions incident)
Two residuals from the missing-sessions audit (v0.2.26 follow-up): - sweep_orphans ran on whatever the local replica held, including a replica that had not heard from the server that boot (gapped cursor, pre-heal, offline at boot). On such a view "space row absent" means "not received yet", not "deleted" — and the sweep's deletes are global and permanent. The sweep now gates on a `synced` latch that flips once a server state applies this process (WS hello answer or HTTPS pull); local-only hosts (no edge configured) are exempt since their table is the whole truth. - spawn_offline_sync silently ignored a push ack it could not parse (captive portal / proxy bodies), leaving the batch marked in-flight. With no socket session there is no disconnect to un-mark it, so the write was stranded until process restart. An unreadable ack now counts as a failed push: the batch becomes pushable again and the next cycle retries it. `synced` also rides RoomStatsSnapshot / SyncStatus for observability. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…strand Orphan sweep waits for server truth; unreadable HTTP ack retries
Keep Studio, the Pi native driver, the self-hosted WorkOS edge, the queued prompt tray, and the Grok harness work. Take upstream's native OpenCode HTTP/SSE driver, reasoning parts, virtualized model picker, registry cursor/ack fixes, transcript viewport restore, and pane-resize. ACP is Grok and Hermes only. Pi and OpenCode both stay native. Conflicted files were combined rather than taking either side wholesale.
write_user_message treated an existing queued id as a no-op, so drain never flipped the row to Complete. WorkOS-unset boot without a bearer also failed to enter development, which broke the offline-dev test. Hang-turn e2e scripts no longer emit Done before interrupt, so a steerable harness cannot park and empty the queue early.
Wire the model-picker scrollbar drag-move listener so grab_offset is read. Drop unused RunRequest bindings in test harnesses, stop importing CHAT_PANEL_MIN into the shell lib, and allow TILE_CORNER_RADIUS on non-macOS where hover surfaces are not painted.
Right-pane drag and target width now keep a 300px chat floor and use takeover width instead of the old 52% cap. Opening the model picker revalidates catalogs and retries a cold OpenCode probe. Studio's titlebar uses the same right inset as chat. PARITY.md lists Cursor, Pi, and OpenCode as native drivers.
Queued user entries were painted as transcript bubbles the moment they were parked. Skip them in rows_for_entry, and add an e2e that the follow-up stays Queued and unsent until the live turn commits it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.