* 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>
* 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
* 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
* 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>
* 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.
* 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
* 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.
* 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.
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)
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)
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)
* 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
* 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
`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>
* 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
* 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.
* 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.
* 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
* 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>
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.
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>
* 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
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>
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
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>
* 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>
* 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>
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
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
* 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>
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
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
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.
#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
* 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>