Commit Graph
16002 Commits
Author SHA1 Message Date
Brennan BensonandNarry 7fbb47e713 fix(omp): use OMP model roles, selectors and effort in chats and workers (#38713)
* fix(omp): launch OMP chats with their saved model and accept OMP effort

OMP's Chat UI model option names only listed provider/id values, so a
worker started with an OMP role or provider/model:level selector failed
before its first message. OMP resolves those on its launch line, so the
chat's saved model now rides 'omp acp --model', and a new session counts
it as applied. OMP's launch catalog gains its thinking levels, so
--effort is accepted and applied (--thinking in a terminal, the session's
thinking option in a chat).

Tracked in STA-13562.

* fix(omp): keep OMP role chats working after a restart

OMP's ACP model option takes listed provider/id values only, and a load restores the conversation's own model, so a role-started chat failed with "Unknown ACP model" on its first send after an Orca restart.

- New chats keep the saved model on OMP's launch line; resumed chats no longer pass it, so a renamed role cannot stop a good conversation from opening.
- When OMP's model option refuses a value as unknown, Orca applies it through OMP's own /model, the resolver --model uses, as a hidden send-time command. It counts as applied only on OMP's "Model set to" reply and a normal end; cancel or timeout retires the process.
- The command's reply and title notices never reach the chat, its status or unread state.
- Proof from the launch line or /model is retired when OMP changes its model or thinking itself; ordinary model-option proof keeps its existing rule.
- A refusal names the agent and the next step; on OMP older than 18.1.9, whose /model takes ids only, it also names the release to update to, carried on the modelUnavailable fact.

Tracked in STA-13562.

Co-authored-by: Narry <3047793+NarryG@users.noreply.github.com>

* fix(omp): keep role steers in order and say when OMP has no model

- A role steer that OMP's model option refuses now holds its place among queued steers: answers ahead of it are cancelled as they start, and a Stop or teardown while it waits withdraws it without sending /model or the message.
- OMP's "No model selected" (an unresolved launch role, or signed out) is reported as its own failure, "OMP has no model selected. Check your model selection and OMP sign-in, then send again.", instead of a sign-in failure; the next send applies the selection again.
- A cancelled model command reads as not sent, and an option write settles once.

Tracked in STA-13562.

Co-authored-by: Narry <3047793+NarryG@users.noreply.github.com>

* fix(omp): keep OMP's no-model text in the log and test a late /model answer

Tracked in STA-13562.

Co-authored-by: Narry <3047793+NarryG@users.noreply.github.com>

* fix(omp): a Stop still withdraws a waiting role steer after main's turn changes

Main now clears stopAt when a turn finishes, so reserveSteer counts Stops
instead of reading stopAt. Refusal wording moves to acpPromptRejection to
keep the turns file within its line budget; behaviour is unchanged.

Co-authored-by: Narry <3047793+NarryG@users.noreply.github.com>

* fix(omp): a Stop withdraws a role message anywhere in its preparation

A message that falls back to OMP's /model now counts as a held steer from
before its option writes until it is admitted. A Stop in that window
withdraws it unsent, even with no turn running; ordinary messages keep
their Stop behaviour. Restores the launch-role no-resend test main's file
removal dropped.

Co-authored-by: Narry <3047793+NarryG@users.noreply.github.com>

* fix(omp): a Stop before OMP refuses /model reads as a withdrawn message

A Stop the role message already took outranks OMP's own refusal of the
model command, so the message reads as cancelled and never sent rather
than as a model failure. Pins that a Stop with no turn running is still
refused while an idle send's first model-option write is pending: the hold
starts when the send falls back, not before its option writes (the host's
Stop aborts the send's signal there).

Co-authored-by: Narry <3047793+NarryG@users.noreply.github.com>

* test(omp): put the cast's safety note on the line above the cast

* test(omp): cover the new failure copy on phone and desktop, and OMP's thinking levels

Tests the no-model sentence and the update hint in English and one
translation on each surface, every level omp --thinking takes, and that a
missing model classifies as the agent's own refusal.

* test(omp): import the launch module once in the catalog test

---------

Co-authored-by: Narry <3047793+NarryG@users.noreply.github.com>
2026-11-06 02:21:09 -08:00
Brennan Benson 3659c0b787 Bound the lifetime of claimed worktree preparations (#38773)
* Keep a lifetime owner for claimed worktree preparation

* Record preparation ownership loss before retirement

* Type the preparation lock-write fixture without an assertion

* Revoke preparation cleanup when ownership changes
2026-11-06 02:15:14 -08:00
Brennan Benson b3b97d8068 Show OpenCode choice questions in chat (#38872)
* Show OpenCode choice questions in chat

* Preserve form replies for omitted fields and cover rendered questions

* Keep cross-process form tests out of renderer typecheck
2026-11-06 02:13:58 -08:00
Brennan BensonandClaude Opus 5.5 2648941358 Fork a Chat UI chat from any finished response (#38326)
* feat(chat): shared contract for forking a chat from a response

Adds the create-time fork source, the host fork capability, the agent fork
level, the forked-from record relationship and the forked-from divider row.

* feat(chat): advertise each agent's fork level and a chat's fork support

* feat(chat): carry a fork source through chat launch, reload and retry

A launch with forkFrom {sessionId, throughItemId} keeps it on the create call,
the saved launch record, reload, retry and its holders, sends no new-chat
options, and is refused before create on a host that does not advertise forks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(chat): fork a native chat from a finished response on desktop

Finished root responses offer "Fork from this response" where the host and
agent can fork: disabled while support is unknown or a fork is opening, opened
in a new tab right after the source, with one toast naming the agent on
failure. The forked-from row draws as a divider linking to its source.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(mobile): fork a chat from a response in the message actions sheet

The sheet offers "Fork from this response" with the desktop's eligibility and
host capability, creates the fork through the phone's chat create, and draws
the forked-from divider with a link to the source chat.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(chat): a fork create replays only as the same fork

* Add native conversation copy adapters

* Create native chat forks with derived source markers

* fix(chat): offer a fork only on rows that show the agent's own message

A displayed row can be a tool call or a status projected as a plan while
reading as a response; both clients now check the journal row's own kind.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(chat): open phone and paired-desktop forks right after their source

The phone names its source tab as the create's placement and the host takes
that tab's group; a paired desktop sends the address captured at the click
when the fork has no tab of its own yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(chat): float a mid-turn response's fork action instead of adding a line

The action on a response the turn continues past hangs from a line of no
height, as the reply controls hang into the gap, so tool-heavy turns keep
their spacing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(chat): follow the chat's own fork verdict on both clients

The options read already says whether the installed agent forks; an observed
no hides the action, an unanswered read keeps it disabled, and yes enables it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Preserve native copy boundaries and rewind behavior

* Preserve imported fork tips and serialize create replays

* fix(mobile): open a fork's source through the session's own chat opener

The forked-from link now asks the controller's existing opener for the source
by history id, so it follows that opener's host, selection and reveal rules
instead of navigating on its own.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Project fork markers from touched journal groups

* Retain fork group restoration for delayed readers

* fix(chat): keep a mid-turn response's fork in its own right gutter

The floated action covered the start of the next row. It now sits beside the
response's last line, in a gutter as tall as the prose, so it adds no line and
never reaches the following row.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Preserve native fork chain boundaries and durable streamed copies

* Place phone forks beside their source in window publications

* fix(tabs): adopt a host's new tab where the host placed it

First adoption put every unseen tab at the end of its group, so a fork the host
published after its source landed last. A new tab now follows its nearest host
predecessor when that tab is here; tabs already adopted keep the user's order.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(chat): reserve the fork gutter on every root response from the first frame

The gutter came and went with the fork offer, so prose reflowed when a turn
finished or support resolved. A pane that draws forks now keeps it on all root
responses, and the fork sits only there, not also beside Copy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Return Codex copies without automatic provider renaming

* Resolve chat placement once at the current layout owner

* Preserve chat publication when saved opens are skipped

* Retry chat opens when queued placement changes

* Bound stale chat placement retries

* test: expect the OpenCode reader's imported fork tip

* perf(chat): keep the fork offer stable while a chat streams

Every stream commit made a new forkable-id set, so the fork context changed and
every offered response re-rendered. The set is reused while its members hold,
and rows get an action object that changes only with support or pending.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: keep the fork params and Claude fork worker inside main's new lint limits

* Test fork copy and source refresh scaling

* fix: merge duplicate imports and stringify the OpenCode fork tip id for main's lint rules

* Keep fork placement behind session tab projection boundaries

* test: annotate the Claude copy test's hoisted account homes instead of asserting

* fix: add the fork action and forked-from strings to the English catalogs

* test: give fixtures the fork copy their agents declare; add the forked-from string to the boot catalog

* test: keep registry fixtures consistent with the fork copy their agents declare

* fix(orcad): ship the Claude fork worker with the server; update tests for fork fields

* Test saved fork selections through import and first send

* Preserve ordinary chat publication while saving placement

* Show fork beside the copy button on phone chat responses

Long-press opens text selection on every current phone, so the actions sheet that held fork never opened. The fork button now sits next to each response's copy button.

* Keep the phone fork slot fixed and offer it on every forkable response

The fork slot is now held by the row's own kind, as desktop's gutter is, so finishing a turn or learning the host's support never moves the timestamp. Responses with no copyable text (a visual only) still get fork, and each button re-renders only when its own offer changes.

* Follow main's moved runtime modules in fork code and tests

* test: build the phone response-actions fixture without type casts

* Let the phone read the host's agents so its fork action can turn on

Phone QA found the fork button always disabled: the phone asks the host which agents can fork, and the host refused that read from paired phones.

* Retry the phone's agent-list read, and read again after a reconnect

A read lost to a dropped connection or a timeout left Fork disabled until the chat was reopened.

* Own the phone's agent-list retry timer in its own effect

CI's effect-cleanup check could not see a timer set inside the read's callback; each retry is now a state change with an effect that sets and clears one timer.

* Give each phone connection its own agent-list retries

A count left over from an earlier connection could leave a new one with no retries, so one failed read kept Fork disabled after the transport moved.

* Scope the phone's agent-list retries to each spell of discovery

When the host's fork support dropped and came back on the same connection, the earlier failures carried over, so one more failure got no retry. Each stretch of discovery now owns its reads, failures and retry timer.

* Keep the forked-from source link reachable by screen readers

The divider's separator role and row label hid the source chat button inside it; the rules are now decorative and the text and button read normally.

* Keep fork names owned by adopted chat records

* Use the fork icon for Fork from this response

* Show the phone meta actions only when a row has one

An empty actions group still shifted the time by the icons' edge margin on rows with no copy, fork or rewind.

* fix(chat): put Fork beside Copy in the response's hover row

The fork button sat alone in a right gutter far from the other row actions.
It now joins the response's existing hover controls next to Copy, where those
controls show, and the gutter is gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(chat): keep a finished turn's actions on its last words and hold the Fork slot

A turn whose last prose was followed by tool or thought rows showed no Copy
and no Fork anywhere until it was collapsed. The actions now belong to the
turn's last prose row, decided when the transcript's rows are built, and keep
the gap below it whole so they never cover the next row. Root responses also
keep an empty Fork slot when not offered, so resolving support never moves
the row's other actions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(chat): give a turn's actions only to a reply with words

An image-only reply after the turn's text took the response actions but could
not draw them, so the text lost Copy and Fork. Slot construction now picks the
turn's last reply by the same words the row draws and copies.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(chat): let an image-only response hold a turn's actions when its text is folded

A collapsed turn that ends in an image hides its earlier text, so no drawn row
could show the response actions and Fork vanished. Each turn's actions now go
to its last drawn response with words, else its last with images. Copy and its
context-menu target appear only where there are words to copy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Fix Claude fork cuts after local commands

* Extract the conversation copy input contract

* Preserve caller results at Claude fork boundaries

* test: check the copied fork for the wake answer without a type assertion

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-06 02:08:24 -08:00
Brennan Benson 0b93cef445 One chat's status change wakes only that chat (#38883)
* perf(native-chat): one chat's status change wakes only that chat

The renderer status feed copied every retained summary and called every
listener on each one-session update, and the status bridge walked every
open tab. Adopt the common keyed-entity pattern: update the session in
place, wake only readers registered for that session, and give the bridge
a session-to-tabs index. Option state now reads only the runtime
permission answer, so a reply preview no longer re-renders the chat.

* fix(native-chat): keep the phone's permission projection and prune stale retry members

Restore projectRuntimePermission for the phone app, ignore a malformed
conversation-name list as the names fold does, drop superseded bridge
members from the retry set, key resume-run ids by the run's start, and
update the continuation test mock.

* perf(native-chat): retry and resume-run checks look only at the touched host and chats

Keep the bridge's retry set per host so another host's update never scans
it, and check only the resume-run entries an update touched.

* test: give the launch create-message feed fake the per-chat subscription

* test: drop call-count assertions from the bridge contact-loss test

Keep the behavior it guards: no throw, revocation applied, later listeners
still notified, and the reconnect snapshot projects.
2026-11-06 02:06:50 -08:00
Brennan Benson 45b5a38a79 perf(native-chat): find a transcript by session id without walking the whole history (#38895)
* perf(native-chat): find a transcript by session id without walking the whole history

Claude lookups probe the chat's cwd bucket, then each project bucket once,
stopping at the first file, instead of reading every folder under projects/.
Codex and omp walks stop at the first match. Session-id discovery for a
transcript that has not appeared backs off to 30 s after its first minute.

* test: drop the launch transcript work-count test the bucket probe replaces

* fix(native-chat): let a cwd hint only fall back, keep its exact encoding, and honor a late cancel

* fix(native-chat): report a cancelled Claude probe instead of a miss; expect the launch cwd hint

* test: spy the walk the transcript resolver now uses in the OMP metadata test

* fix(native-chat): keep known file paths on the fast retry cadence; only id discovery backs off

* test: drop work-count assertions from the adoption budget and OMP metadata tests
2026-11-06 02:06:24 -08:00
Brennan Benson 1ac947c43c perf(native-chat): stream replies that mention Vec<T> without re-rendering the whole reply (#38879)
* perf(native-chat): a bare tag like Vec<T> no longer makes a streaming reply re-render whole

Only a tag the renderer keeps, left open at the end of its block, can wrap later
blocks; every other tag is escaped or unwrapped. Whole-document mode is now
re-derived on each append instead of latched.

* fix(native-chat): only HTML rendered as text skips whole-reply rendering

Tag counting could not prove a split reply renders like the whole one. Keep the
whole-document rule for every raw HTML node, and skip only nodes the unknown-HTML
transform replaces with text, which never reach the HTML parser. Cuts restart when
that policy changes.

* test(native-chat): drop the render-count test for Vec<T> replies

Main's test rules ban work-count tests; the remount test still covers the
user-visible behavior and the numbers stay in the PR description.
2026-11-06 02:05:56 -08:00
Brennan Benson f9e346cf8e Avoid JSON round-trips for desktop chat updates (#38936)
* perf(runtime): pass local stream responses directly over IPC

* test(runtime): explain the partial dispatcher runtime double

* test(runtime): observe object streams in the desktop account fixture
2026-11-06 02:05:35 -08:00
Brennan Benson 5b1ade1e0e Count paired-phone chat and launches in usage stats (#38847)
* Count paired-phone chat sends, launches and tab opens in usage stats

The host already knows when a request comes from a paired phone, so it
records the phone's chat sends, agent launches and opened tabs with the
same opt-out as the desktop's own counts. The desktop chat box tags its
sends as desktop.

* Count the views a phone opens and skip replayed sends

A phone picks chat or terminal view on its own, so the opened count now
comes from the stream it opens (chat transcript or full terminal), not
from tab activation. Replayed sends no longer count twice, and phone
sends report an unknown runtime.

* Count a phone's view only once its stream is open

A terminal counts after its snapshot reaches the phone, and a chat
transcript the host cannot read is not counted.

* Tag phone launches where the terminal launch is built

Keeps the launch and runtime identity files within their line budget:
the existing pane-agent reader is public instead of wrapped, and the
phone launch tag is applied where the launch reaches the terminal.

* Keep the phone launch tag when a launch creates a workspace

* Use type guards instead of casts in phone usage counts

* Never let a phone view count end the stream it observes

The agent is read only for a view not yet counted today, inside a guard.
Launch tests that check a caller's own label use a desktop caller, and a
phone launch that creates a workspace is checked to stay tagged mobile.
2026-11-06 02:04:43 -08:00
OrcaWinandOrcaWin 30724404d8 test: remove 872 low-value PTY and shell tests (#38943)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-06 02:03:00 -08:00
Neil a7e74b57a1 fix(runtime): read an idle agy composer above a custom statusLine (#38930)
A command statusLine replaces agy's '? for shortcuts' hint row, so the
composer_ready rule never matched and readiness held whenever OSC 7501
could not decide (no fresh program status). Accept the composer when up to
three status rows sit below it, no busy hint is among them, and no braille
spinner row sits above it (agy's only turn marker in that layout).

Fixes #34707 (STA-12555)
2026-11-06 01:28:09 -08:00
Neil dbb13c2c38 fix(codex): keep the clipboard SSH marker out of commands Codex runs (#38914)
Orca sets a synthetic SSH_CONNECTION so Codex forwards copies over OSC 52
for remote viewers. Codex's shell tool inherited it, so agy launched from
Codex treated the local host as remote and ignored its keyring login.
The wrapper now blanks only Orca's sentinel in Codex's shell environment
policy; genuine SSH metadata is untouched.

Fixes #37723 (STA-13596)
2026-11-06 01:28:04 -08:00
Neil 9b24b218fb fix(agent-hooks): repair Antigravity hooks after a Windows install or wiped config (#38899)
Hook CLI detection now reads the persisted Windows PATH like terminal launch
does, and an explicit enable while already enabled reconciles hooks so
`orca agent hooks on` repairs a wiped ~/.gemini/config/hooks.json.

Fixes #17672 (STA-6166)
2026-11-06 01:28:00 -08:00
Brennan Benson 6e2b5a98ad Cmd/Ctrl+Enter sends the first queued card after a Stop (#38877)
* feat(native-chat): holding Cmd/Ctrl turns Send into what Cmd/Ctrl+Enter will do

While the chord's modifier is held alone in a busy chat's composer, Send
becomes a labelled Steer, Queue or Steer queued button, and with an empty
composer the queued card it would steer is highlighted. Releasing the key,
pressing any other key, leaving the composer or the window clears it.

* fix(native-chat): the held-Cmd preview promises only what Cmd/Ctrl+Enter will do

The queue offers its head-card steer only while that card can go (not
being edited, no steer already on its way), Shift or Alt held first and an
open picker show no preview, and a draft Send can't take keeps its button
and reason. The card highlight follows the composer's Steer queued button
in CSS, dropping the state and callback that carried it.

* fix(native-chat): only the composer holding a preview listens for window blur

Every busy chat's composer added window and document listeners; now only
the one showing a held-Cmd preview does, and a hold that outlasts a Cmd+Tab
never shows.

* fix(native-chat): use the Button's own size for the held-Cmd pill, and clear its timer in cleanup

* fix(native-chat): no held-Cmd preview while an IME composes

The IME owns Enter during composition, so the chord shows nothing then. The
listener-count test goes: the work it guarded no longer exists.

* test(native-chat): clean up between held-Cmd preview tests

* fix(native-chat): the held-Cmd delay timer lives in its own effect

The key listener records whether the modifier is down; a separate effect
owns the delay's timer and its cleanup, as React Doctor requires.

* fix(native-chat): a double-click on the held-Cmd button acts once

Sending a draft can turn the button into Steer queued before the second
press, which would steer a queued card the person never chose.

* fix(native-chat): the held-Cmd button is a pill as tall as Send

* fix(native-chat): Cmd/Ctrl+Enter sends the first queued card after a Stop, as steering does mid-turn

* test(native-chat): pin which composers send the head card on an empty Cmd/Ctrl+Enter; goal mode never does

* test(native-chat): the send-state resolver takes only the transport fields it reads, so its test needs no cast
2026-11-06 01:14:09 -08:00
OrcaWinandOrcaWin 632cb37d4a test: remove 697 low-value agent and plugin tests (#38932)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-06 01:12:28 -08:00
Neil e648eb9f04 fix(plugins): say that full-access plugins can also control Orca (#37933)
* fix(plugins): say that full-access plugins can also control Orca

Worker plugins can use Orca's local runtime API, so the consent text now
mentions terminals and agents alongside files, network and programs.

* fix(plugins): keep the glossary term in the Japanese full-access warning

* test(e2e): expect the updated plugin full-access text

* test(e2e): expect the updated full-access text in the plugin demo spec
2026-11-06 01:11:55 -08:00
NeilandMartin Vlach f239f9d533 fix(ssh): apply a manual target's IdentitiesOnly setting on the built-in transport (#37926)
`ssh -G` prints `identitiesonly no` when ssh_config leaves it unset, so the
resolved value always won and a manual target's own IdentitiesOnly was ignored.
Manual targets now use IdentitiesOnly when either the target or ssh_config
enables it; ssh_config-backed targets are unchanged.

Co-authored-by: Martin Vlach <mr.huge@seznam.cz>
2026-11-06 01:11:13 -08:00
Neil 1983c2e12b refactor(ipc): remove the unused shell:copyFile handler (#37928)
The handler, preload bridge, and web stub had no callers.
2026-11-06 01:10:28 -08:00
OrcaWinandOrcaWin 590f945a5e test: remove 513 low-value persistence and benchmark tests (#38922)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-06 00:58:38 -08:00
Neil f5bdd62f41 fix(terminal): keep cold-restored scrollback in a tab hidden at relaunch (#38878)
* fix(terminal): keep cold-restored scrollback in a tab hidden at relaunch (STA-14063)

When the window predeclares its pane serializer, main skipped seeding its
host copy of the terminal, including for cold-restore payloads. A tab hidden
at relaunch paints the restored scrollback, but when the fresh shell's prompt
arrives while it is hidden, revealing the tab repaints from main's host copy,
which held only that prompt, so the pane came back blank.

Seed the host copy from a cold-restore payload even while the window owns
serialization; the window's xterm holds only this same payload, so it has no
richer history to prefer. Reattach snapshots and replays keep the old rule.

* test(terminal): verify cold restore through recovery snapshot
2026-11-06 00:56:00 -08:00
Jinjing 95af9000e3 Show switch-back note after enabling Chat UI (#38703)
* Show a switch-back note after enabling Chat UI

* Link Chat UI switch confirmation directly to default setting

* Clarify Chat UI switch confirmation and preserve keyboard focus

* 玩ip
2026-11-06 08:37:09 +00:00
OrcaWinandOrcaWin d9c9b2e755 test: remove 599 low-value persistence and migration tests (#38913)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-06 00:34:22 -08:00
OrcaWinandOrcaWin 687b0ee252 test: remove 52 low-value plugin, checks, and runtime tests (#38909)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-06 00:17:38 -08:00
Brennan Benson c5f1c59009 Let users hide the changed files card in chat (#38756)
* feat(native-chat): let users hide the per-turn changed files card

Adds a Changed files summary setting under Settings > Chat > Behavior (on by
default) and a Hide button on the card that turns it off everywhere, with a
toast linking back to the setting.

* fix(native-chat): confirm Hide only after the setting saves

Show Hidden only once the setting is saved and report a failed save instead;
keep the card header one line tall; cover saved settings without the key.

* test(native-chat): import getDefaultSettings from shared constants

* feat(native-chat): reveal Hide on card hover and move the setting to Appearance

The card's Hide control is now an icon with a Hide tooltip that appears on
hover or keyboard focus, and the Changed files summary switch lives in
Settings > Chat UI > Appearance, where Reset restores it.

* fix(native-chat): put the Hide reveal classes on a wrapper, not the Button

Button owns its effects; the hover and keyboard-focus reveal now lives on a
wrapper span, as other row actions do.
2026-11-06 00:05:16 -08:00
OrcaWinandOrcaWin 3385a667b1 test: remove 431 low-value profile, daemon, and journal tests (#38905)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-06 00:04:33 -08:00
Brennan Benson 3e5383cb75 Show a check's state on hover instead of beside its icon (#38897)
* fix(checks): show a check's state in its icon tooltip, not beside it

The status word repeated what the icon already shows. The icon now carries
the state as its tooltip and its accessible name.

* fix(checks): draw the status icon from the same state its label names

A finished check without a conclusion spun as pending while its tooltip said
Neutral. Both now come from one conclusion, matching the header counts.
2026-11-06 00:01:53 -08:00
Brennan Benson 9c68fc1d20 Show when each check ran and how long it took (#38731)
* feat(checks): show when each check ran and how long it took

Check rows in the right-sidebar Checks panel now show how long a
finished check took and how long ago it finished (or started, while it
runs), with exact start and finish times on hover. GitHub check runs,
legacy commit statuses and GitLab jobs carry provider run times; rows
without times show nothing.

A GitLab pipeline that ran on a commit the merge request has since moved
past says so. The GitHub rollup read keys its 60s cache by the expected
head, so a push never serves the previous head's checks.

* test(checks): bound the clock work of check time labels

* perf(checks): rebuild check time text only when its data changes

Clock ticks now move only each row's age; exact times, duration and copy
are built once per data or language change. Move the run-time mapping
tests to the mapping layer to keep the client test file within budget.

* fix(checks): annotate the gh pr checks reply instead of casting it

* fix(checks): say when a running check started; key the rollup cache by a query comment

A running row reads 'started 5m ago' so its age isn't read as when it
ran. The expected head now rides in a GraphQL comment, which servers
ignore but gh's cache key includes. Add the new copy to the English
catalog and test behavior rather than formatted copy.

* fix(checks): let check times truncate before names in a narrow sidebar

At the narrowest sidebar the time text never shrank, squeezing check
names to nothing and pushing status labels out of their rows. The time
now sits beside the name, gives up width first, and the name keeps a
minimum width.

* fix(checks): rebuild check time copy when translation catalogs change

* fix(checks): reserve room for a running check's growing start age

* test(checks): keep REST check-run times covered through the schema

* fix(checks): read the checks rollup without gh's disk cache

gh's cache key ignores the PR head, so after a push it answered with the
previous head's checks, and it never evicts expired entries. Orca's own
head-keyed checks cache and read coalescing already bound the requests.

* fix(checks): hide check times in rows too narrow to show them

Below a 17rem row the time text is hidden, so the narrowest sidebar keeps
main's name, status and link layout; wider rows truncate the time before
the name, which keeps a 36px minimum.

* fix(checks): truncate a check's duration before its age

The age answers when a check ran, so in a tight row the duration now
gives up its width first.

* test(checks): type the rollup fixture

* fix(checks): show check times only in rows with room for them

Between a 17rem and 19rem row the times squeezed names to their minimum
and clipped; hold them back until 19rem, which the default 350px sidebar
clears.

* fix(checks): keep a minimum name width only while check times show

Rows too narrow for times, including the narrower folder-workspace parent
PR rows, now lay out exactly as before.

* fix(checks): show a running check's elapsed time instead of its start age

'started 10m ago' reserved so much room that running check names shrank
to a few characters at the default sidebar width; beside a running status
the elapsed time ('10m') says the same thing in a quarter of the space.

* test(checks): use a type import for the mocked module
2026-11-05 23:34:53 -08:00
Brennan BensonandYash Kakade 0ce6c7b61f feat(gitlab): filter Tasks merge requests to ones you wrote or review (#38766)
* feat(gitlab): filter Tasks merge requests to ones you wrote or review

Tasks > GitLab > MRs could only filter by state, so on busy projects you
had to scan every open MR to find yours. A second set of chips (Everyone,
Mine, Needs review) now combines with the state chips, so "my merged MRs"
works too.

The filter runs on the host that owns the repo's GitLab sign-in, looks up
the signed-in user fresh on the project's own host, and filters by
author_id or reviewer_id in one request per project. A failed lookup
shows an error instead of everyone's MRs, and older paired servers that
would drop the filter ask to be updated.

Co-authored-by: Yash Kakade <yashkakade2015@gmail.com>

* fix(gitlab): keep filtered MR lists honest while loading and on old servers

- The reviewer chip reads "Reviewing": GitLab lists MRs you were asked to
  review even after you reviewed them, so "Needs review" overpromised.
- Rows belong to the query that loaded them: after a chip change the list
  shows loading, never the previous filter's rows with Start enabled.
- A refresh checks each paired server's filter support once, not once per
  project; failed checks show the real error instead of "update the server",
  and the web client sends the filtered list to the runtime it checked.
- Signed-out GitLab gets a sign-in message, and an unresolved SSH project
  under a filter shows an error instead of an empty list.

Co-authored-by: Yash Kakade <yashkakade2015@gmail.com>

* fix(gitlab): pin filtered MR lists to the server that confirmed support

A refresh now shares one support check per paired server and pairing, and
the filtered list is sent to the exact runtime and pairing that answered,
so a server replaced mid-refresh can't serve "Mine" unchecked. The web
client scopes its shared check to the current pairing and reuses the
existing result unwrapping. The capability test now counts real status
probes: one per server per refresh for 12 or 120 projects.

Co-authored-by: Yash Kakade <yashkakade2015@gmail.com>

* fix(gitlab): discard filter-support answers from a replaced pairing

A paired server re-paired while Orca was asking whether it supports the
MR filter no longer leaves "update the server" from the old pairing's
answer; the check fails with the usual pairing-changed error instead.
Signed-out detection no longer matches a 401 inside a host or port.

Co-authored-by: Yash Kakade <yashkakade2015@gmail.com>

* test(gitlab): type the filter-change test's props without a cast

---------

Co-authored-by: Yash Kakade <yashkakade2015@gmail.com>
2026-11-05 23:33:39 -08:00
Brennan Benson 4cffcb32ab perf(native-chat): stop rescanning every transcript row on scroll renders (#38887)
The message list re-rendered on every visible-range and scrolling-state change, and each render serialized every slot key and rebuilt the message rail from every slot even when the slots were unchanged. Both now recompute only when the slots change.
2026-11-05 23:31:25 -08:00
OrcaWinandOrcaWin d96aeb9bc7 test: remove 677 low-value mock and projection tests (#38885)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 23:29:26 -08:00
Brennan Bensonandgsxdsm 8c81986932 perf(native-chat): skip unused work when revealing a subagent (#38874)
Read the /clear floor only when a reveal must decide whether to page, and skip the section partition for an open roster whose children have no rows yet.

Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
2026-11-05 23:17:01 -08:00
OrcaWinandOrcaWin 64f6f96ddc test: remove 561 low-value mock and projection tests (#38881)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 23:16:29 -08:00
OrcaWinandOrcaWin 692c81ca17 test: remove 864 low-value chat and mobile test cases (#38871)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 22:57:04 -08:00
Brennan Benson 1f0b3402fc Holding Cmd/Ctrl in a busy chat shows what Cmd/Ctrl+Enter will do (#38749)
* feat(native-chat): holding Cmd/Ctrl turns Send into what Cmd/Ctrl+Enter will do

While the chord's modifier is held alone in a busy chat's composer, Send
becomes a labelled Steer, Queue or Steer queued button, and with an empty
composer the queued card it would steer is highlighted. Releasing the key,
pressing any other key, leaving the composer or the window clears it.

* fix(native-chat): the held-Cmd preview promises only what Cmd/Ctrl+Enter will do

The queue offers its head-card steer only while that card can go (not
being edited, no steer already on its way), Shift or Alt held first and an
open picker show no preview, and a draft Send can't take keeps its button
and reason. The card highlight follows the composer's Steer queued button
in CSS, dropping the state and callback that carried it.

* fix(native-chat): only the composer holding a preview listens for window blur

Every busy chat's composer added window and document listeners; now only
the one showing a held-Cmd preview does, and a hold that outlasts a Cmd+Tab
never shows.

* fix(native-chat): use the Button's own size for the held-Cmd pill, and clear its timer in cleanup

* fix(native-chat): no held-Cmd preview while an IME composes

The IME owns Enter during composition, so the chord shows nothing then. The
listener-count test goes: the work it guarded no longer exists.

* test(native-chat): clean up between held-Cmd preview tests

* fix(native-chat): the held-Cmd delay timer lives in its own effect

The key listener records whether the modifier is down; a separate effect
owns the delay's timer and its cleanup, as React Doctor requires.

* fix(native-chat): a double-click on the held-Cmd button acts once

Sending a draft can turn the button into Steer queued before the second
press, which would steer a queued card the person never chose.

* fix(native-chat): the held-Cmd button is a pill as tall as Send
2026-11-05 22:55:29 -08:00
NeilandBing.Z 8ea884135e fix(runtime): bind serve-host terminals to their workspace at create (#38864)
Terminals a paired desktop or mobile client opened on a headless serve
host got a workspace-less serve-<uuid> session id, so the host inferred
their owner from the cwd. Folder workspaces that share a path then traded
those terminals whenever one was listed, and deleting a workspace stopped
terminals shown in another.

Serve ids now carry the workspace like every other daemon id. Inventory
refresh keeps a persisted layout binding even when a targeted listing does
not include its workspace, so restored serve-<uuid> terminals stay put.
A cwd guess is recorded as such and never drives a worktree-scoped stop.

Fixes #36716

Reported-by: Guy Shechter (@shechter)

Co-authored-by: Bing.Z <zzb@gxsmjx.com>
2026-11-05 22:53:21 -08:00
Neil 4c13ae4632 chore(diagnostics): log the three silent paths that drop terminal tabs (#38873)
A tab that vanished from a desktop left no trace of which path removed it.
Each path now logs one concise line with counts and ids, and changes no
behaviour:
- a host frame dropping any of a worktree's mirrored terminal tabs
  (rate-limited to one line per host and worktree every 30s, counting
  skipped drops);
- a runtime retiring the terminal surfaces of an exited pty;
- the load-time sweep of deregistered repo residue.

Refs STA-12725
2026-11-05 22:38:40 -08:00
Brennan Bensonandagustin2606 e92552a413 Keep Claude plugin UI out of chat and recover blocked replies (#37965)
Keep Claude plugin status and toast frames out of native chat, and give terminal-input requests an actionable failure with safe conversation recovery. Preserve genuine outside replies while hiding internal repair content.

Tracked in STA-13468. Built on #37480 by @agustin2606.

Co-authored-by: agustin2606 <agustinvarela2606@gmail.com>
2026-11-05 22:34:16 -08:00
Neilandggbdpq ce7f68f7c0 fix(workspace): give recipe VM wakes one owner, serialized with suspends (#38842)
* fix(workspace): wake a suspended recipe VM on the host when the workspace activates

Sleeping a recipe VM workspace already runs on the host: the sleep
execution calls the platform's suspendVm, which runs the recipe's
suspend. Waking it did not — the recipe's resume only ran through the
window IPC handler `ephemeralVm:resumeWorkspace`, which the host's own
desktop renderer calls when a sleeping workspace is clicked. So a
slept VM workspace could not be woken from a headless `orca serve`
(no window), a paired client (whose renderer calls its own machine's
IPC), or the CLI; the phone hit the same wall (#34679).

Extract the wake core into `resumeEphemeralVmWorkspace` (mirroring the
host-side `suspendEphemeralVmWorkspace`), keep the window IPC a thin
caller, add the pairing `resumeVm` to WorkspaceSleepPlatform next to
`suspendVm`, and fire a queued VM wake from `activateManagedWorktree`,
the activation path every caller shares. The wake goes through the
workspace lifecycle queue like any wake: a sleep still running sees
the reopen and keeps the VM up, and a wake behind an already-finished
suspend resumes it. The wake is fire-and-forget — a recipe resume can
outlast activation timeouts — so failures land in the log and the
runtime status (resume_failed), and the next activation retries.

Desktop windows keep their IPC path; a host-side wake and a window
resume can overlap only when a phone/web/CLI activation races a window
click, the same double-resume exposure a sidebar double-click already
had.

Refs #36923

Generated-by: GLM-5.3-Flash (ZCode)

* fix(workspace): give recipe VM wakes one owner and serialize them with suspends

The window's resume IPC now goes through the runtime's WorkspaceSleepCoordinator.wakeVm.
A wake already waiting or running is joined, so one reopening runs the recipe's resume
once, and each workspace's VM suspends and resumes one at a time: opening a workspace
while its sleep is still suspending the VM now resumes it afterwards instead of racing
the suspend and leaving it asleep. Lifecycle queueing alone could not give this, since
it is off in shipped builds.

The activation-path wake is dropped: recipe VM workspaces are owned by the VM's own
server, so the host's worktree.activate never resolves them. Waking on remote
activation is a follow-up at the environment-forwarding layer.

Refs #36923

Co-authored-by: ggbdpq <ggbdpq@gmail.com>

* test(workspace): a click after a suspend started wakes the VM again

Refs #36923

* fix(workspace): serialize recipe VM wakes instead of joining them

A click could join a wake that ran before a sleep the user asked for after it,
so the VM ended up suspended though the last action was opening it. Each wake
now queues behind the VM operations before it; the recipe's resume only runs
on a suspended VM, so a wake behind another finds it already up.

Refs #36923

* fix(workspace): wake a recipe VM outside the lifecycle queue; a sleep's reopen check starts at its request

The lifecycle queue waits to observe the workspace's host before running a
command, and a suspended VM is that host, so a queued wake could never resume
it. VM operations are already serialized per workspace, so the wake runs
outside the queue. A sleep now notes reopens from the moment it is requested,
so opening the workspace while that sleep waits its turn keeps the VM up.

Refs #36923

* fix(workspace): check for a reopen when a queued VM suspend actually runs

A suspend waiting behind a resume had already checked for reopens, so opening
a slept pane during that wait still suspended the VM. The sleep now hands its
reopen check to the serialized suspend, which checks it right before it runs.

Refs #36923

---------

Co-authored-by: ggbdpq <ggbdpq@gmail.com>
2026-11-05 22:31:37 -08:00
a1f0670779 fix(remote): drop a restored agent's row when its server closes the tab (#38758)
* fix(remote): drop a restored agent's row when its server closes the tab

A server that restarted publishes the agents it restored without their
worktree. The mirror judges every agent row by worktree and connection,
so such a row counted as another runtime's: when the server closed the
tab, the retraction sweep skipped it and planted no retention marker,
and the desktop kept a greyed "finished" row for a pane that no longer
existed. Republishing never touched it; only restarting the desktop did.

Stamp the frame's worktree on mirrored agent entries that arrive without
one, as the mirror already does for the connection.

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

* fix(remote): name the worktree on every agent a session-tabs frame publishes

A pane restored after a server restart has no live PTY, and its hydrated hook
row may name no worktree, so the host published its agent without one. Paired
clients judge row ownership by worktree, and older ones kept such a row as a
greyed retained agent after the server closed its tab. Every terminal tab in a
session-tabs frame belongs to that frame's worktree, so stamp it there.

Fixes #38434

* test(remote): load the restored-agent test through the moved runtime fixtures

Fixes #38434

* fix(remote): give every reader of a host frame's agent status the frame's worktree

Older hosts omit the worktree on agents restored after a restart. The store
patch filled it in, but completion notifications compared the host entry
without it against the stored one, so they never matched and a paired client
missed those agents' working and done notifications. remapHostAgentStatus now
fills the frame's worktree, so both readers see the same owner.

Fixes #38434

* refactor(remote): build a frame's agent status in its own helper

Keeps the session-tabs projection under the file-length limit.

Fixes #38434

* test(remote): fix the restored-agent test's clock; type the frame status helper without a cast

Fixes #38434

---------

Co-authored-by: registered2nd <39094401+registered2nd@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:29:15 -08:00
Neil 29f1b5d1c0 fix(runtime): keep a restored permission wait blocking prompts while the reattached screen shows its dialog (#38761)
After an orca serve restart, the hook row that recorded a Claude permission
wait is hydrated as restoredUnconfirmed and the explicit-status selector
skipped it, so the send guard saw no evidence and typed into the open menu.

No input reaches a pane while its runtime is down, so a dialog open at
shutdown is still open. A restored permission row (not provider-session-only,
inside the 30-minute window) now stands until a current screen shows no
dialog:

- The current screen is the caught-up headless grid, or, while that grid may
  hold only the bytes since reattach, the provider's current frame. An
  unreadable, blank or outdated screen keeps the wait (fail closed).
- Status reads, prompt writes and the onPermission wait hold read the
  screen first when a restored wait stands, so a dialog answered after the
  restart lifts it without waiting on a hook.
- Once a screen rules the dialog out, the restored wait is spent for that
  PTY; later output, such as the prompt's own echo, cannot revive it.

Restored working/idle rows stay unconfirmed. Host-only change.

Fixes #35962
2026-11-05 22:24:40 -08:00
Neil 9f9dbcf6d3 fix(terminal): adopted daemon terminals take title and output from their stream (#38760)
The daemon adapter's inventory listing reported every surviving session's
title as "shell", though the listing carries no title. That made-up value
became the fallback tab title when adoption ran before the attach seed, and a
terminal.create retry that adopted a survivor after a restart recorded it as
the terminal's observed title and returned it.

That create-retry adoption also never attached the session's stream, so the
adopted terminal's output never reached main: lastOutputAt stayed null and its
real title never landed until some later inventory refresh attached it.

- The daemon listing reports no title. The attach snapshot's lastTitle and the
  stream stay the single source, as for every other terminal.
- A create retry that adopts a survivor attaches it through the same main
  attachment inventory adoption uses.
- A new runtime record treats an empty title as unknown, as an update already
  does, so it never reads as a manual title.

Inventory adoption already attaches survivors (#36137); this closes the create
retry path and drops the made-up title. No wire change: old and new daemons
send the same listing.

Fixes #22809
2026-11-05 22:23:02 -08:00
Brennan Bensonandgsxdsm 5f845b6024 feat(native-chat): open a subagent from the agents strip (#38640)
* feat(native-chat): open a subagent from the agents strip

Clicking an agent in the strip above the composer now opens its
activity in place in the conversation: the transcript expands the
"Kicked off" list and the child's section and lands on its newest
activity. A child with no activity yet lands on its entry; one whose
spawn is older than the loaded window pages history in (abandoned by
a reader scroll, never past the latest /clear). Stop stays its own
button; command and monitor rows are unchanged.

The transcript's navigation (rail picks, diff reveals and now subagent
reveals) moves into one hook sharing one cancellable history loop.

Supersedes #38470.

Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>

* fix(native-chat): find a rowless sibling's entry in the row its roster folds into

Siblings' roster items fold into the first sibling's row, so a later
sibling's own roster item id names a row that is never drawn; a reveal
of that agent before its first activity paged all history and landed
nothing. Resolve the drawn row naming the agent instead.

Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>

* test(native-chat): check where a strip reveal lands

Measure the landing: the end of the child's newest drawn row reaches
the bottom of the view, past an open sibling's rows. Fails if the
reveal does not scroll, aligns the row's start, or picks the child's
first row. The reader-scroll cancel now holds a real page in flight.

Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>

* fix(native-chat): keep an end-aligned reveal pinned while rows above it measure

The virtualizer reconciles a jump only until it holds for a frame, and
size changes are compensated only for rows wholly above the viewport.
An end-aligned reveal lands with the visible rows above its target
still at their estimates; when they measure taller, the target's end
was pushed below the view (seen in QA on a first click into a closed
roster). While an end-aligned jump is held, compensate growth in the
target and every row above it; a reader scroll ends the hold.

Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>

* fix(native-chat): end a reveal's end pin when the reader acts on the content

The held end alignment compensated growth above its row until the next
scroll; Enter on a disclosure above the revealed row then scrolled what
the reader had just opened out of view. A key on the content now hands
the view back, as a press already did. Pin the late-measure test to the
one row it grows.

Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>

* fix(native-chat): hold a reveal's end pin by row identity

The end pin held a slot index; a section closing or a sibling's new row
before it shifted indexes, so growth of an unrelated row could move the
view. Hold the row's key, resolve its index once per measurement pass,
and let go when the row is no longer drawn. Any key on the transcript
now releases the pin, including a cancelled Space.

Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>

* fix(native-chat): find a held end row in one read while it stays put

The key lookup walked the measurements once per resize, so a streaming
revealed row cost work growing with the transcript every frame. Read
the held index first and scan only when rows moved before it, keeping
the found index. Pass the strip its session's tasks as one prop.

Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>

* fix(native-chat): rebase the virtualizer target when the reader takes an end landing

Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>

---------

Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
2026-11-05 22:21:28 -08:00
Neil 3a4fe5ac38 fix(orcad): keep Snap Chromium profiles where the snap can write (#38843)
Strict snaps cannot create dot-directories in $HOME, so a snap-launched
ORCA_BROWSER_EXECUTABLE could never lock its profile under ~/.config/orca.
Detect launchers that dispatch through snapd and place the profile in the
snap's common data directory, and report an unwritable profile directly
instead of the driver's --no-sandbox hint.

Fixes #35999
2026-11-05 22:11:12 -08:00
OrcaWinandOrcaWin f7df773764 test: remove 497 low-value mock and projection tests (#38846)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 22:07:46 -08:00
Neil 99e750805c fix(agent-sleep): a tab a paired client is showing or typing into is in use (#38768)
Agent sleep only treated the host window's own foreground tabs and phone
driver locks as in use. A paired desktop showing a tab, or anyone typing
into it from another client, never reached the planner, so the host slept
the tab the user was looking at.

The runtime now answers "is this PTY in use elsewhere": a remote view
that reports hidden panes (it pauses output while hidden) and is not
paused, a phone driver, and the last user input from any client. The host
window asks its main process over IPC for its own PTYs; a client planning
runtime-hosted tabs gets the same facts from terminal.list
(includeAttention). The planner treats a shown PTY like a phone-driven one
and folds remote input into its input guard.

Clients too old to report hidden panes never count as showing a tab, and
hosts too old to report attention leave sleep as it was, so a missing
signal never turns into never-sleep.

Fixes #36427
2026-11-05 21:55:59 -08:00
Jinwoo Hong a57c19aa91 Resolve workspace lineage parents in one shared module (#38835)
Inert refactor: move the same-store parent rule and merged resolver into src/shared/worktree/workspace-lineage-graph.ts and add one row identity rule. No behaviour change.
2026-11-06 00:49:05 -05:00
Neil ea421720b9 fix(sidebar): quiet the host pill outline on the selected workspace card (#38841)
#37294 outlined the host pill on the selected workspace card at 80% of the
sidebar text color, so in dark mode the pill's border outshone its own label
and the branch name. Tint the sidebar foreground 40% into the sidebar
surface instead, the style guide's recipe for a tint from existing tokens.
The outline stays visible on the selected wash (about 2.5:1 dark, 2.3:1
light by default), stays neutral in accent-colored themes, follows each
theme's sidebar colors, and sits below the label's contrast.

Fixes #38641
2026-11-05 21:48:00 -08:00
1038adb786 fix(browser): larger tunnel windows for high-latency links, bounded by connection credit (#38839)
* fix(browser): negotiate a larger tunnel flow-control window for high-latency links

Embedded-browser tabs routed through a paired runtime (e.g. another Mac over
Tailscale) were capped by a fixed 256 KB per-stream flow-control window. That
window also bounds per-stream throughput to window / RTT: on a ~90 ms link the
ceiling is ~2.9 MB/s per connection regardless of real bandwidth, while on a
sub-millisecond LAN the same window allows ~256 MB/s, so the limit is invisible
locally and dominant remotely.

The window value is validated symmetrically on both ends (each side rejects a
credit grant above its own constant), so simply raising the constant would break
browsing between mismatched Orca versions. Instead negotiate it: each side
advertises its maximum (client in the attach params, host in the ready event)
and both use min(own, peer), falling back to the legacy 256 KB window whenever
either side does not advertise. Advertising peers settle on a 2 MB window (~8x
the high-RTT ceiling). Retained buffers and the outbound memory budget scale to
cover the larger window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(browser): track granted receive credit and scale the session retained limit

Review fixes for the window negotiation: both ends still initialized
receiveCredit to the legacy 256 KB constant while announcing the negotiated
window, so a peer legitimately using the announced 2 MB window tripped credit
validation — the client closed the tunnel and the session treated the traffic
as a protocol violation. Initialize credit to zero and assign it where the
grant is announced, so tracking always matches the announcement; a conformance
test now drives a full negotiated window in both directions unsettled.

Also scale the per-session retained-byte limit with the negotiated window
(16 windows, floored at the previous fixed 8 MB) so concurrent streams on a
slow link keep their stream-count headroom instead of being shed at four.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(browser): scale the pending-chunk cap with the negotiated window

The 256-chunk pending caps paired with the legacy 256 KB window at 1 KiB
frames. Under a negotiated 2 MB window, more than 256 small unsettled frames
fit inside the byte credit, and the receiver treated that permitted traffic
as an overflow — the session closed on the destination write path and the
client fenced the whole tunnel on the receive path. Derive the cap from the
maximum window at the same 1 KiB-per-chunk pairing (2048), keeping the flood
bound for tinier frames. The conformance regression now drives a full
negotiated window of 1 KiB frames in both directions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(browser): size claim capacity to the byte budget at chunk granularity

Each retained chunk consumes one claim, so the fixed 16,384-claim host limit
exhausted at eight stalled 2 MB windows of 1 KiB frames — 16 MiB of a 64 MiB
byte budget — and shed the next stream as a receive-buffer overflow. Derive
the claim caps from the byte budgets at the same 1 KiB-per-chunk pairing the
pending caps use, so claim exhaustion never precedes byte exhaustion for
in-protocol frames. Claims are counters, not stored entries, so the higher
caps retain no extra memory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(browser): bound larger tunnel windows with connection-level credit

Fixes #38053

Rework the window negotiation onto a window.v2 capability: the client
advertises network.browserTunnel.window.v2 and the host echoes it on
ready. v2 tunnels add a connection window per direction (16 MiB to the
client, 4 MiB to the host) acknowledged on receipt over stream 0, so the
queued bytes on either WebSocket stay well below its overflow cap however
many streams grow, and a stalled reader cannot starve other streams.
Older peers keep the legacy 256 KiB / 256-chunk bounds.

Streams start at the legacy 256 KiB window and grow toward 2 MiB by the
bytes their reader consumes, drawing on the connection window. Growth is
charged only while a stream holds unread data: it is released when the
reader drains and recharged when data arrives again, so idle keep-alive
sockets don't hold a fresh download at legacy speed. Connection
acknowledgements are sent after the Data frame is dispatched, so a
synchronous peer cannot reorder its next frame ahead.

Tradeoff: because idle streams are not charged, an idle grown stream that
wakes and then stalls can exceed the growth pool by its own growth. The
per-stream memory budgets still fail that stream rather than the tunnel.

Co-authored-by: jeky22 <38278199+jeky22@users.noreply.github.com>

* fix(browser): keep grown tunnel windows charged until a stream is genuinely idle

Fixes #38053

Growth was released whenever a reader drained, and Chromium drains at
once, so the connection pool barely bounded growth: 16 grown downloads
that stalled together reached the 32 MiB client budget and were reset,
and 8 stalled uploads passed the host's 12 MiB retained limit.

Growth now stays charged while a stream holds it. A stream gives its
charge up only once it is idle (drained, nothing received for 1 s) and
another stream needs the pool, so kept-alive sockets still don't slow a
fresh download. A woken idle stream is charged again; while that
overcommits the pool, consuming streams withhold credit to shrink back
toward the legacy window. Zero-credit updates are not sent.

* fix(browser): charge each tunnel frame a credit floor and complete empty writes

Fixes #38053

The WebSocket queues cap frames as well as bytes, so a connection window
spent on 1 KiB frames could queue 4,096 frames and close a paired
transport capped at 2,048. Each DATA frame now costs at least 8 KiB of
connection credit on both sides, bounding queued data frames to 2,048
toward the client and 512 toward the host.

An empty write found no credit to spend and waited forever, blocking
every later write; it now completes at once.

---------

Co-authored-by: jeky22 <38278199+jeky22@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-11-05 21:47:22 -08:00
Brennan Benson f9011c75c3 Keep long setup commands from hiding workspace setup controls (#38824)
* Keep long setup commands within the workspace dialog

* Cover setup preview containment in the workspace dialog
2026-11-05 21:42:53 -08:00
OrcaWinandOrcaWin a4cad3766b test: remove 1296 low-value mock and projection tests (#38837)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 21:39:35 -08:00