A managed SSH or paired-server workspace has no SSH connection id, so the
notebook read it as local and ran its cells in the desktop's own Python
against a same-named local file. Derive the execution host from resolveOwner;
refuse remote, ambiguous and unloaded owners.
* feat(layout): main drops the layout of workspaces the catalog no longer validates (dark, layout-owner build only)
* rename the paired-host check so the switch-off bundle keeps its names
* a repo id two hosts register skips the listing purge
The chat, simulator, and browser tab recorders each computed where the window
drew a just-opened tab with the same lines. They now share drawnTabPlacement.
* feat(sidebar): main produces the local workspace rows, checked against the window (dark)
* fix(sidebar): workspace rows invalidate the listing only on metadata saves; prove it on the real cache
* fix(sidebar): workspace rows merge the listing fresh instead of invalidating the shared cache
* chore(sidebar): workspace rows comments say what the fresh merge reads
Sessions saved before #29930 can hold a moved editor tab whose
worktree id still names the workspace it came from. Pin that the
saver writes the workspace's own id and that the window's tab carries
it after main's first snapshot. Tests only.
* fix(tasks): bound GitLab all-projects fan-out to avoid runtime queue overflow
The GitLab Tasks list started one listIssues/listMRs runtime RPC per selected repo at once. With 150+ projects this, combined with other callers on the same selector, overflowed the runtime RPC call queue and surfaced "Remote runtime call queue is full".
Fetch per-repo lists through mapSettledWithConcurrency with a limit of 6 (below the queue's foreground concurrency of 8), and stop starting new calls once the selection goes stale.
Fixes#30133
* test(tasks): assert merged GitLab rows without a type assertion
---------
Co-authored-by: Neil <neil@stably.ai>
* fix(sidebar): main takes its row baseline from the session's catalog and skips unchanged listings
* fix(sidebar): read catalog listings the way the window's merge does
* fix(sidebar): a seeding listing re-samples, and listings compare with the window's equality
* refactor(shared): move the pane title sanitizer to shared, unchanged
So main's layout Loader can apply the same rule the window applies on load.
* fix(layout): pane titles stay layout in a layout-owner build (dark)
The window stops writing a revealed terminal's tab name as its pane title,
and main's Loader drops a lone pane title that only repeats the tab's name,
as the window's load does. Both behind ORCA_LAYOUT_OWNER_IS_MAIN.
* fix(layout): the Loader compares a lone pane title with the tab bar's quick command label too (dark)
As the window's row hydration does when the row has no label.
* fix(layout): the Loader compares a lone pane title with the tab bar label, the live title (dark)
A bare exit returns the shell's last status. Under the e2e profile on macOS,
/etc/zshrc leaves $? at 1, so the pane is kept behind the exited card on
purpose and the layout check never settles.
* feat(sidebar): dark phone feed of the sidebar inputs (sidebarInputs.read)
* fix(sidebar): feed review: fresh read on ready, notices kept per revision, one flush owner, closed connections never subscribe
* fix(sidebar): feed skips values JSON cannot hold; a failed read forgets its notice
* fix(sidebar): name the feed batch key by where the batch starts from
* fix(ssh): a tab closed while a managed host is disconnected stays closed after Connect
#30130 made a user's Disconnect mark the host's managed server manually
disconnected, so the desktop now answers its calls itself with
runtime_manually_disconnected. The tab close path read that as the host's
answer and dropped the close instead of holding it for replay, so the tab
came back on reconnect. Treat it as an undelivered close, and route the
Disconnect sources to the managed-orcad Docker lane.
* test(e2e): close the disconnected host's tab the way a user does
The journey called the store's bare closeTab, which sends only terminal.close
and holds nothing; it passed only because the disconnected host used to be
redialed in the background, which #30130 stopped. Click the tab's X instead,
the path that holds and replays an undelivered close.
* test(e2e): the shared-home spec tells a host's own server from a PID that only collides
Each Docker host has its own PID namespace, so the PID host A records in the
login-home slot can belong to an unrelated live process on host B; kill -0
alone then read it as B running from A's slot. Count a recorded PID only when
its process runs from that slot.
* fix(serve): orca serve on orcad serves the browser client and answers plain HTTP
orcad, the default local orca serve host since the compatibility launcher,
was never handed the app's web client directory, so its HTTP server had no
request listener: every GET hung and no Web client URL was printed (#30100).
The CLI now passes the app's unpacked out/web to orcad via --web-client-root,
a server without a web client answers plain HTTP with 404 instead of hanging,
and packaging fails when the unpacked web client or a file it loads is missing.
* test(serve): pin orca serve handing orcad the app's web client root
* fix(layout-owner): a frame rebuild keeps a background terminal's live agent title (dark, layout-owner build only)
The frame slice builder reuses restore's row hydration, which resets any
agent-activity title because a prior session's agents have exited. A
title main reports live for a running pane is not stale, so rows keep it.
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(layout-owner): restore's row hydration skips the agent-title reset for live titles
Moves the live-title keep into the row hydration so the frame builder
adds no layout write of its own; restore passes nothing and is unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Save-divergence detector: skip empty workspaces, see dropped tab-bar entries and kept pane trees (dark, layout-owner build only)
- A workspace key that names nothing (or only places a stored closed-tab record) is no longer
reported as "workspace added/removed" or "terminal row owner changed".
- A save that drops a terminal tab's tab-bar entry while keeping its row is reported, read from
the stored session before the Loader rebuilds the entry.
- A pane tree main keeps over the window's (windowSaveForMain) is reported, so a window-side
pane repair shows up.
* Detector: keep reporting empty-row flips, cover every overridden pane tree and one-sided closed tabs (dark, layout-owner build only)
* Detector: a save with no pane tree for a tab main keeps one for is reported too (dark, layout-owner build only)
* Detector: a closed tab record moved to another worktree in one save is reported (dark, layout-owner build only)
A user's Disconnect of an SSH host is now recorded for the session, and background
dials (a managed server's tunnel and status checks, census, host-resume recovery,
pane and automation restores) refuse it until an explicit Connect. Disconnecting a
managed host from Settings also stops its server's status checks, and the status
bar's Disconnect of a managed server also disconnects the SSH host it runs on, so
both surfaces read the same verdict.
Fixes#30086Fixes#30085
* fix(sidebar): draw a project group once when two hosts publish it under one id
Moving a project to a managed server copies its project group there under the same id while the desktop keeps its copy. The row builder emitted a header per copy, so the second sat empty and both shared one virtual-row key.
Fixes#30045
* fix(cmd-j): search a same-id project group by the copy the sidebar shows
* fix(sidebar): reveal a same-id project group through the copy the sidebar draws
* fix(sidebar): reveal a folder workspace through the group parents the sidebar draws
* fix(sidebar): reveal from the host-filtered groups the rows were built from
A project's group membership is stored on the project's own host, and that host stores a group id it does not hold as 'no group' while still returning the row. Moving a remote-only project into a local group (menu or drag) therefore looked successful, and could even take it out of its current group.
The store now refuses a move into a group that only exists in another host's catalog, and reports failure when the host answers with the group cleared.
Refs #13699
* fix(layout): main's Loader repairs a terminal bound in two panes of one tab (dark, layout-owner build only)
* fix(layout): a window save stores main's repaired pane tree, so the duplicate-terminal repair survives the save (dark)
* test(layout): the SSH duplicate-terminal case builds an SSH-hosted tab
* refactor(sidebar): move the Smart comparator and attention scoring to src/shared/sidebar-list
Main needs them to own the frozen order. Renderer helpers they depend on move with
them; the window imports the shared copies. No behaviour change.
* refactor(sidebar): one shared ranking for the frozen sort order
The window's sort memo and the palette's Smart sort now call rankWorktreesForSort,
so main can produce the same order from its own records. The pure state-history
step moves to shared for the same reason. No behaviour change.
* feat(sidebar): main keeps the list's time rules on one clock, checked against the window in dev and test
* test(sidebar): fake-clock parity of main's temporal owner against the window
* fix(sidebar): main restarts the settle window only where the window's sortEpoch bumps
* test(sidebar): type the owner harness without an assertion
* fix(sidebar): main's temporal owner stays idle until a dev or test window begins a session
* fix(sidebar): main reads Hide sleeping from the field the window persists
* refactor(sidebar): keep the begin params in their own contract module so the snapshot schema compiles out
* chore: restore files the merge's formatter rewrote to their origin/main content
On a headless orcad with no browser provider, every browser RPC went to a
rejecting proxy, including browser.tabClose. Client-hosted page records and
session rows are runtime records, so closing them never needed the engine;
the proxy now retires them, and orcad's provider proxy checks its own page
registry before asking a provider that never held the page.
Orphan recovery republished a pending host row as ready on the strength of the
client's retained wake binding alone, even when the host's terminal inventory
omitted the handle. The mirror then counted the dead handle as live, so a slept
workspace came back awake in the sidebar. A pending row now stays pending unless
the host's inventory lists its handle; the wake binding is still kept.
Fixes#30108
* feat(sidebar): main serves workspace icons as small data URLs for the phone, unused until the phone switch
* fix(sidebar): settle review findings in the icons RPC and the manual-redirect port fix
* docs(sidebar): scope the icon fetch-slot bound to a lone read
* fix(sidebar): gate the icons RPC behind the sidebar-inputs switch; prove re-encoding end to end
* fix(sidebar): restore two files the merge reformatted; one pure annotation for the icons params
* fix(sidebar): restore two files the pre-commit formatter rewrote during the merge
* fix(sidebar): icons fetch through their own manual-redirect request; shared desktop HTTP client back to main
* fix(sidebar): settle review findings in the icons manual-redirect request
* fix(sidebar): end a bodyless icon response at once; clearer fetchRepoIcon doc
* fix(native-chat): the queue button you pressed shows a working spinner while its action is on its way
* test: keep the pending Resume test's action typed
* fix: the shared spinner shows its still hourglass under reduced motion instead of nothing
* fix(mobile): a compact queue: shorter header, a capped scrolling list, and an editor that fits the card
* fix(mobile): keep queue actions visible and reveal new cards
* test(mobile): allow shared SVG host fixture
* fix(mobile): queue header and editor controls grow with large system text instead of clipping
* fix(mobile): at large text sizes the queue header's buttons wrap below its label instead of squeezing it out
* fix(mobile): at large text sizes the queued editor's Cancel and Save wrap below its heading instead of truncating it
* fix(mobile): at large text sizes the queue header's and editor's buttons take a line of their own
* fix(mobile): the large-text button lines wrap when one button is all a small phone's line holds
* test(mobile): the composer tray test's react-native stub reports the text scale
* fix(mobile): while a card is edited at large text, the paused header's button line gives its height to the editor
* refactor(pty): one spawn step stamps a terminal's pane identity for both spawn lanes
The window spawn controller and the host runtime's caller each wrote
ORCA_PANE_KEY/ORCA_TAB_ID/ORCA_WORKTREE_ID and jcode's runtime dir, in
different layers, so per-pane env behavior drifted (the jcode daemon
pre-warm exists only on the window lane). stampPtySpawnPaneEnv now writes
them in both controllers from the spawn's own tab/leaf/worktree ids;
buildTerminalWorkspaceEnv no longer stamps them. No behavior change: a
TEMPORARY prewarmsJcodeOnDaemonHost input keeps host daemon spawns from
pre-warming until its own PR.
* test(runtime): read a fake spawn's pane env without type assertions
* test(pty): host serializer-settlement spawns name their pane by tab and leaf, as the runtime does
* fix(pty): preserve caller runtime directory after managed preparation fails
* feat(mobile): name the desktop under the Tasks title
Tasks can open on any connected desktop, so the header now shows which
one, in the same subtitle line the host screen uses under its title.
* fix(mobile): reserve the Tasks desktop line so the title does not jump
The subtitle line now holds its height before the host profile is read,
a failed read leaves it blank, and the tests cover a desktop switch
while a read is in flight.
* test(mobile): re-measure the page script sweep after the Tasks subtitle
The Tasks route now shares the host screen's styles, so it emits one
script fewer (77 to 76 at its prefix, 80 to 79 for the tree). Re-swept
every route prefix; the worst route step is now 11.
* feat(acp): an agent's chat starts wait out a change to its shared sign-in
A launch spec can carry a gate that refuses new chat starts while an operation
replaces or removes the sign-in all of that agent's chats share on this host,
and aborts starts already under way when one begins. Antigravity's row sets it.
The refusal reuses the accountSwitchInProgress reason, now worded for any agent.
* Drain ACP starts before stopping chats for account changes
* Forward gated aborts into ACP launch preparation
* fix(acp): the launch gate holds every chat child it admits until it exits, so an operation can't run beside a chat still starting
A start leaves the gate when its child is up, but the host only lists the chat as loaded once its
attach finishes. A sign-in switch (and later Remove) in that window found no start to abort and no
loaded chat to stop. The gate now keeps each admitted child until it exits, and an operation closes
and proves gone any it still holds after the loaded chats stopped; otherwise it fails without
running.
* refactor(native-chat): one helper for failure sentences that only name the agent
Keeps agent-session-failure-words.ts under its line limit now that main grew it; the five sentences
read the same.
* fix(acp): a conversation copy runs the agent too, so it waits out a sign-in change like a chat start
* feat(linear): edit and delete Linear comments from the CLI
Adds orca linear comment edit <commentId> and orca linear comment delete <commentId>
(alias rm). The comment id alone names the comment; Orca finds the connected workspace
that owns it unless --workspace is given. Both refresh a linked worktree's issue and
report unconfirmed writes with a read-back step. The orca-linear guide documents them.
* test(linear): use a checked linked worktree fixture
* fix(linear): bound and cancel comment actions
* fix(native-chat): a Codex chat is Working only while Codex holds the send
Working read 'a send still pending delivery', so any echo or idle signal
that never arrived (a hook-blocked prompt, an answer naming no turn, idle
before a timed-out send was written) kept the chat Working, Stop up, the
queue frozen and /compact refused until the agent exited.
Codex now reports which sends it still holds: unanswered, answered into a
turn that has not ended, or answered into none while a status read after
that answer says the thread runs. Replies and statuses are ordered as they
are read, so a stale status cannot release a newer send. The host reads
that set (plus the short window while it hands a send over) for Working,
publishes a send the child stopped holding as in doubt on every page and
frame so older clients agree, and republishes when the set changes with no
row. The Codex idle latch and its release are removed.
* fix(native-chat): name the handover journal type; reverse without mutating
* test(native-chat): a lost Codex answer while idle is no longer the chat's work; run the held-sends test with the database runtime
* fix(native-chat): bound work updates and own command handovers
* refactor(native-chat): reuse session delivery plumbing
* test(native-chat): allow separate ownership restatements
* test(wire): resolve the release tab predicate after relocation
* test(native-chat): reuse existing promise and empty-batch primitives
A steer Codex answered into a turn that then ended without taking it is
rejected and moved to the thread, so the sidebar verdict and the
completion feed treated it as a refused request of its own and reported
it in place of the turn it joined.
* fix(mobile): ask which desktop to open Tasks on when several are connected
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(mobile): offer only desktops that have the tapped task provider
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>