Commit graph

1331 commits

Author SHA1 Message Date
Pascal André
3be331dfa9
fix(transcript): add up each agent message's tokens over its cleanup selection (#1063)
Some checks are pending
Publish help / build (push) Waiting to run
Publish help / deploy (push) Blocked by required conditions
#1059 showed a token line only on agent messages whose model call ended with text, so in OpenCode V2, where an agent message is usually followed by tool calls in the same native message, most messages had no line.

Every agent message now shows a token line at the end of its final text, the row that already carries "delete tools and reasoning". It adds up the message's own native usage and that of every message holding a tool or reasoning part the cleanup selects, reusing that selection instead of a separate projection. Tool-only steps get no line and no line sits between tools, so technical groups stay whole and their Delete stays available; failed messages that show text keep their line; prompts, queued prompts and omitted-part gaps follow the cleanup scope because it is the same selection. A message whose text is followed by tools also counts in the next message's line, since its tools belong to that selection. The line renders inside the content row, so settling or the Tokens filter never shifts the positionally rendered items after it. projectUsageLines, the reserved usage item, its cache signature, hasVisibleUsage and the border sentinel are removed; sumMessageUsage is the only helper.

Validation: sumMessageUsage unit test; usage-metrics covers a line on a text-then-read message placed before its tools, an answer adding up the messages holding its selected reads, one deletable read group, line geometry, the Tokens filter and a settling text-then-tool step that keeps its expanded tool element (fails on the earlier insertion). usage-metrics, search-highlight, status-tokens, full-source-copy, reasoning-collapse, history-navigation, system-messages and render-cost browser tests, session-actions and message-part-grouping unit tests and the UI typecheck pass. Reviewed independently, including by GPT-6.1 Sol (high).
2026-10-21 03:59:47 +02:00
Pascal André
00f39ada16
fix(timeline): space every marker evenly (#1062)
The rail doubled the gap wherever a tool marker started a different message, a collapsed tool group ended or a prompt followed an assistant with tools: a V1 notion of tools belonging to their assistant message. OpenCode V2 records each model call as its own message, so almost every tool started a new group while tools called together stayed tight, giving irregular, pronounced gaps that meant nothing to the reader. Every visible marker now follows the previous one by one uniform gap, and the message-tool bookkeeping is removed.

Validation: render-cost checks the exact 10,000-marker extent with uniform gaps; timeline-tool-filters, history-navigation and system-messages browser tests and the UI typecheck pass.
2026-10-21 03:28:29 +02:00
Pascal André
da0dc279a7
ci(tauri): stop the refcount negative control at its first crash and warn when a runner never races (#1061)
The Windows runtime handle regression builds a copy with the counter fix reverted and required that negative control to crash within ten processes before the corrected build's five runs. The race is timing-dependent: of the last ten PR builds, five runners reproduced it (1 to 8 crashes in ten) and five never did, so tests-tauri-windows failed about every other pull request, unrelated to the change under test.

The negative control now runs up to fifty processes (about 0.16 s each) and stops at the first native fault. On GitHub Actions, a runner that never reproduces it emits a warning naming its CPU instead of failing; local --baseline runs still fail, so a change to the fix cannot pass on a machine that never races. The corrected build's runs, the accepted fault codes and the clean-teardown check for surviving processes are unchanged; the counter-boundary and vendored-Tao checks guard the fix on every runner. Docs state that corrected runs on a warning machine provide no timing evidence.

Validation: syntax check and two independent reviews; the sample comes from the tests-tauri-windows logs of the ten most recent PR Build Validation runs.
2026-10-21 02:35:20 +02:00
Pascal André
8eefcb3660
fix(transcript): show one token line per agent response so tool groups stay whole (#1059)
fix(transcript): show one token line per agent response so tool groups stay whole

OpenCode V2 records every model call as its own assistant message, and the transcript closed each one with its own token/cost line. Those lines ended technical groups, so a run of commands or reads became a series of one-tool groups, and a group's Delete action, which needs at least two tools, disappeared.

Token lines now follow the scope of the response's "delete tools and reasoning" cleanup, as the transcript showed them before V2: a line closes at the end of each settled step whose last tool, reasoning or text item is response text, summing that step and every settled step since the previous line or prompt. Steps ending with tools pass their usage on to the next response, so no line appears between them and their tools form one group again. A turn ending without a response (interrupted or failed) shows no line. projectUsageLines in message-part-grouping is the pure projection; the message section feeds it the loaded transcript (prompts and native idle records end a turn) and each message block renders its line, with the summed values in its cache signature. The cleanup boundary predicate is shared as isResponseTextPart.

Validation: message-part-grouping unit tests cover folding, unanswered and open turns, prompt boundaries and the closing predicate; the usage-metrics browser fixture gains a tool-only step and checks a single summed line, no line on the tool steps, one deletable read group, the line geometry and the Tokens filter; it fails on the previous per-step lines. full-source-copy, reasoning-collapse, status-tokens, history-navigation, system-messages, render-cost and timeline-tool-filters browser tests, session-actions unit tests and the UI typecheck pass.
2026-10-21 02:29:02 +02:00
Pascal André
f3c0fe671e
fix(timeline): stop adding an empty agent marker after every tool step (#1058)
fix(timeline): stop adding an empty agent marker after every tool step

In OpenCode V2 each agent step is its own assistant message, and most steps only call tools. The timeline's structural index for history knew an assistant message's tool and reasoning counts but not whether it had text, so the projection drew a tool marker followed by an agent marker for every step. For tool-only and reasoning-only steps that agent marker was empty: no hover preview, and clicking it reached a message showing nothing but its tools, or nothing when tools are hidden in Message content. Resident (loaded) messages already dropped such markers.

The outline index now also counts an assistant message's non-blank text parts that are neither synthetic nor ignored, the same text the hover preview shows. The projection draws an assistant marker only when that count (or the resident segments) shows visible text, so a tool-only step keeps its tool marker alone and a reasoning-only step has none. The count is optional in the contract: an older plugin copy still loaded by the daemon reports no count, and its markers stay as before. Persisted indexes saved without the count display immediately and then revalidate every checkpoint once, like the representative tool names. The unused message-timeline-group-* classes, which no stylesheet or test referenced, are removed; the group spacing that still uses tool membership is kept.

Validation: navigation.test covers text counts for text, tool-only and blank/synthetic/ignored/reasoning-only steps; session-outline-projection covers tool-only, reasoning-only, text, unknown-count, shell and resident cases; session-outline-persistence covers validation and the one-time refresh of indexes without counts; the native navigation script now asserts the counts on real native storage (CI). history-navigation, timeline-tool-filters, system-messages and render-cost browser tests, the session-outline tests and the UI typecheck pass.
2026-10-21 01:49:40 +02:00
Pascal André
310a91e52e
fix(hotkeys): name each action's shortcut in its tooltip and make Ctrl+K really clear the composer (#1057)
Shortcut hints belong in tooltips, but only a few buttons appended their binding, each through its own copy of the same expression. A shared withHotkey(label, id) in lib/hotkeys.ts now returns the label followed by the effective binding (or the label alone when unbound), and every tooltip uses it. The header's command palette and chat search buttons, new session, Info, the composer's agent/model/thinking selectors, search previous/next, the home page Browse button and the active instance or sidecar tab's close button now name their shortcut; the collapsed header menu and the composer's history/Clear items show it as their description. Accessible names are unchanged, and a hint appears only where the shortcut performs that action in the control's current context (Ctrl+W only on the active tab).

Ctrl+K and the palette's Clear input command only blanked the textarea's DOM value, so the controlled composer kept the old draft: Send stayed enabled and Enter sent the text the user had just cleared. Both now ask the active instance to clear its shown composer, which dispatches PROMPT_CLEAR_EVENT so the composer runs its own Clear action (undoable, resets history navigation and the picker). The request is never kept pending, so a late clear cannot erase text typed afterwards. The selector shortcuts and Ctrl+K also work in the no-session draft composer, which shows the same selectors.

Validation: header-windows checks the palette, search and selector tooltips with default and overridden bindings, and a new test clears a conversation draft and a first-prompt draft with Ctrl+K (Send becomes disabled) and opens the agent selector with Ctrl+Shift+A in both states; it fails on the previous shortcut code. header-windows, shell-navigation, tab-chrome, hotkeys-settings, session-interrupt, project-tab-close and composer-alternate-send browser tests and the UI typecheck pass.
2026-10-21 01:34:54 +02:00
Pascal André
f5c5eefefc
chore(opencode): qualify and recommend runtime 2.1.1 (#1055)
Recommend the qualified external OpenCode 2.1.1 runtime while retaining minimum 2.1.0 and the unchanged locked client/plugin build. Align setup/update regression expectations and the recommended-version help tables in all ten locales; preserve authenticated contract recognition and mutation/ownership fences.

Record isolated Windows native runtime, session/account/usage, pruning UI/history, four historical migrations and all eleven recurring Mission journeys. Server units pass 2442 with seven skips, UI units 1635 and focused browser tests 125; final dev Settings/preview-offer deltas and retained-minimum controls are revalidated.

Two review passes found no actionable issue in the PR. Admission/update tests and explicit isolated 1.x-to-2.1.1 replacement pass. The user explicitly authorized administrator merge despite the unrelated Tauri Windows negative-control race test failure, the general test job still running at inspection and the required GitHub review. No stable release is published by this merge.
2026-10-21 01:16:19 +02:00
Pascal André
88532c23bd
feat(composer): queue or send now from the More actions menu, Alt+Enter by default (#1054)
The composer's More actions menu now offers the follow-up delivery Enter does not use: Queue message when Enter steers, Steer now when Enter queues. It is enabled only in normal mode while the session runs and a draft can be sent, and it goes through the same delivery resolution as the shortcut, so an idle session always sends normally.

Alt+Enter becomes the default alternate-send shortcut on Windows and Linux, with Ctrl+Shift+Enter kept as a second binding. On macOS Option+Return is the native line break in text fields, so only Cmd+Shift+Return sends there; the platform check lives once in hotkey-definitions. The file/command picker now ignores any modified Enter, so Alt+Enter never selects the highlighted item. The menu tooltip shows the effective shortcuts, the help tables note Alt+Enter is for Windows and Linux, and the menu rebuilds only when its enabled states flip.

Validation: composer-alternate-send and composer-picker-enter browser tests (including Alt+Enter with the picker open), hotkey-bindings unit tests for macOS and other defaults, usePromptKeyDown, submitPrompt, hotkeys-settings, help test and UI typecheck pass.
2026-10-21 01:05:23 +02:00
Pascal André
fe27c270fd
fix(timeline): highlight the message's visible marker instead of a hidden tool (#1056)
fix(timeline): highlight the message's visible marker instead of a hidden tool

The transcript reports a message's first timeline marker as active, and that marker is often a tool. Since #1049 a hidden tool marker reappeared whenever it became active, so tools hidden in Message content kept popping into the rail while scrolling, for no visible reason. A hidden tool marker now stays hidden; the active highlight and the rail reveal move to the first visible marker of the same message (activeVisibleId), and a message with no visible marker highlights nothing. Markers with an active permission still stay visible.

Validation: timeline-tool-filters checks that a hidden active tool stays hidden, its message's assistant marker is highlighted, a message without visible markers highlights nothing, and a visible tool keeps its own highlight; history-navigation (61 tests) and the render-cost timeline test pass; UI typecheck passes.
2026-10-21 01:00:01 +02:00
Pascal André
534cd18860
fix(updates): offer a preview release only once all its builds are published (#1053)
fix(updates): offer a preview release only once all its builds are published

Release publication creates the GitHub release first and uploads its builds over the next 40 minutes or so; the complete updater manifest (latest.json) is uploaded only after every platform build. The preview monitor offered the newest published release as soon as it was created, so the update notification appeared before any build existed. It is shown once per version, so no second notification came when the release became installable, and the desktop updater, which requires latest.json, opened the release page instead of installing.

The monitor now offers only releases carrying latest.json, the same rule the native updater applies. A newer release that is still building no longer hides the newest ready one, and the installed release is still recognised for publication order whether or not it has a manifest. A notification therefore appears only when the release is complete, and its install action works.

Validation: dev-release-monitor unit tests cover releases still building, a release becoming ready and an installed release without a manifest; existing feed and lookup tests pass.
2026-10-21 00:41:39 +02:00
Pascal André
64ff1a8fc6
fix(settings): show OpenCode version first and drop the Runtime header (#1052)
fix(settings): show OpenCode version first and drop the Runtime header

Settings > OpenCode now starts with the OpenCode version card (status, install/update and service actions), followed by the executable selection and then the log level card. The executable card loses its separate "Runtime" heading and server-setting badge: the binary selector already carries its own "OpenCode binary" heading and description, so the extra row only repeated it. The startup recovery dialog shares the same card and drops the duplicate heading too. The two unused strings are removed from all ten locales and AGENTS.md records the new order.

Validation: the opencode-setup browser suite (33 tests) passes, including the settings layout test now asserting the version card first, the selector second without a header, collapsed disclosures and logs last; a capture confirmed the order. UI typecheck passes.
2026-10-21 00:31:25 +02:00
Pascal André
46208ad902
fix(settings): adopt a live window for Preferences startup state and spin the Stats refresh (#1050)
Native Preferences showed "The CodeNomad window that opened Preferences is no longer available" with disabled startup controls whenever its opener was unknown (Preferences restored at launch) or closed, even with CodeNomad windows open. Startup state (restore previous state, saved snapshot, Clear) is each window's own, so the host now adopts the most recently used local window as the opener in that case: Electron through PreferencesWindowRegistry.adoptOpener (startupStateTargetId), Tauri through set_opener (startup_state_target). The epoch rotates on adoption, so a change prepared for another window degrades to a read of the adopted one before any write. The warning and its string in all ten locales are removed; the card re-reads up to three times while the adopted window starts and stays disabled only when no local window can answer. Code contracts, AGENTS.md and the public help in all ten locales describe the opener, otherwise the most recently used window.

Stats' text Refresh button becomes the shared icon button sized like the period select, keeping its localized accessible name and tooltip; the icon spins while a read runs.

Validation: Electron unit tests for live/closed/unknown openers, adoption, epoch rotation and fenced changes; Tauri unit test for adoption and fencing; preferences-startup-state (Electron and Tauri hosts), other-profiles and service-usage browser tests; UI and Electron typechecks; help test; rustfmt check. Independent review: the first pass found that startup state is per window and that the fence no longer followed the target; the adoption fix was re-reviewed and approved, with two trivial notes applied.
2026-10-20 23:34:52 +02:00
Pascal André
7d9093de68
feat(timeline): follow Message content tool visibility (#1049)
Timeline tool markers now follow the Message content filters: a marker disappears when the tool type it shows (its icon; the outline's representative tool for indexed history) is hidden there, and the Nothing preset removes every tool marker. As with hidden tools before, the transcript's active marker and markers with an active permission stay visible, so highlighting and reveal keep following the transcript. Collapsed tools keep their marker.

Because the rail now follows those filters, the separate "Show tool calls in the timeline" preference is removed everywhere it surfaced: Settings > General, the View menu item in the renderer, Electron and Tauri hosts, the palette command, its shortcut catalog entry (72 actions, 23 initially unbound) and its strings in all ten locales and the help. Saved values are ignored; the server schema passes unknown keys through, and hosts ignore an extra field from older renderers. The never-updated expandedMessageIds timeline input is dropped. Intended default change: tool markers are visible with the Balanced preset unless their type is hidden in Message content.

Validation: new timeline-tool-filters browser test (hidden, Other-routed and collapsed tools, Nothing/Everything presets, a hidden active marker shown and highlighted); view-menu, a real Electron View menu, hotkeys-settings, transcript-presets, render-cost, history-navigation and other timeline suites (one local-environment failure that also fails on unmodified dev); related unit tests; UI typecheck; rustfmt check of view_menu.rs. Independent review approved after the active-marker fix.
2026-10-20 23:11:08 +02:00
Pascal André
78d06faf0b
test(status): find the Tokens trigger when its summary is shown (#1048)
test(status): find the Tokens trigger when its summary is shown

Since #1037 a collapsed Tokens section keeps its compact input/output/cost summary in the disclosure trigger, so the trigger's accessible name becomes "Tokens Input: …". The status-panel test still looked for a button named exactly "Tokens", so its second click (re-expanding after a collapse) timed out and the browser suite failed on every PR.

The test now matches the trigger by its leading "Tokens" label, which also excludes the neighbouring "Token usage…" info button, and asserts that the collapsed summary appears, covering the behavior #1037 introduced.

Validation: reproduced the timeout on unmodified dev locally (and in CI on unrelated PRs); status-panel.test.ts now passes 3/3.
2026-10-20 22:19:53 +02:00
Pascal André
50066fc27d
chore(release): prepare CodeNomad 1.0.0 versions (#1045)
Some checks are pending
Publish help / build (push) Waiting to run
Publish help / deploy (push) Blocked by required conditions
Align root/workspace manifests and lock entries, Tauri configuration and Cargo versions for the 1.0.0 stable and preview family. Require a 1.0.0 server for the separately published remote UI while retaining every third-party dependency and the existing local remote-tunnel link.

Check committed manifest/lock consistency and stable/prerelease offline workspace installation. OpenCode dependencies, minimum and recommendation remain at 2.1.0; 2.1.1 qualification is deferred by explicit user request.

The user explicitly authorized administrator merge and subsequent dev prerelease dispatch. This does not merge #1028 into main or publish a stable release.
2026-10-20 20:36:44 +02:00
Pascal André
4d4bf14908
test(missions): await recurring fixture clock quiescence (#1044)
## Cause
Remaining recurring-day CI failures observe a coordinator before its
awaited admission/error publication. The fixture clock advance
previously waited four timer ticks, not a completed clock turn.
Production recurrence correctly awaits those operations.

## Fix
Track each Job's own live sleep or terminal exit and await bounded
quiescence on each wake. Stale race sleepers no longer mask active work.
Existing five-second deadline and parked model/Form semantics retained;
no production recurrence changes.

## Evidence
Held-admission success/refusal regressions fail with original advance
and pass fixed. Four fixture callers36/36; recurring-day with
controlled25ms and75ms admission delays25/25 each; clock units7/7;
server typecheck and diff check pass. No assertion
weakening/skips/timeout increases. Full server CI not rerun locally. All
fixtures isolated; no shared daemon/user DB.

Oversized touched source: recurring-day-fixture.ts ~560 lines; test452
lines.
2026-10-20 20:02:22 +02:00
Pascal André
04e7aae1db
feat(transcript): add a Nothing preset and a preset menu in Message content (#1043)
Transcript presets are now Nothing, Minimal, Balanced and Everything. Nothing hides every message-content row, including all registered, historical and unknown tools, thinking, diagnostics, tool inputs, tokens and system messages. Detailed is retired: saved Detailed choices load as Custom with Detailed's effective tool column (only read, skill and invalid collapsed, other tools expanded) and the same legacy thinking fallback, so existing transcripts keep their appearance.

The Message content popup gains a native select under its title with the four presets. A disabled Custom entry appears only after a manual row change, and the saved preference always drives the shown value, so refused or failed saves never leave a stale choice. Escape closes an open list before the popup. Preset recognition and patches move from Chat settings into the shared transcript-visibility module used by both surfaces; the tool-output command reuses the shared Other-tools resolution.

Preset names and descriptions were English in most locales; they are now translated in all ten, along with the accent-less French, German and Spanish card strings. The public help lists the new localized presets.

Validation: new transcript-presets browser test (popup order, choices, Custom after a manual change, pointer choice keeps the popup, Escape order, French labels) plus existing status-tokens tests; unit tests for preset round-trips, Nothing visibility and the Detailed migration; behavior-registry tests; help test; key parity across locales; UI typecheck with the pinned 2.1.0 client. Independent review approved with no P0-P2 findings; its two trivial P3 notes were applied.
2026-10-20 19:56:05 +02:00
Pascal André
6f8881d5ae
fix(stats): restore cached totals and refresh only while visible (#1041)
## Summary
Replace the renderer-only workspace-ID cache with bounded validated
display snapshots in backend-owned state.ui.serviceUsage. Cache survives
recreated renderer/backend/workspace identity, keyed by authenticated
service/discovery/execution-host/folder fingerprint and period/timezone.
Eight entries, 1 MiB maximum; no plaintext credentials or paths.
Preferences does not write the opener's client-state journal.

Show previous totals/charts immediately with a cached-state label while
revalidating; preserve them on error. Hidden/unmounted Stats starts no
reads, cancels trailing work and fences late results. Visibility resumes
one read; execution hints use the existing activity cooldown instead of
one native scan per descendant. Manual refresh, period, reconnect and
deletion/pruning paths retained.

## Validation
- Rebased on current dev 7a2ee897e, retaining #1037 shared fixture
cache/shutdown and token UI plus #1039 launcher changes.
- Server/UI typechecks pass; cache/scheduler units 8/8; browser
service-usage 5/5, usage-metrics 4/4, status-tokens 4/4, sequentially.
- Real renderer AND backend recreation with a two-second stats delay:
2098 ms cold / 68 ms restored, then fresh totals replace cached values.
- Hidden-start regression: base makes one unwanted read; fixed makes
zero. Tests cover hidden events, visibility, burst scheduling, identity
changes, retention/errors and malformed cache bounds.
- Isolated native fixture with four synthetic sessions passes; repeated
native reads ~15–19 ms. OpenCode still repeats aggregation; no claim to
optimize large-history scans.
- No app/shared daemon/user data touched. Live installed app and
non-Windows native acceptance not tested.

## Oversized touched files
http-server.ts ~2446 lines; api-client.ts ~817; 10 locale settings.ts
files ~735–749. No unrelated refactor.
2026-10-20 19:49:45 +02:00
Pascal André
b818a28c3d
test(pruning): align native UI journey with token grouping (#1040)
## Cause and correction
Native pruning CI timeout on all three OSes was a fixture mismatch:
visible per-step token usage splits technical groups, but the journey
still expected the second cross-message prune request. It reproduces on
BOTH OpenCode 2.1.0 and 2.1.1.

Assert the visible grouping boundary, then explicitly hide message usage
before testing cross-message cleanup. Use immutable config fixture
responses so Solid updates. Add SSE/HTTP replay-metadata parity
assertions to the existing native fixture.

## Validation
- Base native UI journeys timeout on 2.1.0 and 2.1.1; corrected complete
journeys pass both (completed:true).
- UI tests 97/97; server pruning/broker tests 21/21; UI/server
typechecks and diff check pass.
- Local Windows only; Linux/macOS CI acceptance still pending.
- The two reported metadata unit failures are from stale local client
2.0.27; they reproduce with that dependency and pass unchanged with
lockfile client 2.1.0. No metadata or contract workaround introduced.
- No production code, guard, timeout, dependency pin or shared
app/daemon/state changed.

Largest changed file ~428 lines.
2026-10-20 19:37:16 +02:00
Pascal André
cfab3d5fb6
fix(settings): accent update and pairing actions (#1042)
Use the existing primary selector treatment for OpenCode install/update and the post-update service restart, the Info page update/install or download action, and Remote Control start/pairing-link actions. Sharing OpenCodeSetupPanel applies the same emphasis to required-update startup recovery. Colors continue to come from the selected theme accent and on-accent text tokens.

Make Remote Control header refresh an icon-only window button. Keep its localized accessible label and tooltip, disabled behavior and loading spinner. No action handlers or update/restart admission logic change; AGENTS.md records the action hierarchy.

Validation: 64 existing browser tests pass across opencode-setup, remote-control-settings, desktop-updates and server-info. An isolated visual check rendered the real components in French on the Classic dark palette, verified accent/text colors and square corners, and captured settings/startup update and restart states, both Info update surfaces and wide/narrow Remote Control. UI typecheck passes against the pinned 2.1.0 client declarations; git diff --check passes.
2026-10-20 19:30:04 +02:00
Pascal André
6a6367b560
chore(opencode): retire adaptations for runtimes and events OpenCode V2 no longer produces (#1034)
## Summary
This PR removes OpenCode compatibility code that nothing can reach any
more:
- Code that was already dead at the former 2.0.27 minimum, and is still
dead at the current 2.1.0 minimum (#1029).
- Handlers for events and data shapes that OpenCode V2 never produces.

Each item was checked against the upstream 2.0.27 and 2.1.0 sources
before removal.

Kept on purpose:
- Workarounds for upstream bugs or limits that are still present in
2.1.0.
- Code that reads conversations written by older runtimes: V1-migrated
tool names and metadata, `workspaceID` data, task summary titles,
`session-legacy` replay.
- Code that recognises and upgrades old CLIs and live older daemons: 1.x
replacement, beta recognition, 2.0.11 service discovery, 2.0.15 native
upgrade, `/api/status` and `/api/health` identification.

## Removed or changed
**Server**
- Negotiation: one "not modern → `canonical_api`" rule replaces the
pre-2.0.4 legacy schema recognition, and runs before #1029's
`native_contract` check.
- The transport no longer rewrites `/api/status`.
- Desktop skills no longer send the pre-2.0.4 `location` and `slash`
fields.
- The legacy `auth.json` usage fallback for daemons without
`credential.list` (pre-2.0.20) is removed, along with its UI reason and
the i18n key in 10 locales. A missing endpoint still fails safely.
- Workspace selectors are rejected up front, without a native resolution
request.
- CLI service probes try `/api/info` first. An admitted daemon no longer
costs two authenticated 404s on every check.

**UI**
- Remaining V1 `session.idle`/`session.status` branches: Missions store,
pending discovery (UI and server), `eventAffectsMessages`.
- `server.instance.disposed` and the now-unused `rehydrateInstance`.
- The V1 `worktree.ready` envelope (the handler now reads the V2 shape).
- V1 tool-state titles and the `success` status alias.
- Part `time.start` and `time.end`.
- Text stored as objects or segment arrays.
- `deriveMessageStatus`.
- The V1 `error.data.message` shape.
- `projectID` and `location` fallbacks on `session.created`.
- The replay-only `session.message.content.updated`.
- The unreachable `output.answers` fallback.

**Fixtures, Missions and docs**
- Legacy branches removed from the location, fork, families, pruning and
automation native fixtures. The WSL setup fixture now compares against
the reported version instead of 2.0.11.
- Unused 2.0.21 launch helpers removed.
- The Missions native and trajectory fixtures also run on the minimum
lane.
- The capability proof checks the reported version.
- Stale 2.0.2x labels now say "as of the 2.1.0 minimum".

**Follow-ups from the #1029 post-merge review**
- Test fixtures no longer send the ignored `recent: ""` compaction field
(18 sites).
- The manual compaction-isolation fixture accepts any published version
at or above the minimum.
- `CONTRIBUTING.md` wording updated.

114 files, +319 / −1117: source +90 / −487, tests/fixtures/CI +214 /
−604, i18n −10, docs +15 / −16.

## Validation (rebased on dev with #1029)
**Typechecks:** server and UI pass.

**Server tests**
- Touched areas: 480/480.
- Full server CI step: 2430 pass, 0 fail, 7 skipped, 1 cancelled. The
cancelled test is the `index.test.ts` real-process shutdown test; it hit
its 5 s timeout under load. It passes alone (4/4), and that file is
unchanged versus dev.

**UI unit tests**
- Touched files: 230/230.
- CI steps: 574/574, 200/200 and 519/519.

**Browser suites**, one at a time:
- Provider and service usage: provider-usage 13/13, service-usage 3/3.
- Missions: mission-visibility 3/3, mission-control-feedback 5/5,
mission-lifecycle-revision 10/10, mission-lifecycle-navigation 10/10,
mission-current-passage 1/1, mission-native-family 11/11.
- Transcript and rendering: compaction-responsiveness 5/5,
session-rendering 47/47, reasoning-collapse 3/3, render-cost 17/17,
tool-images 4/4, system-messages 10/10.
- Other: search-highlight 11 pass (1 skipped by its env gate),
panel-extensions 13/13.

**Native fixtures on an isolated 2.1.0 CLI:** location, session-fork,
automation, session-pruning, missions native, missions trajectory,
provider-usage, Missions capability proof and compaction-isolation all
pass. The WSL setup fixture was not run (it needs a WSL distro with a
Linux CLI).

## Left for later
- `RuntimeIdentity.discovery` is now write-only.
- `ownsLocation` has an unused `_client` parameter (about 30 callers).
- A test-only V1 translation in `auto-accept-manager.test.ts`.
- Out of scope: HTML-entity decoding (a product decision), the
`MutableHashMap` loop, the pruning-UI import-order workaround, unused
log-based Missions readers, and the unshipped managed-owner module with
its 2.0.24-pinned test.

Oversized source files touched: `stores/instances.ts` ~2,209,
`message-block.tsx` ~1,601, `workspaces/manager.ts` ~1,104,
`tool-call.css` ~1,047, `tool-call.tsx` ~865, `opencode-data.ts` ~865,
`api-types.ts` ~784, `session-events.ts` ~606,
`recurring-day-fixture.ts` ~535.
2026-10-20 19:19:47 +02:00
Pascal André
7a2ee897ec
fix(projects): keep the new-tab launcher project-only (#1039)
## Summary
Remove the three bottom actions (Servers, SideCar, Full home) from the
project-tab plus launcher. Also remove its server disclosure, profile
hook/loading, local expansion state, server actions/dialog binding,
unused props/icons/CSS and four keys in all locales. This is not just
hidden chrome.

Recent search/list, Browse, Temporary and Clone remain. A normal plus
click still opens home. All long-press/context/keyboard gestures and
Escape focus remain. Home keeps remote server and SideCar functionality;
shared server setup is mounted only for its caller.

## Validation
UI typecheck passed. Real new-instance-launcher browser suite 8/8:
retained actions/gestures/focus, no server requests from launcher even
with saved profiles, home remote/SideCar controls retained.
Desktop/narrow captures inspected. git diff --check passed. No
app/daemon/state touched.

## Scope
48 files (+110/-190), largely 10-locale help and removed translations.
App.tsx remains oversized (~915 lines); only obsolete launcher props
changed.
2026-10-20 18:44:36 +02:00
Pascal André
b85a32b210
fix(tokens): hide message usage in presets and keep compact summaries visible (#1037)
## User-visible behavior
- Message token usage hidden in Minimal/Balanced/Detailed; shown in
Everything/Complet. Factory defaults and absent-value command fallbacks
aligned, explicit saved choices retained.
- Status Tokens independent of transcript usage preferences. Collapsed
header: session input/output/cost, one line with ellipsis on narrow
widths. Expanded: session metrics then descendant-inclusive totals in
two groups, no metrics duplicated in header.
- Message Tokens follows the same disclosure pattern: input/output/cost
in collapsed header; six existing metrics only in expanded body; hidden
renders nothing.
- Full-value accessible names/titles, keyboard, narrow/RTL and
subscription Included behavior retained.

## Implementation
Focused status collapsedSummary slot, shared metric rendering, scoped
token CSS. Existing usage styles moved out of message-base.css; shared
product fixture now owns an optimizer cache and drained shutdown.

## Validation
- UI typecheck passed; 39 targeted UI units and 3 server schema tests
passed.
- Browser status-tokens 4/4 and usage-metrics 4/4, individually, real
components/native stores.
- Desktop/narrow/RTL collapsed and expanded captures inspected.
- git diff --check passed.
- No installed-app/shared-daemon/user-storage changes; no Stats refresh
or notification changes.

## Oversized touched files
preferences.tsx ~1591 lines; behavior-registry.ts ~514; message-base.css
~615. No unrelated refactor.
2026-10-20 18:43:20 +02:00
Pascal André
c2b6333d65
test(notifications): typecheck lifecycle fixture mock hooks (#1038)
Follow-up to #1036: beforeEach is typed TestContext | SuiteContext, so
context.mock failed typechecking. Use node:test's module mock tracker
with explicit restoration in finally. Same fixture network block and
assertions; no product changes. UI typecheck and all 11
lifecycle/routing tests pass with current 2.1 dependencies.
2026-10-20 18:43:13 +02:00
Pascal André
30d8f07186
test(notifications): opt into history in lifecycle delivery checks (#1036)
## Summary
Test-only follow-up to #1035: the lifecycle delivery tests must
explicitly enable history instead of relying on the old default. Add a
real-event regression for default-off history, retain the once-per-run
and suppression assertions, block fixture network access, and restore
in-memory settings/mocks after each test.

## Validation
- Node 24 targeted lifecycle and routing tests: 11/11.
- Exact ownership CI list: 519/521. The two replay metadata failures
reproduce on unchanged tests at opencode-data.test.ts:339 and
session-pruning-projection.test.ts:116; not fixed or hidden by this
test-only PR.
- No production or dependency changes, no access to shared
app/daemon/storage.
- git diff --check passes.
2026-10-20 18:33:36 +02:00
Pascal André
569fde5267
fix(notifications): keep session states out of history by default (#1035)
## Summary
- Default History off for both needs-input and session-finished
notifications. Other session events already default to History off.
- Session states can become stale after an answer or a resumed V2 run;
they should not accumulate in the bell history by default.
- Preserve saved explicit routing, screen/system settings, and the
existing mandatory error visibility. No migration or deletion of
existing history.
- No changes to tokens, statistics, update notices or notification
timing.

## Validation
- Two routing regressions fail before the change; all 5 routing tests
pass after it.
- Covers absent/partial settings, all three session categories, explicit
history opt-ins, legacy system settings and error overrides.
- git diff --check passes.

## Scope
Two small files only; no dependencies or styling changes.
2026-10-20 18:09:18 +02:00
Pascal André
d6a890fba7
chore(opencode): qualify 2.1.0 and adopt its native contracts (#1029)
Adopt the OpenCode 2.1 native replay and compaction contracts, align client/plugin pins and set minimum and recommended runtime to 2.1.0. Preserve authenticated contract recognition for future/custom runtimes and explicit installation/restart boundaries.

Preserve historical conversations through official projection-based pruning revisions and independently checked migrations from 2.0.3, beta-19271 and 2.0.32. Retain ownership, execution claims, stale-selection checks and transaction fences. Update setup, help and qualification evidence.

Isolated Windows native qualification and integration checks cover pruning UI, historical reimport, Compare/Wayfinder, recurring Missions, builds and typechecks. Document broad-suite limitations and outstanding platform/package/mobile acceptance; this merge does not publish CodeNomad 1.0.0.
2026-10-20 16:46:38 +02:00
Pascal André
73c0621d0d
fix(sessions): wire finish, retry and usage signals to OpenCode V2 events (#1033)
## What changes for the user
Four things that silently stopped working with OpenCode V2 work again:

- **"A session finished" notifications** arrive again. A successful or
failed run notifies. A run you stopped yourself does not.
- **Provider retries** show the retrying status and the countdown toast
again.
- **Usage statistics** refresh after each run.
- **The token usage line** under assistant replies (preference,
transcript filter row and hotkey) shows each settled reply's tokens and
cost again.

Per-session transcript reducers are also released again once a run has
settled.

## Why
OpenCode V2 declares `session.idle` and `session.status` in its schema,
but core never publishes them. Runs end with
`session.execution.{succeeded,failed,interrupted}`, and retries arrive
as `session.retry.scheduled`. These features were still keyed only to
the V1 events.

The token usage line had a second problem: it was drawn from V1
`step-finish` parts, which V2 never produces. V2 stores tokens and cost
on each assistant message instead.

A new isolated native fixture,
`scripts/test-session-lifecycle-native.mjs`, confirms this on CLI 2.1.0.
It uses its own storage, port and fake provider, and never the shared
service. In its complete, retry, fail and interrupt scenarios, it sees
no `session.idle` or `session.status` event. Upstream 2.0.27 sources
show the same: these events are declared but never published.

## How
- **Finish notification:** sent from the V2 terminal path after
`session.active` confirms the run ended.
  - Succeeded and failed runs notify.
- User and shutdown interrupts stay silent; idle-timeout interrupts
notify.
  - Subsessions and unknown sessions stay silent.
  - A stray `session.idle` cannot cause a duplicate.
- **Retry:** `session.retry.scheduled` sets the retry state and the
transient toast. The next attempt or the end of the run clears them.
- **Run-end detection:** a shared `lib/native-execution-events.ts`
drives the usage refresh and reducer retirement.
- **Reducer retirement:** it also waits for queued prompts, so prompts
left in the inbox after an interrupt stay listed.
- **Usage line:** moves to `components/message-usage.tsx` with the
existing classes.
- **Removed:**
  - the dead V1 `StepCard`, its step-part types and CSS;
  - the V1 `session.idle`/`session.status` handlers.
- **AGENTS.md:** one bullet so future features wire to V2 lifecycle
events.

Left for the follow-up cleanup PR: the remaining dead `session.idle`
fallbacks in pending discovery, Missions triggers and test fixtures.

## Validation
New tests that fail on unmodified dev and pass here:

| Suite | dev | this PR |
|---|---|---|
| lifecycle unit | 3/4 fail | 4/4 |
| retirement | 8/9 fail | 9/9 |
| browser `usage-metrics` | 3/3 fail | 3/3 |
| browser `service-usage` | 2/3 fail | 3/3 |

- UI typecheck is clean.
- **Passing unit suites:**
- opencode-data-idle 9, runtime-contract 4, session-native-events 8,
message-part-grouping 6
- opencode-transcript-replay 17, opencode-compaction 11,
session-pruning-projection 2, session-pruning-pagination 6
- session-request-authority 77, opencode-data 30, message-timeline-v2 2,
transcript-visibility 8
  - missions 1, instances-pending-compaction 7
- **Passing browser suite:** `system-messages` 10/10.
- The native lifecycle capture passes.

Oversized files touched:

| File | Lines |
|---|---|
| `message-block.tsx` | ~1,760 (down ~240) |
| `message-section.tsx` | ~1,630 |
| `opencode-data.ts` | ~905 |
| `session-events.ts` | ~670 |
| `message-timeline.tsx` | ~645 |
| `message-base.css` | ~675 |
| `session-request-authority.test.ts` | ~2,400 |
| `opencode-data.test.ts` | ~1,215 |
2026-10-20 15:51:48 +02:00
Pascal André
debbdce33f
feat(missions): add a "Compare two models" Mission template (#1026)
## Summary

Adds a fifth Mission template, **Compare two models** (`compare`). It
runs the same bounded task on two different models and ends with a blind
verdict.

- **Creation:** the user picks **model A** and **model B** in the main
creation form. Both are required and must name different models
(provider + model id; a reasoning variant alone is not a different
model). This is enforced on every creation path: HTTP, prepare,
recurrence create and agent-started missions.
- **Roles:** `model-a`, `model-b` and `comparator`.
  - A and B share one instruction text and report contract.
- The coordinator writes the task, inputs and criteria once, and gives
both byte-identical briefs.
- The server refuses a run task whose brief differs from its sibling's,
or whose per-task execution would change its role's model.
- **Blind judge, always on:** the comparator works on neutral X/Y
labels.
- It reports one versioned `comparison` artifact: the A/B task
references, per-criterion scores for both runs, sourced observations,
and a verdict `a | b | equivalent | inconclusive`.
  - Completion requires both runs and that comparison.
- **Judge model:** frozen once at creation, like every other role.
- An explicit judge is kept; otherwise the judge copies the
coordinator's effective model. When that model is unknown, both stay on
the native default.
  - Replays and retries therefore reuse the stored profile.
  - The editor warns when the judge's model equals A or B.
- **Readers:** the verdict, both model names, a score table and evidence
by path. Hashes stay under Technical details, for current and archived
recurring passages.

The template came out of evaluating Harness2's comparison loop. It
deliberately has **no** lesson, human approval, budget or duration
handling, and leaves the Wayfinder human gate unchanged. Up to five
models, per-run worktrees and launching from the composer's model picker
(OpenChamber-style multi-run) are left for later.

`AGENTS.md` records the single editor exception: the A/B selectors sit
outside the collapsed Options disclosure. All ten locales are updated.

## Size and upkeep

62 files, +837/-96:

| Part | Lines |
|---|---|
| Product code (about 16 files are one-line template-list hooks) |
+280/-57 |
| Locales (13 keys × 10) | +131 |
| Tests | +409 |
| Docs | +17 |

`missions/control.ts` stays oversized (~1,500 lines).

## Validation

- Full server suite: 2,426 tests pass, 7 existing skips. Root and server
typechecks, the server build and the plugin build pass.
- UI: 45 unit tests and 36 browser tests pass.
- The native trajectory fixture, extended with a compare case, passes on
OpenCode 2.0.31. It checks two distinct models, a one-newline brief
difference refused with the journal unchanged, a real blind judgment and
a completed mission. The native contract and recurring Q fixtures also
pass.
- Regressions cover:
  - distinct-model refusals on every creation path;
  - brief and override refusals;
  - artifact validation and the completion policy;
- judge freezing at creation, revision replay, and retry after a
coordinator model change;
  - editor inheritance and readable evidence.
- Independent review rounds ended with no P0-P3 findings.
- The editor and reader were checked from screenshots of the real Solid
components in the browser harness, not in a packaged app.
2026-10-20 13:55:12 +02:00
Pascal André
b6221e9232
fix(composer): resume typing from the conversation focused after a send (#1032)
After a send, desktop focus deliberately moves to the conversation stream so the transcript can be read with the keyboard, and touch-only devices keep the virtual keyboard closed. The global type-to-focus listener treated that focusable scroller as an ordinary control, so typing after a send did nothing until the composer was clicked or Ctrl+P pressed (#1031).

The decision now lives in prompt-input/type-to-focus.ts. The exact conversation stream of the composer's own session view is a reading surface: arrows, paging, Home/End and Space keep scrolling it, while a printable character (Shift allowed) focuses the composer during keydown so the browser inserts it once. Fields, controls, nested focusable scrollers, IME composition and plain-key shortcuts keep their keys. Ctrl/Alt/Meta chords, including AltGr and macOS Option characters, intentionally never reactivate the composer, so a shortcut cannot re-enter the prompt; missing such a character after a send is accepted. No preference, delayed refocus timer or mobile keyboard reopening.

Validation: unit coverage for the decision rules and a real SessionView browser regression (send, reading keys, resuming with the first character, focused controls) that fails without the fix; existing composer draft/IME, picker, aside, device upload and copy-focus browser tests pass; UI typecheck baseline unchanged. Gatekeeper review reported one P3 (AltGr), waived by the maintainer as intended behaviour. Native CI jobs failing against today's OpenCode 2.1.0 release are unrelated.

Closes #1031.
2026-10-20 12:03:51 +02:00
Pascal André
ee035ad272
fix(files): refresh Workspace with scoped, rate-limited invalidations (#1030)
Fix Workspace listings that stayed stale until manual refresh or F5. Reproduced
the issue in the attached Tauri application: a host-created file was absent
until the panel's Refresh button reread it. OpenCode filesystem events were
the only invalidation source, not a subscription to every browsed directory.

Observe authorized listed directories on the host through bounded,
non-recursive native watches, registered before listing. Preserve physical
containment, inode and workspace-lifetime checks and close watches on removal
or shutdown. Forward actual directory observations through the existing SSE
event bus, without inventing native OpenCode events or adding polling,
recursive scans or an independent service.

Coalesce passive cache invalidations before readers wake: a short isolated-
change delay, then at most one publication every five seconds during sustained
activity, as requested. Only pending observations create a timer. Explicit
mutation/recovery invalidations remain immediate. Scope tree versions by
browsing root and listed directory, so only stale visible directories reread;
other worktrees, unchanged siblings and hidden/collapsed folders do not scan.
Retain existing read budgets, cache-first rows and coalesced in-flight demand.

Keep logical listing aliases separate from physical watch targets, so every
internal junction/symlink subscriber receives its own cache invalidation.
Directory listings include an opaque device/inode identity: when a parent
detects removal or replacement, discard only that child's cached subtree,
cancel its old reads and reload still-expanded paths to bind new observations.
Ordinary mtime changes never force a subtree scan.

Validation covers real filesystem create/edit/rename/unlink, ownership,
replacement, aliases, watcher budgets/disposal, continuous cache bursts and
immediate explicit mutations. A real Chromium regression exercises the whole
filesystem-to-watch-to-SSE-to-Workspace path without F5, including parent-only
replacement and multiple junction aliases. Existing Files/Git browser checks,
the full server suite and targeted UI checks pass; typecheck diagnostics remain
at baseline. No installed application or shared OpenCode daemon was replaced
or restarted.

Final server validation: 2,415 tests pass, seven existing skips. Two independent
gatekeeper rounds; the final review reports zero actionable P0-P3 findings at
728bf59861d063863c6bb3351d5ce0a030ce6866. The hosted CI is still running at merge.

Closes #1027.
2026-10-20 00:17:40 +02:00
Pascal André
362efb5911
test(missions): fold the one-time decision journey into the trajectory fixture (#1024)
## Summary

#1023 fixed one-time Wayfinder decisions with about 85 lines of product
code but 410 lines of tests. Its 248-line native script re-implemented
the daemon, bridge and workspace setup that
`test-missions-native-trajectory.mjs` already has.
`native-invocation.test.ts` also carried overlapping cases.

- The decision journey is now one case of the trajectory fixture,
reusing its private daemon, shipped plugin, bridge, workspace and real
subagents. The case covers:
  - refusal before an answer, with no journal mutation;
  - a native child question answered through the authenticated UI route;
  - completion by discovery without a supplied artifact.
- `scripts/test-missions-wayfinder-decision-native.mjs` is deleted.
- `native-invocation.test.ts` (162 to 90 lines) keeps one test per
distinct behaviour:
  - concurrent isolation and stale invocations;
  - exact `Exit.fail`/`Exit.die` preservation;
- interruption joining a raw read that ignores abort, through the real
`acquireNativeHumanAnswers` adapter;
- the per-check deadline between two successful checks and after an
expired one.
- No product code changes. Net -260 lines.

## Validation

- With the pre-#1023 `managed-owner-plugin.ts` substituted in memory,
the trajectory case fails after the UI reply with `Native human answer
graph unavailable`. Restored, all 9 checks pass on OpenCode 2.0.31.
- Detaching reads from the runner, or converting errors to defects,
makes the unit tests fail.
- The full server suite passes (2,410 tests, 7 existing skips), as do
the server typecheck, the plugin build and bundle test, and the native
contract fixture.
- An independent review ended with no P0-P3 findings.
2026-10-19 16:15:50 +02:00
Pascal André
c42cfe740a
fix(missions): run the one-time human gate in the invoking native graph (#1023)
## Summary

A one-time Wayfinder mission could never complete a decision task. The
decision child asks its native question, the human answers it in
CodeNomad (authenticated UI reply, durable mark confirmed `via: "ui"`),
and the coordinator's decision report is still refused with `Native
human answer graph unavailable`.

The one-time human gate is built in `managed-owner-plugin.ts` from the
plugin setup graph. That graph lacks the Location, Form and Database
services that `acquireNativeHumanAnswers` requires. Recurring passages
build their own gate in the Job's Location graph
(`native-recurrence-due.ts`), so they were not affected. This is why the
recurring Wayfinder journey (Q) kept passing while the one-time path was
broken.

## Fix

- `native-invocation.ts` captures each invoking native graph with
`AsyncLocalStorage` around the plugin's tool executes and forwarded
Missions RPC handlers. The one-time gate runs its reads in that graph.
- Concurrent invocations stay isolated. A missing or finished invocation
is refused, never served by another invocation's graph or the setup
graph.
- Each complete human check gets its own 10 s budget, the bound
`native-human-evidence.ts` already uses. Its nested SQL/Form/storage
reads share that budget, which is not reset per query.
- Interruption or expiry aborts those reads. The borrowed graph is
released only after every started read has settled. A truly
non-interruptible native finalizer must still finish first; this is
documented.
- Expiry surfaces as an ordinary refusal.
- Recurring runners are unchanged.

## Validation

- New native fixture
`scripts/test-missions-wayfinder-decision-native.mjs`. It drives the
shipped plugin on a private daemon with a synthetic provider: a real
child asks a native question, and the reply goes through the
authenticated UI route.
- Before the answer, the decision report is refused with no journal
mutation.
- After it, the decision completes by discovery, without a supplied
artifact.
- On unmodified `dev` it fails with the error above; with this change it
passes (OpenCode 2.0.31).
- Unit regressions in `native-invocation.test.ts` use the real
`acquireNativeHumanAnswers`. They cover invocation isolation, missing or
stale invocations, Effect errors, interrupted and blocked reads, and
successive checks separated by slow work. Each new regression fails on
the earlier code.
- The full server suite passes (2,413 tests, 7 existing skips), as do
typechecks, the plugin build and bundle test, and the native
`test-missions-native`, contract, trajectory, family-control and
recurring Q fixtures. After rebasing onto #1022 (UI preferences only),
the server typecheck and the 34 targeted human-answer tests pass again.
- An independent review loop ended with no P0-P3 findings.
2026-10-19 14:52:29 +02:00
Pascal André
4ff7cb8f12
fix(preferences): restore the correct project context after startup (#1022)
Native Preferences can reopen with a backend workspace ID saved before restart. Those IDs are ephemeral, so Providers and Web search were reading an obsolete proxy and remaining in an error state even after the project restored. Resolve the project through the existing metadata-only inventory and wait for its ready event before reading project settings.

Prefer the exact record while it exists; otherwise choose only one unambiguous exact-folder match. Persist the opened backend-host workspacePath separately from the selected native directory so WSL and worktree requests retain their execution-host Location. Both Electron and Tauri accept and store the optional field. Legacy requests without it retain the conservative exact-directory fallback; no path translation or ancestor is guessed.

Reads follow visible section demand, matching lifecycle events and reconnects, without polling or hydrating other workspaces. Record deletion tombstones before candidates are known, revalidate ambiguity after deletion, and fence stale responses and newer explicit Preferences requests. Rebinding uses the existing persistence queue and preserves scrolling performed while the native write is pending.

Validation: 28 browser tests across startup restoration, Preferences lifetime/opener state, Web search and the native Missions entry point; 44 TypeScript request-contract and Electron client-state tests; UI typecheck and diff check. New startup regressions cover both hosts, delayed readiness, obsolete IDs, unresolved deletion, ambiguous folders, WSL paths, reconnects and newer view intent. Tauri validation and persistence tests cover the updated request contract. Independent review approved the follow-up after its deletion, WSL and scroll findings were addressed.

CI also exposed an existing Markdown completion stopwatch failure (178 ms versus 150 ms). A test-only follow-up verifies immediate warm parser dispatch before timers instead of measuring highlighting/observation latency, then checks exactly one final parse and highlighted final text. All 11 tests pass and a deliberate 200 ms dispatch-delay mutation fails; production Markdown remains unchanged.
2026-10-19 14:23:22 +02:00
Pascal André
e32d9331f4
refactor(automation): unify native browser previews on the shared page engine (#1020)
Unify application-window and session-preview automation on the same server-side
page engine. BrowserPage serializes per-session operations, binds refs to the
exact native guest/document generation, fences cancelled and late results and
always attempts exact lease cleanup. Both tools share page-action validation
and budgeted, scoped plain-text outlines, without arbitrary model evaluation.

Replace Electron and Windows Tauri snapshot/ref/click/type implementations
with the private browser.page.begin/send/check/end channel. Native hosts keep
guest ownership, visibility, URL policy, storage, history, emulation, navigation
and screenshots. Bounded operation leases and fixed protocol allowlists check
identity before native effects and after IO; hidden, ambiguous, replaced,
navigated or expired targets refuse rather than selecting another guest.
Cleanup never detaches another owner's persistent emulation overrides. No
renderer IPC, guest capabilities or legacy page-action fallback is added.

Electron uses WebContents.insertText and sendInputEvent for embedded guest
text and keyboard input: direct CDP input can report success while silently
dropping it before the first pointer click. Preserve key modifiers and text
events without showing or raising a window. ContextMenu is explicitly refused
with Shift+F10 guidance rather than emitted as an unknown native key.

Validation includes native ownership/expiry/deadline/concurrency regressions,
20 Rust browser-controller checks and cargo check --tests, 24 Electron
controller tests, full server validation and isolated OpenCode 2.0.31 plugin
and bundle checks. The real hidden-window Electron integration drives both
production ends through replacing type, select, printable keys, Ctrl+A and
Backspace, Tab, Enter, pointer clicks, waits, scoped snapshots, hidden targets
and stale refs after navigation. Three independent gatekeeper rounds reached
zero actionable P0-P3 findings; subsequent parent-only rebases preserve the
identical reviewed patch.

At merge, the CI tests job failed only an unchanged client-state process
timing assertion (661 ms against a 500 ms ceiling). Its isolated regression
passes, including the affected live-registration case in about 47 ms; native
platform and compatibility jobs passed. No installed app or shared OpenCode
daemon was changed or restarted.

Refs #1008.
2026-10-19 09:27:58 +02:00
Pascal André
b2d5b722ec
feat(automation): complete safe native window interactions (#1018)
Complete application-window interaction on the shared page engine introduced
by #1015, while preserving native window/run and document-scoped ref authority.

Add real pointer clicks, hover, stepped pointer dragging, wheel scrolling,
keyboard chords and bounded text waits. Flat tool commands are validated at
both bridge boundaries. Clicks refuse covered targets; press(ref) refuses a
target that cannot acquire focus, so Enter never reaches an unrelated draft.
Drag requires both ends visible instead of scrolling under a measured source.
Preferences remains outside automation; focusing Preferences or a remote
window falls back to the most recently used local application window.

Trusted native input carries monotonic provenance intervals. Human Form
decisions check the originating event timestamp, not the time a stalled
renderer handles it; delayed keys and their implicit submit clicks remain
automated after lease expiry. Fixed page functions keep their depth marker.
Native events use the page's epoch clock, and cleanup preserves the original
press timestamp, independently of configured command timeouts.
Failed sequences best-effort release held buttons and keys, including when
the cleanup mark write itself times out. An admitted input timeout is reported
as an unknown outcome with inspection-before-retry guidance: the renderer may
deliver the input or finish the click later. Never replay these inputs.

Validation covers the actual Mission editor and InterruptionDock in isolated
Chromium, including a 16-second renderer freeze and a control human click.
Targeted server tests cover key mapping, both bridge parsers, focus/target
fences, failure cleanup and late input outcomes. Full server validation,
isolated OpenCode 2.0.31 automation, bundle regression and Tauri compilation
also pass; typecheck diagnostics remain at baseline. No installed application
or shared daemon was changed or restarted.

Four independent gatekeeper passes; the final review reports zero actionable
P0-P3 findings at 0ffb5a53b425500336854902c7068cd2233695f4.

Refs #1008.
2026-10-19 08:48:14 +02:00
Pascal André
7eb401eb18
fix(files): keep the Changes stage icon after the row's overflow menu opens (#1021)
FileRowActions renders each secondary action inline and in the row's overflow menu, which replaces the inline buttons on narrow rows. The Changes stage/unstage action created its icon once, so opening the menu moved that DOM node and, once the row widened again, the inline button had no icon. The icon is now a getter, like the Workspace tree's row actions; the action array keeps its identity for large inventories and the pinned preview eye, which never overflows, is unchanged.

Validation: git-changes-scale opens and closes the menu on a narrow row, widens it and checks the inline stage icon (0 icons before the fix); git-changes-scale 6/6, git-history 20/20, typecheck.
2026-10-19 08:24:15 +02:00
Pascal André
f800a8bf05
fix(ui): initialize header menu memo after its dependencies (#1019)
## Summary
Fix the project-shell startup crash introduced by #1017.

Solid evaluates `createMemo` immediately. The header menu memo called
`paletteOpen()` and `showingInfoView()` before those accessors were
initialized, throwing a ReferenceError before the shell mounted.

Move the memo below both dependencies. Memoization, menu behavior and
icon ownership remain unchanged. No new infrastructure or duplicated
test fixture.

## Validation
- UI typecheck passes.
- Three existing Chromium real-shell tests pass: request-badge
navigation, held locale loading, and narrow header actions.
- The existing request-badge test reproduced the crash on the unmodified
base during the second review of #1017.

## Maintenance note
`packages/ui/src/components/instance/instance-shell2.tsx` is already
oversized (1,328 lines); unrelated refactoring is intentionally out of
scope.
2026-10-19 08:09:08 +02:00
heunghingwan
e56b286a54
fix(transcript): keep stream events from re-rendering unchanged messages (#1017)
## Problem

A DevTools performance trace captured on the deployed web UI during an
active streaming session (44 s recording) showed the renderer main
thread saturated by the backend events stream:

- 158 stream events, **23.2 s** of the 44 s spent inside the `onmessage`
→ `dispatchBatch` → projection → re-render chain (avg 147 ms/event, max
503 ms)
- one **10.6 s** long task draining a backlog of queued frames (1,597
nested JS calls) — the UI froze completely while it ran
- **10.9 s** of pure DOM mutation, 8.8 s of it inside lucide icon
components re-inserting SVG nodes that had not changed
- **2.3 s** of `JSON.stringify` comparing message parts of unchanged
messages

Every stream event re-projected the whole resident message window and
invalidated every mounted message row, not just the one being streamed
into.

## Root causes

1. **Record identity churn** — `hydrateMessages` built a brand-new
`MessageRecord` (and part map) for *every* message in the window on
*every* event, even when nothing changed. Replacing the objects notified
every Solid subscriber of every mounted `MessageBlock`.
2. **Fresh action-menu arrays** — message/tool action menus rebuilt
their item arrays with fresh JSX icon elements on each evaluation,
evaluated twice per render (`<For each>` + the `data-action-overflow`
boolean). `For` keys by reference, so every re-evaluation remounted
every button and icon.
3. **Per-frame reactive pass** — each stream frame ran its own full
`solidBatch` dispatch, so a busy main thread draining N queued frames
did N full passes in one task.
4. **Serializing to compare** — `havePartsChanged`/info comparison used
`JSON.stringify` per part per message per event.

## Changes

- `message-v2/instance-store.ts`: `hydrateMessages` now reuses the
resident record object whenever content, explicit timestamps,
role/status and prompt-display metadata are unchanged; a fully identical
snapshot returns before any store write, session-entry update or
session-revision bump. Comparisons use a new allocation-free structural
equality (`partDataEqual`) with early exit instead of `JSON.stringify`.
- `lib/event-transport.ts`: same-tick stream events are buffered FIFO
and flushed as one batch per microtask; buffered events flush
synchronously before error/disconnect reporting and on `disconnect()`,
so delivered events are never dropped. `serverEvents.dispatchBatch`
stays synchronous, so all existing tests and fixtures are unaffected.
- Action menus memoize their item arrays (`createMemo`): message-item,
tool-call (dual memo mirroring message-item), message-block (passed as
the `headerMenuItems` accessor), prompt-input, WorkspaceTree (per row),
instance-shell2. session-list and FilesPanel already used the equivalent
stable-array-with-lazy-getters pattern.

## Design note: rejected approach

A reference-anchored "skip projection when the SDK list is unchanged"
fast path was implemented and then withdrawn: the SDK reduce mutates
message rows in place through Solid `produce` (`text.text += delta`), so
list and row references stay stable across content changes and identity
can never prove a session unchanged (the replay tests caught this
immediately). Projections always run; record reuse inside
`hydrateMessages` keeps them cheap. This is recorded in AGENTS.md and in
a comment at the projection site to prevent reintroduction.

## Behavior compatibility

- Record `updatedAt` semantics: reuse only fires when an explicit
`updatedAt` is absent or equal, so timestamps never freeze.
- A content-identical forced refresh now *keeps* the resident
`messageInfo` reference instead of swapping it — matching the documented
"identical snapshot must not invalidate caches" invariant. One assertion
in `session-request-authority.test.ts` was updated from `notStrictEqual`
to `strictEqual` accordingly.
- `instance-store.test.ts` gains identity-reuse, reference-stable-parts
and replacement-on-change cases; `opencode-transcript-replay.test.ts`
gains a real-projection case proving a stream delta replaces only its
own record (bounded-window eviction aside); `event-transport.test.ts`
(new) covers coalescing, FIFO order and error/disconnect flush ordering.

## Validation

- UI unit suite: 1,618/1,618 pass; `tsc --noEmit` clean
- Browser: message-scroll 128/128, render-cost 17/17 (tool-call rows),
git-changes-scale + git-history 26/26 (WorkspaceTree/FileRowActions),
search-highlight 11 pass + 1 pre-existing skip
- Two `history-navigation` tests fail identically on a clean checkout
(verified via stash A/B) — pre-existing environment flakiness, not
caused by this change

---------

Co-authored-by: Pascal André <pascalandr@gmail.com>
2026-10-19 07:45:32 +02:00
Pascal André
1932da9307
feat(automation): shared page engine with budgeted outlines, select and replacing type (#1015)
codenomad.inspect/act could not fill real forms (#1008): type appended to a
prefilled field, native <select> could not be changed, and inspections were a
flat JSON list silently cut after 750 nodes.

Page automation now lives in a transport-agnostic engine under
packages/server/src/automation/page/, used by DeveloperCdp for the CodeNomad
window and designed to carry the browser preview next:

- snapshot.ts renders the accessibility tree as an indented plain-text outline
  with refs. Wrappers, unnamed images and text repeating a name computed from
  contents are dropped; fields show their value (numeric ones included) instead
  of inner nodes; native selects list options with selected/disabled. A
  32 000-character budget is shared among siblings: small parts stay whole,
  large ones collapse in place with "(+N hidden)" or keep their first and last
  items around "(+N more)". inspect accepts ref (one subtree) and query (exact
  role, or text in name/value/description, with contents and ancestors).
- refs.ts names a DOM node per target, run and navigation epoch; a ref keeps
  its name and stays usable for the whole page generation.
- actions.ts acts through fixed page functions whose inputs travel as protocol
  arguments. type refuses disabled fields and fields that cannot take focus
  (closed disclosure, modal dialog), so text never lands in another field; it
  replaces the field's text by default, clear: false appends (email/number
  through their value). Date/time/number/range/color values are validated
  before anything changes and keep the browser's normalization. select picks
  options by label, else value, dispatching input/change only on a change;
  clicking an option ref picks that exact option. Results return the field
  value or chosen labels; page exceptions are reported.

The trust model is unchanged and documented: in the CodeNomad window page
functions run under __codenomadAutomationDepth, clicks stay programmatic and
native text insertion yields only input events, so automation can never
produce the trusted dock interaction a human Form answer requires.

Validation: snapshot unit tests (exact outlines, query, scope, fair collapse,
budget property), updated controller/plugin/route tests, and a real-Chromium
regression driving DeveloperCdp over the DevTools endpoint, including the real
Mission editor (title replaced, chosen playbook reaches the created mission)
and every reviewed edge case. Full server suite green apart from a load-only
5 s timeout in index.test.ts that passes alone; isolated native automation
fixture passes on OpenCode 2.0.31. Three gatekeeper rounds; the last found no
P0-P3 issue.

Closes #1008.
2026-10-19 07:38:34 +02:00
Pascal André
ae51c67b3c
fix(notifications): keep the incomplete-recovery warning on screen (#1016)
Follow-up to #1014: the unified routing filed the pending-request recovery warning under 'A session needs your answer', whose default has no bubble, so the warning AGENTS.md requires to be seen once no longer appeared on screen. It now uses 'Projects and tabs' (Screen + History by default), which fits every cause and is not shared with third-party plugin toasts.

The interruption-dock regression counts recovery warnings by their constant title, since the question asked mid-test now adds its own needs-your-answer History entry by design; a warning rearmed by invalidation is still caught. AGENTS.md records that must-see warnings never use needsInput.

Validation: interruption-dock 19/19; pending-request, startup-priority, recovery-cause and routing unit tests 38/38.
2026-10-19 01:50:32 +02:00
Pascal André
8cecb08704
feat(settings): compact Settings, unified notification routing and autosaving Missions preferences (#1014)
Settings had grown long captions, inconsistent groups and two separate
notification circuits, and Missions preferences used an explicit draft with
Save/Reset/Reload that no other section needed. This pass makes the screen
shorter and the behaviour uniform without changing what is stored.

Compact layout. Rows keep a title; secondary explanations move behind a
focusable "i" tip (settings-info-tip.tsx). Folded parts share one quiet
disclosure style (settings-disclosure.css). In-group row lines use a new
--border-row token, fainter than the --border-divider between groups.
Providers splits Models and Web search into folded cards; Web search starts
closed and mounts nothing until opened, so its metadata is read on demand.
The separator between them spans the content pane. A global
`details > :not(summary)` box-sizing rule fixes folded content inheriting the
details slot's content-box, which made full-width selects overflow. Unused
messages are removed in all ten locales.

Notifications. Every showToastNotification declares a category, and
Settings > Notifications routes each category to On screen / History /
System from one table (lib/notification-routing.ts). Errors always reach the
screen and History so a rejection is never lost; transient progress messages
stay on screen only. Session needs-input and completion use the same routing.
Saved legacy choices (OS notifications off, needs-input/idle toggles) seed the
table until it is first saved. The tab-bar bell now always opens History.

Keyboard shortcuts. The catalog lists the 24 initially unbound palette
commands too (73 actions), reusing their translated names and keeping existing
aliases as one row.

Missions preferences. Each choice is written at once, compare-and-swapped
against the value stored at its turn. Edits made during a write stay enabled
(disabling a focused control drops keyboard focus) and queue behind it. A
rejected or unconfirmed write is never resent: later queued edits built on it
are dropped and a read-only reload shows the stored values. Reload appears only
when that read fails or the document is unreadable. Agent/model choices now use
one Agent/Model/Reasoning row per role in every scope and at creation, with no
second selector level, sub-fold or Debugging special case. In a mission type,
rows show the inherited global values with a "Global" mark. Editing a field
customizes the role from those values, and a revert icon inherits again.
Inherit, explicit native default and a selected profile stay distinct. Reset
(confirmed) lives in that fold and clears only the shown scope's agents and
models. The project depth saves on Enter or blur; invalid values are flagged
and never written. Reads and writes make the field read-only so focus stays,
and refreshing is offered only after an error or an unconfirmed write. The
depth group is titled "Subsession nesting depth" and shows "OpenCode effective
depth"; scope badges are gone.

Validation: typecheck; unit tests for notification routing, mission i18n
alignment and profile summaries; browser suites mission-defaults-models (new
autosave, conflict, queue, reset and inherited-row tests), mission-control,
mission-editor-profiles, mission-editor-lifetime, mission-create-*,
mission-panel-*, websearch-settings, preferences-startup(-state),
plugin-controls, project-tab-close, new-instance-launcher, tab-chrome,
appearance, git-history and hotkeys-settings.

Review fixes.

Missions defaults: a queued edit now touches one role, or one scope's
task-session policy, and is refused when that stored value is no longer the
one it was made from. A choice made while a rejected write was being reread
could otherwise copy the whole scope from a stale view, resend the rejected
choice and erase another window's change. Independent edits still apply to the
value stored at their turn. The Settings rows report one role at a time
(MissionProfileControls.onRoleChange); creation keeps its whole-request change.

Project depth: pressing Inherit keeps focus in the field, so leaving the field
for the button no longer saves the typed value first and loses the click; it
stays focusable while a read or write runs. A write acknowledged after the
section was left is accepted and reread on the next activation instead of
being shown as unconfirmed. Escape discards an unsaved value without writing.

Notifications: the routing table builds a quick second click on the pending
table instead of reverting the first. Routing is read untracked so effects that
notify do not rerun on every settings change. The link-open error goes through
routing. Web notifications without permission are skipped silently. Wording
that still mentioned a global enable switch now names the System column. A
never-saved former system switch deliberately takes the new defaults
(product decision); a unit test records it.

Settings: info tips toggle on tap for touch screens (Kobalte tooltips ignore
touch hover and close on click, and a tap first focuses the trigger, so the
target state is decided at press time). Settings switches expose role=switch.
The palette strip remeasures when it mounts late. AGENTS.md now records the
measured palette layout, the Settings switch round exception and the shared
OpenCode disclosure styles; dev-docs/MISSIONS.md the autosave semantics.
Removed dead CSS and the unused MissionTaskModeControls children prop; the depth
input uses the field surface and its info tip sits outside the heading.

Validation: typecheck; unit tests for routing, profile role edits and mission
i18n; browser suites mission-defaults-models (new queued-role and touch tip
tests, updated rejection test), mission-control (Inherit after typing, write
acknowledged after leaving, Escape), mission-editor-profiles, appearance,
hotkeys-settings, mission-settings-brief, notification-icon-badge,
plugin-controls, preferences-startup(-state), preferences-window,
provider-accounts, git-history, remote-access/control-settings,
websearch-settings.
2026-10-19 01:23:35 +02:00
Pascal André
b4f1dde4f8
feat(missions): coordinator hand-offs, task interrupts and native decision evidence (#1013)
Completes the coordinator frictions of #1009 left after #1011 (points 4 to 9)
and repairs the recurring Wayfinder human-mark journey checks.

mission.interrupt({ taskKey }) stops a retired native task's conversations
through the authenticated bridge: the coordinator's direct children whose first
user message carries that task's canonical assignment header. Each family is
interrupted deepest first, then the conversation, with lifecycle Stop's
post-interrupt recheck, settle and quiet verification; queued inputs are
cancelled under the worktree-deletion fence. It refuses while any open task
lives in that conversation's family (reuse or nesting chains, or its actor),
and never touches the coordinator, other tasks or the plan.

mission.prepare creates a paused one-time hand-off through the editor's
prepareMissionCreation pipeline, holds and fences, and never starts it. A
trusted preparedBy gives the preparing coordinator read-only visibility
(inspect({ missionID }), inspect({ list: true })), never control. Retries read
the journal's creation event first and are reported uncertain, never as
nothing created.

mission.inspect({ taskKey }) adds the task's observed native conversations as
advisory evidence; the task-key parser is shared with the UI. A native
Wayfinder decision readout may omit its artifact: the gate's discover() derives
candidates from exact native structure only and verifies each; only a complete
discovery with exactly one candidate fills it. requestID "auto" briefings may
let the server pick the revision. One replacement may merge several retired
tasks, and the task reader lists every predecessor. Scheduled passages, which
have no backend route, are not offered the new tools.

The Q and N journeys queried a retired human-marks key pattern; they now read
HUMAN_MARK_STORAGE_PREFIX, and the Q UI run exercises discovery natively.

Validation: the full server suite passes (2,383 tests, 7 existing skips).
Isolated native fixtures pass against a private OpenCode 2.0.31:
test-missions-native, trajectory, contract, family-control, and recurring
journeys ABCD and QN. The task-reader browser suite and bundle regression
pass; typechecks show only existing diagnostics. Three gatekeeper rounds; the
last found no P0-P3 issue.

Closes #1009.
2026-10-19 00:34:27 +02:00
heunghingwan
ae85369433
fix(server): stop destroying the SSE event stream on single oversized frames (#1012)
## Problem

The browser's `/api/events` SSE stream was hard-destroyed whenever a
**single event frame exceeded the 1 MiB backpressure limit**. Native
message parts embed images as `data:image/...;base64,...` URIs, and
base64 inflates bytes by ~1.33× — so any conversation where the model
reads a screenshot (typically >700 KB PNG) routinely produced >1 MiB
event frames.

Each occurrence killed every connected browser client mid-stream
(surfacing as `net::ERR_HTTP2_PROTOCOL_ERROR` through HTTP/2-terminating
proxies), triggering an EventSource reconnect storm with visible
fallout:

- transcript force-reloads — previous images reverting to their running
state;
- status panels (provider usage, MCP) blanking and refetching on every
reconnect;
- main-thread freezes from burst re-hydration (only compositor-driven
image animation kept running);
- a repeating cycle: reconnect → next oversized image frame → destroy
again.

Models that never touch images never triggered any of this.

## Root cause

`write()` in `server/routes/events.ts` conflated **one large frame**
with **sustained backlog**: both hit `handleClose(true)` →
`reply.raw.destroy()`. A single large frame is legal in SSE and can
simply be paced by socket drain. There was also a second kill path:
after a large frame's `write()` returned `false`, its bytes were still
counted toward `bufferedBytes`, so the *next* (small) event crossed the
limit and destroyed the connection.

## Change

- A single oversized frame is now always written through; the socket
write buffer plus the existing **10 s drain timeout** pace and
eventually disconnect a client that never consumes it.
- The force-destroy now applies only while backpressured, when the
**queued pending backlog** exceeds `backpressureLimitBytes` — i.e. a
genuinely slow consumer accumulating events, which is what the guard was
for.
- Byte accounting fixed in both directions: frames already handed to the
socket no longer count toward the queued backlog (`write()` and
`handleDrain()`'s re-attempt path).

## Edge cases

- Per-client memory: one in-flight oversized frame sits in the Node
socket buffer. Event size stays bounded upstream by the native event
relay's payload budget, and the drain timeout still bounds a stuck
client.
- The pending-queue disconnect and drain-timeout semantics are
unchanged; the heartbeat/ping-pong and CORS behavior are untouched.

## Out of scope (deferred to upstream)

Stripping image bytes out of event payloads entirely — stubbing `data:`
URIs at the relay and hydrating them through bounded native message
reads — is **not** part of this change. That would additionally remove
the multi-megabyte JSON parse cost on the renderer main thread, but it
alters the event payload contract, so it is left for an upstream
decision. This PR fixes the transport-level destroy only.

## Validation

- New regression test: a single over-limit payload (512-byte frame under
a 256-byte limit) is delivered in full, the stream stays connected, and
subsequent events flow — this test fails on the previous code, which
destroyed the connection instantly.
- Existing SSE backpressure suite (CORS passthrough, queue/flush on
drain, deterministic drain-timeout disconnect, pending-buffer
disconnect) — all green, unchanged.
- `tsc --noEmit` clean for the server package.

---------

Co-authored-by: Pascal André <pascalandr@gmail.com>
2026-10-18 23:16:35 +02:00
Pascal André
1173beb37c
fix(missions): compact coordinator tool results and state the decision rule (#1011)
Coordinators driving a real Wayfinder mission hit three costly frictions
(#1009): every mission tool mutation echoed the whole map (about 50 KB after
ten tasks), the canonical assignment prompt only existed inside that
response, and the decision role read as a self-contained analysis although
completing it requires the human's own answer to a native question.

mission.delegate, revise, report and briefing now return a compact readout
(missions/tool-result.ts): disposition, current revision, status, run state,
frontier and the affected tasks, plus the contract and exact assignment
prompt for a declared native task. Briefs, reports and the objective stay
out; mission.inspect keeps the full map, and mission.inspect({ taskKey })
rereads one task with its contract and current prompt. The returned
revision feeds expectedRevision and basedOnRevision directly. Reports name
the task the control actually resolved, including an independent actor's
omitted taskKey.

The native Wayfinder decision role, the coordinator contract and native
decision declarations state the rule verifyHumanDecision enforces: the
task's own conversation asks through the native question tool and the user
answers in CodeNomad; self-contained analysis belongs to research.
Independent-mode coordinators, which that gate does not check, do not get
it. The refusal shows the exact provenance shape the gate validates, kept
aligned with nativeDecisionProvenance by a test. Refusals for a withdrawn
readout and unsettled withdrawn work explain what to do, a briefing citing
missing or retired tasks names them, and the Missions skill matches.

No journal, gate, ownership, RPC or UI contract changes. Interrupting a
retired task's native child, multi-task replacement, a paused-mission
creation tool, native execution visibility and Form IDs remain open.

Validation: 1,104 Missions server tests pass (one existing skip), with new
compact-result, task reread, retired readout, briefing source, implicit
report key, refusal shape and decision-rule tests. The isolated native
Missions, trajectory and contract fixtures pass against OpenCode 2.0.31.
Server typechecks show only the existing diagnostics. Two gatekeeper
rounds; the second found no P0-P3 issue.

Refs #1009.
2026-10-18 22:28:39 +02:00
Pascal André
afe0f877e5
fix(sessions): show idle subsessions on collapsed parent chevrons (#1010)
Collapsed parents now show a steady green chevron when their subtree has
a visible Idle badge. Previously only input requests, work and compaction
contributed to the chevron, hiding the completion hint in collapsed trees.

Extend the existing descendant counts and reuse the session row's visibility
policy, including unseen-subagent retention, expiry and seen/fade cleanup.
Input, work and compaction keep priority; retry and interrupted states do
not become green idle hints. Expanded chevrons remain plain. Use the shared
idle color token and counted accessible labels in all ten locales.

The existing real SessionList browser regression now checks nested idle
completion, the matching visible badge, green color and steady animation,
priority, plural counts, preference changes, timer expiry and seen/fade
cleanup. Its isolated Vite cache and immutable preference snapshots keep
the test deterministic. AGENTS.md records the updated visual contract.

Validation: 29 unit tests and the browser regression pass, independently
rerun by the gatekeeper. Rendered capture inspected. UI typechecking retains
the known unrelated help-url.test.ts diagnostic with ES2022 libraries.
COMMENT review at 5777b735a9d311809524dbb00a3c45663e6c664f found zero
actionable P0-P3 findings. Full CI was still running at admin merge.
2026-10-18 21:35:28 +02:00
Pascal André
a8e3e14e49
fix(sessions): respect OpenCode's resolved default agent (#1007)
Keep the order returned by OpenCode agent.list(), which places the resolved
host default first. Sorting by ID silently replaced a plugin-provided
orchestrator default with build when creating a conversation, and selected
the model for that wrong agent.

The existing creation and per-agent model selection now consume the native
order. Explicit agent choices retain priority. No plugin-specific resolver
or model-chain implementation is introduced.

Add and register a regression covering native default order, orchestrator's
configured model, explicit build selection, and a changed native default
after refresh. The regression fails before the fix and passes afterward;
all 180 focused tests pass. UI typechecking retains only the pre-existing
help-url.test.ts error with ES2022 libraries.

Independent gatekeeper COMMENT review found zero actionable P0-P3 findings
at e0e38d5a104b15c3d0173f84877d5c1dd96b5784. Full CI was still running at
admin merge; no shared service or installed application was restarted.

Fixes #1005.
2026-10-18 15:39:44 +02:00
Pascal André
d840271d6b
fix(files): keep large Changes lists responsive across refreshes (#1006)
## What changes for the user
Files → Changes stays responsive on checkouts with thousands of changes.

- **Long lists show 200 files per section.** The rest is behind a "N
files not shown · Show 200 more" line, which moves focus to the first
newly shown file. Section counts stay the full totals.
- **Selected files stay reachable.** The current, previewed and
bulk-selected files always appear, right after the first 200 if needed.
- **Refreshes no longer rebuild the list.** When a filesystem change
leaves the status unchanged, nothing is redrawn. When a file changes,
only its row updates.

## Why
On the installed build, a checkout with 2,680 git changes froze the app
while Files was open: clicks took about a minute and WebView2 reached
4–8 GB.

CDP showed:
- about 62k of the page's 66k DOM nodes under the Changes list;
- 38% of a CPU profile in DOM `insertBefore`/`remove`.

Every native `filesystem.changed` refresh produced fresh objects, so the
keyed `<For>` recreated every row. Each row also scanned the selection
twice. The body stays mounted while hidden in Workspace/Commits, so the
cost applied there too.

## How
- `reuseUnchanged()` in `git-changes-model.ts`:
  - an identical status keeps the previous array, so nothing recomputes;
  - a changed status reuses the objects of unchanged records.
- The new `git-change-window.ts` holds the 200-row window and its reveal
state. The reveal survives refreshes and resets on worktree change.
- `createSelector` gives each row its selection state without a per-row
scan. Shift-ranges follow the rendered rows.
- The unused `entry` back-reference on `GitChangeListItem` is removed,
since it would go stale once items are reused.

## Measurements
Real `FilesPanel` fixture with 2,770 rows (700 staged, 2,070 unstaged),
Chromium. These are dev builds, so absolute times are pessimistic.

| | Before | After |
|---|---|---|
| DOM nodes after opening Changes | 63,197 | 9,216 |
| Refresh with nothing changed | 63,010 nodes replaced, 32.7 s blocking
| 0 nodes, ~3 ms |
| Refresh with one file changed | 63,010 nodes replaced | that row only
(23 nodes), ~10 ms |
| Selecting a row | 2,777 attribute changes | 4 attribute changes, plus
the auto-selected row leaving |

## Validation
- **UI typecheck:** clean.
- **Unit tests:** 23/23 (model, row window, hook, files-panel state).
- **Browser:**
- new `git-changes-scale` 3/3: row cap, zero mutations on an identical
refresh, single-row update, Show more focus and persistence, worktree
reset, cross-section ranges, stage button, preview past the limit,
drag-and-drop;
  - `git-history` 20/20;
  - `git-init` 3/3.

Oversized files touched:
- `useGitChanges.ts`, ~580 lines;
- `git-history.test.ts`, 980 lines.
2026-10-18 15:33:54 +02:00
Pascal André
87639f0275
fix(transcript): reveal display-truncated text in place instead of a copy-only suffix (#1000)
Some checks failed
Publish help / build (push) Has been cancelled
Publish help / deploy (push) Has been cancelled
## Summary
#890 replaced the copy-only footer for **omitted message parts**. A
second display bound from #648 remained: every rendered text block was
cut at 10,000 characters with "[Output truncated for rendering; copy to
access the full output]". Long subagent reports, assistant prose and
tool outputs could only be read by copying, even though the model
received everything.

**Now:** a truncated block shows its prefix, then at the exact cut a
localized line, "N more characters hidden to keep this message
responsive", with **Show all**. Above a ¼ MB remainder the button reads
**Show 250,000 more**. It reveals the rest in place.
- **Reveal state** sits next to #890's part reveals
(`record-display-cache`), keyed by instance/message/part/section. It
survives virtualization and session switches and resets with the
instance.
- **Reading position** is held while the text grows. Tool outputs grow
inside their own pane, and focus moves to the revealed text.
- **Copy, search and speech** keep reading native content.

## Bounds (from measured Chromium render cost)
| Content | Shown before reveal |
|---|---|
| Prose: text parts, compaction summaries, subagent prompts/reports,
skills | 50,000 chars (was 10,000) |
| Tool output: shell, read, write, diffs, edit/patch, execute, webfetch,
inputs, errors | 10,000 chars (unchanged) |
| Each reveal step | up to 250,000 chars |
| Syntax highlighting | off above 100,000 chars |

## Implementation
- **Where bounding happens:** it moved from each renderer into the
rendering components: `renderMarkdown` (with a `codeLanguage` option),
`renderDiff` and a new `BoundedText`. They use a pure window helper
(`lib/render-text-window.ts`) that cuts on nearby line breaks and never
splits a surrogate pair.
- **New pieces:**
  - `components/hidden-text.tsx`;
  - shared `reveal-focus.ts` (also used by #890's reveal);
- `transcript-alignment.ts`, which holds the reveal point through the
list's existing `alignElement`.
- **Removed:** the dead `renderAnsi` path and the copy-only Markdown
source button (with its now-unused i18n key).
- **Docs:** `dev-docs/TEXT_DISPLAY_BOUNDS.md`; `AGENTS.md` extends the
hidden-parts bullet.

## Validation
- Unit tests:
  - `render-text-window`;
  - `record-display-cache` / instance store.
- New `reveal-truncated-text` browser suite:
  - reveal in place;
  - anchor held while following the bottom;
  - tool pane reveal;
  - persistence across scrolling and session switches.
- Updated suites: `full-source-copy`, `markdown-latest-request`,
`mission-primitives`, `render-cost`.
- 31 relevant browser files, run one at a time: 459 pass, 1 opt-in skip.
UI typecheck clean.
- Known follow-up: reasoning text keeps its own silent 10,000-character
cut.

Oversized files touched: `message-block.tsx` (~1,963 lines),
`tool-call.tsx` (~998), `message-item.tsx` (~828),
`tool-call/renderers/task.tsx` (~581).

An independent gatekeeper review will be published before the admin
merge.
2026-10-18 05:01:03 +02:00
Pascal André
16fad5279e
fix(usage): explain the ChatGPT token-sharing quota limitation (#999)
OpenAI's "Sign in with ChatGPT" login can be used for chat, but OpenAI does
not currently expose its quota through the Codex usage endpoint. The Usage
panel previously presented this as a temporary failure and suggested that
reconnecting might restore it.

The native Codex reader now detects this exact selected login and returns a
dedicated, credential-free reason, so the panel explains the limitation in
all ten UI languages instead of calling it a temporary outage. No quota
request or account fallback is attempted for this login, and its caches are
cleared if the selected account changes.

This is a diagnostic explanation only. It does not restore quota access for
this login. The legacy Codex browser/device login keeps its existing reader
and continues to show quotas. The same limitation is documented in
dev-docs/PROVIDER_ACCOUNTS_UX_PLAN.md, with OpenAI's confirmation linked.

Validation: 19 server tests pass (native selection, account changes, cache
revocation, routes, credential redaction); 14 provider-usage browser tests
pass (English/French 320 px, account-change recovery, isolated Vite cache).
Typechecks report the same baseline diagnostics as before the change, with
ES2022 libraries: 14 server errors and the known UI help-url.test.ts error.
No installed build or shared service was restarted.

Independent gatekeeper COMMENT review found zero actionable P0-P3 findings
at 6742620d0153ae1fee6b3f7e0c25b4a8d4dfdf5b.
2026-10-18 04:27:19 +02:00