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>
* fix(cli): use the terminal's worktree when cwd is outside every worktree
Browser, terminal and computer commands run from an unmanaged cwd fell back to the worktree focused in the window. They now use the calling terminal's ORCA_WORKTREE_ID or folder ORCA_WORKSPACE_ID before that default. Fixes#37899.
* fix(cli): resolve the terminal worktree against the catalog
Match ORCA_WORKTREE_ID on the caller's host so ids shared across hosts
go out as identity:, accept folder: ids from either terminal env var,
and keep the runtime default when worktree.list cannot be read.
* fix(cli): refuse a stale terminal worktree instead of using the focused one
When ORCA_WORKTREE_ID names a worktree the catalog no longer lists on the
caller's host, fail with a --worktree hint rather than silently targeting the
workspace focused in Orca. Clear the terminal's workspace env in the test setup
so CLI tests run the same inside an Orca terminal.
* fix(cli): keep the floating terminal on the runtime default
The floating terminal's ORCA_WORKTREE_ID is a sentinel with no catalog row, so it must not hit the stale-workspace error. Word that error as what the CLI can tell: the workspace is not listed, which also covers hidden worktrees.
* fix(cli): ignore terminal workspace ids from another Orca profile
A shell from the installed app that targets a dev build or throwaway serve carries worktree ids that runtime never issued. Reuse the runtime-source fence rule to skip them, so those commands keep the target runtime's default instead of failing.
* test(cli): drop tests that pass without the terminal-workspace fallback
Remove the list-failure and cwd-precedence cases, which pass on main, and the SSH stubs the folder case never reads.
---------
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
* feat(mobile): render GitHub alerts, task boxes, inline tags, images and footnotes
GitHub text on the phone now draws what desktop and github.com do:
> [!NOTE]-style alerts (desktop's marker rule, shared), read-only task
boxes, <sub>/<sup>/<kbd>/<ins> and <b>/<i>/<del>/<code> inline tags, https
<img> and Markdown images at their authored width, raised footnote numbers,
and #123 links in the PR view into the PR's own repository.
* fix(mobile): keep linked badges and commented images, read only pixel widths
An <a href> around inline content becomes a link, so a badge keeps its
target; images inside links, comments, icons by height, and percentage
widths stay as before. Footnote numbers follow the text scale.
* fix(mobile): keep images inside headings and styles in place, read link hrefs as HTML does
Only images that a p, div, picture or center places are cut out of HTML,
in one pass; an image in a heading, link or bold stays in its element.
Inline <a> hrefs use the shared attribute reader (quotes and entities),
<code> keeps only the text of tags inside it, and alert titles follow the
text scale.
* feat(mobile): usage-limit card with the host's automatic continue, Continue and Switch Account
* refactor(mobile): give the notice disclosure its own file to fit the usage-limit status
* fix(mobile): drop the cancel failure once the host shows the cut cancelled
* fix(mobile): keep the usage-limit status up while a later re-ask is pending
A re-ask at a new journal cursor no longer blanks the reset line and pills;
the cut keeps its last answer. Don't continue reports its disabled state.
* perf(runtime): hash granted file downloads once instead of per page
Each 384 KiB page of a digest-pinned granted file re-read and re-hashed the
whole file (~270 MiB of work for 10 MiB) on the host main process. Sequential
pages now feed a running SHA-256 checked against the pinned digest on the EOF
page; the client discards the download on failure. Out-of-order, procfs, and
concurrent pagings keep the whole-file check.
* perf(runtime): verify each granted-file page against per-block digests
Replace the cross-page running digest with 64 KiB block digests taken at grant
time from the same read as the whole-file digest. Each page now reads only the
blocks it touches and is checked on its own, so every page is verified before it
is returned (as before), with no per-grant paging state and no reliance on the
client discarding a download after a failed EOF page.
On pages adding more than 200 nodes per check, the detector walked the whole
document for shadow roots every 500 ms. Whole-document walks now run at most
once per 5 s on their own timer; form-gone checks keep their cadence, and
typing or focus still discovers a field's shadow roots immediately.
Show Codex file-change proposals in approval cards and keep verbose operation details collapsed by default on desktop and phone, with reason, access and answer choices visible.
Preserve approval identity and answer behavior, keyboard focus, and scroll position when details expand or collapse.
`orca terminal read term_abc` failed with a bare "Unknown command" because the
handle landed in the command path. When a term_/structworker_ handle follows a
command that accepts a target --terminal flag, suggest the flag form instead.
Caller-identity --terminal commands are skipped. The terminal-handle prefix
check moves to a dependency-light shared leaf reused by the session-address
codec.
Fixes#38312
* fix(git): refuse unsafe branch switches in an existing workspace
The phone's Switch branch ran `git checkout` with no checks: uncommitted
files moved to the other branch, and a switch could land under a working
agent or mid-rebase. One host check now covers every client: it refuses,
with a plain reason, when files are uncommitted, a merge/rebase/
cherry-pick/revert is in progress, HEAD is detached or unborn, another
git action is running, or an agent in the checkout is working or waiting
for approval. Checks and the checkout run under a per-checkout lock.
After a switch the host clears the old branch's review links and push
target and tells every client, so a phone-only host stays correct, and
records that the user chose the branch so first-work naming never
renames it. The open Commits list follows the new branch.
* fix(git): review round 1 repairs for the guarded branch switch
- check out through the provider so the user's switch keeps the interactive git queue
- a landed switch that Windows left files behind on now tells the user instead of reporting success
- never let a Windows yes/no prompt hold the switch lock
- refuse submodule edits that submodule ignore config hides from status
- record the switch on the target host's metadata row and never fail a landed switch on bookkeeping
- read workspace paths from their ids instead of scanning the catalog on every switch
- admit agent rows like the sidebar roster (listed tab, or a live terminal by pane, tab or handle)
- move the host-side switch hooks into their own module
* test: type the new switch test fakes instead of casting them
* fix(git): second repair round for the guarded branch switch
- report files Windows left behind from git's own unlink warnings, not a status re-read, so
ordinary branch differences and submodule commit changes are never called failures
- the phone refreshes its branch and files after a switch that landed with a warning
- refuse only edits inside a submodule its ignore config hides, never a commit mismatch alone,
reading status without optional locks in the interactive queue
- read the agent process probe when a switch runs, not while the runtime is constructed
- check the agent's checkout path before terminal liveness and build the tab list once per switch
- the runtime wiring test no longer reaches real git
* fix(git): scope the switch's agent check to the checkout and keep landed-switch outcomes honest
- find candidate agents by checkout path first; build terminal liveness once, only from this
checkout's terminals and its workspaces' saved tabs; probe candidates together and re-read
the snapshot once; no work for unrelated workspaces (contract test at N and 10N)
- a checkout that fails after HEAD moved is recorded as landed; files left behind are named only
when git reports it could not replace them, otherwise git's own error is shown
- parse git's unlink warnings for paths with apostrophes
- the phone keeps the refusal or warning visible while it reloads the branch
- cover a submodule that is only at another commit
* fix(git): name git's own error when a switch lands after a failing hook; use the shared unlink parser
* fix(git): keep the switch's agent check flat for unrelated folder workspaces
- read folder workspace directories once per switch instead of a linear search per row and terminal
- re-read each candidate's current status by pane key instead of rebuilding the whole snapshot
- skip the hidden-submodule status pass in repos without submodules
- the cost contract covers folder workspaces, many in-checkout agents and PTY passes
- a real-git test covers a switch that lands before a failing post-checkout hook
* fix(git): never quote git's progress line as a landed switch's error; fixed clock in the cost test
* fix(git): decide the hidden-submodule check from the index, not a visible .gitmodules
* perf(git): read only index modes for the submodule shortcut so large repos stay under the output cap
On a multi-question card only a click on a step tab changed question.
Left and Right now step to the previous or next question while an
option row or a step tab has focus, keep every answer, and stop at the
first and last question. Inside the answer field they still move the
caret.
Co-authored-by: Michael Schaecher <276865230+big-sheq@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
On macOS, privacy protection can stop Orca from copying another browser's
cookie database. The copy fails with EPERM, and Orca told the user to close
the browser, which does not help. Chromium and Firefox imports now show the
Full Disk Access hint that Safari imports already had.
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
* Bound completed ACP ownership by publication and turn lifetime
* Resolve ACP ownership before opening and reuse settlement observations
* Scope ACP resource contract to ownership retirement
* Annotate ACP closed ownership state without an assertion
The GitHub tab only treated gitlab.com remotes as proven GitLab, so every
project on a self-hosted GitLab got a 'No GitHub source detected' row. Use
glab's known hosts plus the hosts a partial auth failure still proved
logged in (glab exits 1 when gitlab.com has no token).
Replace setState-only effects with render-time adjustment (prev-value or
self-limiting guard) so prop changes no longer commit a stale render and
then re-render. Drop the now-obsolete no-derived-state-effect suppression
for use-combined-diff-view-preferences.ts.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
A saved pairing refused by this host's own server (it now publishes a different key, e.g. after
the SSH User changed) now says so, names the exact recovery (Forget the server, then Connect the
host), and stops retrying, instead of blaming another Orca on the port. Servers reached through
an SSH tunnel are their own host kind, so connection failures there no longer suggest Tailscale.
Refs #37766
* Keep dropped-macOS updaters on their last compatible build and explain why
Stamp minimumSystemVersion into every mac update manifest from the built
app's LSMinimumSystemVersion, and refuse to publish an ungated release.
When the updater withholds a newer release for this macOS, show a one-time
notice and say so in Settings and the update card.
* Trim the unsupported-Mac notice copy
A server learns of a desktop that can host its browser pages only after that
desktop opens a browser tab, so open-url failed until one existed. Desktops now
opt in on their client-events subscription. When a server-initiated page needs
a host and no lease exists, the server asks the newest such desktop to start
hosting and places the page on the lease it names. Only an explicit decline
moves on to the next desktop, so a slow desktop can never leave two leases; a
desktop that stays silent is named in the error.
When several desktops already host, the page goes to the one showing this
workspace's pages, then the newest lease, instead of failing as ambiguous, and
no desktop without a lease is asked.
Managed SSH desktops do not opt in, so their hosts keep using the desktop
browser bridge. Leases stay lazy, so an idle desktop holds none and bridge and
tab-query routing are unchanged.
Fixes#35066
Co-authored-by: coolwindX <45729206+coolwindX@users.noreply.github.com>
Plugin new-file items chosen from the floating workspace's + menu were
created under the active main workspace. Route floating targets (an
existing floating tab group, or the empty floating panel) to the
floating-notes directory with the floating workspace identity, matching
the built-in floating Markdown action.
Co-authored-by: Hunter Pittman <22402914+Hunter-Pittman@users.noreply.github.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* fix(runtime-environments): route federation ack and import over shared control
The one-second federation sync pulls over the retained shared-control
connection but sent orchestration.federationAck and
orchestration.federationImport on a fresh one-shot socket, closed on the
main thread after every tick that carries progress. On a Mac whose
network content filter holds each TCP close for about a second, that
close froze keystroke relay for the whole app.
Allowlist the two relay mutations in shouldUseSharedControlEnvelope so
they take the same support-routed path as federationPull, including the
one-shot fallback for hosts without shared control. Both handlers are
synchronous and idempotent by request id and cursor. Long-running
mutations such as orchestration.workerStart stay one-shot, and
ORCHESTRATION_MUTATION_METHODS is unchanged.
* test(runtime-environments): pin federation relay transport to shared control
Cover the transport of one federation sync tick so a change that
reintroduces a per-tick one-shot socket fails a test:
- a pull, ack and import for one dispatch open no one-shot socket beyond
the initial status.get when the host advertises shared control, and
keep all three on one-shot sockets when it does not
- federationAck and federationImport fall back to one-shot with their
envelope intact when shared control is unavailable
- shouldUseSharedControlEnvelope shares control for the relay reads and
the two allowlisted mutations, and keeps orchestration.workerStart and
other mutations on one-shot
* fix(orchestration): prove the worker server contract once per host incarnation
Every federation mutation without caller-proven contract first fetched
status.get to check the orchestration contract capability, then sent the
mutation as a separate call. The contract is now proven once per host
incarnation (runtime id) and each later mutation is pinned to that
incarnation through the existing expectedRuntimeId routing. A restarted host
answers runtime_environment_changed before any effect; the new incarnation is
proved and the mutation resent. A downgraded restart without the contract
receives no mutation, closing the window the old probe left between its check
and the send.
Fixes#37516
* fix(remote-runtime): honour request preparation on shared control
Federation import selects the prefix of its relay batch that fits the
host's inbound request bound through prepareRequest. That preparation was
only applied on one-shot sockets, so once import moved to shared control
its full batch was sent unprepared. Shared-control requests now serialize
through the same preparation step as one-shot requests, and every route an
import can take (shared or one-shot, pinned to a runtime or not) carries it.
The orchestration transport takes prepareRequest and expectedRuntimeId as
one send-options object.
Fixes#37516
---------
Co-authored-by: Blue Thunder Somogyi <bluethunder.somogyi@bastion.com>
The shell picker IPC handlers (pickDirectory, pickAttachment,
pickAttachments, pickImage, pickRepoIconImage, pickAudio) opened
parentless OS dialogs, so on macOS the picker floated as a free window
that could fall behind Orca instead of attaching as a sheet. Pass the
sender's BrowserWindow to showSingleFlightOpenDialog, which already
falls back to a parentless dialog when the window is missing or
destroyed.
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
GitHub returns no field value for the built-in Parent issue field, so
grouping by it put every row in "No Parent issue". Group by
row.content.parentIssue (keyed by URL so multi-repo projects with the
same issue number stay separate) and order groups by issue number.
Fixes#38099
Monaco registers only .cshtml for the Razor language, so Blazor .razor
components opened as plaintext. Add .razor to the Razor registration in
the association generator and regenerate the shared associations JSON.
Fixes#38380
The CLI now measures the serialized runtime request against the shared
1 MiB local transport limit before writing any bytes. Oversized requests
fail with invalid_argument and requestSent=false instead of being cut off
by the server mid-frame, and the client skips mutation recovery for these
unsent requests so it never suggests resending the same payload.
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
The upload chose between local tar's and the remote's errors partly by which
process exit Node observed first, so a missing local directory could surface
as the remote tar's truncated-stream error and be classed as host-answered.
It now waits for both, then blames the remote only when the pipe into ssh
failed, meaning the far end stopped reading before local tar's output ended
(a local tar SIGPIPE or write error always comes with that). Any other local
tar failure caused the remote EOF and is kept.
Fixes STA-13013
The focused-agent selector returned null for any active tab that was not
a terminal, so no agent row highlighted while a chat tab was open. Resolve
the focused group's active chat tab and use its structured session pane
key, the same key chat rows and the status bridge use.
Fixes#38156
Co-authored-by: Neil <neil@stably.ai>
After a resize/reflow, xterm can silently clamp scrollToLine against stale
scrollbar geometry, leaving the viewport short of the saved anchor. Treat a
mismatch between viewportY and the target line as a retry instead of a
successful restore, so the next frame lands on the anchored line.
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
The file-search text-key guard made the global shortcut handler yield
Cmd+R whenever focus was in an editable field, so the default tab.rename
chord did nothing while typing in a native chat composer. Exempt the
default (non-overridden) tab.rename chord when the target sits inside a
native chat root.
Co-authored-by: Michael Schaecher <276865230+big-sheq@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
A custom editor whose document session has `merge` posts `editor.merges`,
so Orca hands it disk writes made under unsaved edits instead of showing
the changed-on-disk banner. The frame host forwards only a guest's
`editor.subscribed` and trusted chords to the editor, so in isolated
frames (the desktop default) the announcement was dropped: the merge
never ran and the banner always showed.
`editor.merges` is not input. Page script sends it through
`orca.document.session()` just as it sends `editor.subscribed`, so it
passes the guest filter the same way.
Fixes#38535
A UA-only Emulation.setUserAgentOverride blanks every client hint, so
desktop tabs on the temporary override (e.g. Google sign-in) sent empty
Sec-CH-UA-Platform and friends. Restate Chromium's own desktop platform,
platform version, and architecture with the override so hints match the
non-overridden browser on macOS, Windows, and Linux.
Co-authored-by: Neil <neil@stably.ai>
The equalize-pane-sizes keybinding did nothing while focus was inside a
Chat UI pane because the native chat key handler only matched the split
shortcuts. Match terminal.equalizePaneSizes alongside split right/down and
run it through the terminal pane actions. When the pane cannot equalize
(lone or expanded pane) or lives in a tab group, leave the chord
unconsumed so the composer still receives it.
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Workspace HTML previews are served from a grant-bearing orca-doc-preview
URL that the grab payload sanitizer blanks, so annotations sent to an
agent arrived as "Design Feedback: current page" with an empty URL.
Main now resolves the guest's committed document URL (tracked from
did-navigate, so pushState cannot rewrite it) through the live preview
grant to the host file path, and uses it as the payload page URL when it
is display-safe. The annotation heading keeps Windows paths intact
instead of parsing `C:\` as a URL scheme.
Fixes#38564
Co-authored-by: Claude <noreply@anthropic.com>