Commit Graph
12022 Commits
Author SHA1 Message Date
NeilandClaude fbd0a29eed fix(ssh): two overlapping bridged-terminal attaches hand back the viewer they replace (#30856)
* fix(ssh): two overlapping bridged attaches hand back the viewer they replace

Co-authored-by: Claude <noreply@anthropic.com>

* fix(ssh): the newest bridged attach keeps its viewer even when an older one answers last

Co-authored-by: Claude <noreply@anthropic.com>

* fix(ssh): an older bridged connect still paints its screen when a newer attach keeps the viewer

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 16:31:28 -07:00
Jinwoo HongandE2E Test 3f9e4ecc1b chore(format): format the files main's new formatting check rejects (#30929)
#30863 made the static job fail on unformatted files; 22 files that landed
around it were never run through oxfmt. Formatter output only, plus one
shared paneActive local in NativeChatStructuredSession.tsx so formatting
does not push that file past max-lines.

Co-authored-by: E2E Test <e2e@test.local>
2026-10-19 19:29:10 -04:00
ad5ef0d944 [mem] fix(memory): release consumed buffers, abandoned waits, and deleted diff views (#30650)
* fix(memory): release consumed stream buffers and abandoned request state

* fix(memory): preserve queued cancellation for shared promise callers

* fix(memory): keep workspace cache cleanup free of browser side effects

* fix(memory): follow renamed workspace owners in cached diff views

* fix(memory): a viewer unmounting after its workspace is removed cannot re-create cached diff entries

Combined-diff view state, scroll offset and scroll anchor now live in one owner-tagged
entry per view key. A mounted viewer claims its key; writes only update claimed entries,
so the late unmount write that follows a workspace removal is a no-op instead of
re-adding orphan scroll entries.

* fix(memory): tighten retention fixes and let evicted diff views heal

- Combined diff: writes go through a per-mount lease. Forgetting a workspace revokes its
  leases, so a late unmount write cannot resurrect entries, while an LRU-evicted entry of
  a still-mounted viewer is re-created on its next write. Drops the duplicated view scrollTop.
- NDJSON framer: copy a retained tail only when it is a strict slice of its chunk, copy an
  unfinished line once, and copy every rejected-line prefix.
- Binary frame reader: single exit path for the end-of-feed leftover copy.
- Relay readiness reuses waitForPromiseWithSignal instead of re-implementing it.
- PTY preconnect input: pass the listener-removing settle into createInput instead of
  mutating the input afterwards.

* docs(memory): state the deferSettled ordering guarantee precisely

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
2026-10-19 16:23:32 -07:00
Jinwoo HongandE2E Test e7d6ee645b chore(lint): bring orcad-managed-tunnel-manager.ts back under max-lines (#30921)
Move the 'recorded tunnel still matches this build' check into
orcad-managed-tunnel-active.ts as activeTunnelMatches(). No behaviour change.

Co-authored-by: E2E Test <e2e@test.local>
2026-10-19 19:17:28 -04:00
Jinjing f14475caa6 fix(browser): label popups from committed document origins (#30540)
* fix(browser): label popup chrome from committed documents

* fix(browser): keep opaque popup documents neutral

* test(browser): prepare real popup identity validation

* test(browser): apply background policy in popup probes

* test(browser): fix popup title and view-lifetime checks

* fix(browser): ignore subframe failures in popup chrome

* rm draft docs

* fix(browser): finish popup probe teardown after signal exit

* test(browser): use typed popup probe window access

* fix(browser): prevent automatic popup title updates

* commit

* fix test
2026-10-19 16:13:54 -07:00
Neil 30d1e8dba5 perf(gitlab): skip unused review details in Checks refreshes (#28643) 2026-10-19 16:12:08 -07:00
Neil c5d03fc475 fix(ssh): a system SSH forward owns its listener even when ssh_config sets a ControlMaster (#30887)
* fix(ssh): system SSH forward processes own their listener even under a user ControlMaster

Forward processes ran through the user's ssh_config ControlMaster, so the master held the
listener (it survived the child's kill) and logged sshd's refusals where the managed tunnel's
fallback check never looked. Every forward process now builds its args through one
unmultiplexed builder (-S none); orphan matching tolerates the older argv.

* docs(ssh): say why a forward never reused a live master's login

* docs(ssh): state the forward login trade-off plainly

* test(ssh): keep the real-OpenSSH test's test-double cast on its SAFETY line
2026-10-19 16:11:05 -07:00
Neil 7e75a86caa refactor(orcad-migration): drop the host-side editor-drafts transfer; keep the durable-commit gate (#30884) 2026-10-19 16:05:34 -07:00
Neil 0a3d203f61 fix(ssh): Disconnect closes the managed server tunnels the SSH session carried (#30891)
* fix(ssh): Disconnect closes the managed server tunnels the SSH session carried

The managed tunnels live in their own manager, so the session teardown closed ordinary
forwards and the connection but left a system-SSH tunnel's ssh -L process authenticated and
listening. The teardown now closes every managed tunnel built or building on the target.

* fix(ssh): a session teardown also closes forwards still starting or awaiting their identity check

* fix(ssh): a connection teardown waits for every forward of it to settle, starts and closes under way included

* fix(ssh): bound a teardown's wait on starts, skip edit rollback after one, and never record a tunnel a teardown overtook

* fix(ssh): an edit a Disconnect cancelled leaves the target's saved forwards alone
2026-10-19 16:00:38 -07:00
Jinwoo HongandE2E Test 099350d85c fix(terminal): Restart on a failed terminal opens a plain shell instead of Codex (#30872)
* fix(terminal): Restart on a failed terminal opens a plain shell instead of Codex

The exit card's Restart passed a null startup, and a Codex account-restart
fallback added in #25485 turned that null into `codex`. Account switches
already name their agent explicitly, so the fallback had no legitimate caller.

* test(terminal): stub the exit-actions controller without Reflect.get

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-19 18:56:26 -04:00
Brennan Benson c1969010cb feat(native-chat): $ opens Codex skills in the composer (#30827)
* feat(native-chat): `$` opens Codex skills in the composer

Typing `$` in a Codex chat now opens a skills-only list, as Codex's own
apps do. `/` still lists commands and skills for every agent, and other
agents keep `$` as plain text.

* fix(native-chat): `$` skips shell variables and parameters, as Codex does

In Codex chats `$HOME`, `$5`, `$(pwd)`, `${X}` and `$?` stay prose instead of opening the skill list, so Enter sends them; one classifier now serves the trigger, the menu and the insert.

* fix(native-chat): `$` matches skill names only, as Codex does

A `$` query no longer matches skill descriptions, so `echo $CODEX_HOME` + Enter sends instead of picking skill-installer; `/` still searches descriptions.
2026-10-19 15:55:32 -07:00
Neil c8a4fc6047 refactor(editor): keep tab buffer state in a per-tab store record mirrored onto OpenFile (#30885) 2026-10-19 15:54:00 -07:00
Neil ff70e9a5e4 perf(file-explorer): reject nonmatching names before path checks (#28572) 2026-10-19 15:53:01 -07:00
Neil 98ee268825 fix(ssh): a managed tunnel follows the host's bound port after another desktop restarts the server (#30888)
The tunnel was rebuilt only when this desktop's own check saw the server start. When another
desktop restarted it onto a new port, the old forward stayed and every check answered
unverifiable. A check that finds the server not answering now reads the port the host's
readiness record names and, when it moved, rebuilds the tunnel there.
2026-10-19 15:39:54 -07:00
Neil daad41f892 perf: preserve matching Terminal settings sections during search (#23164)
* perf: preserve matching Terminal settings sections during search

* fix(settings): keep a retained Terminal sessions section fresh and share the section stack

Move the search-stable section wrapper into SettingsSectionStack so every settings
pane keys sections by their own element key instead of the array index, and subscribe
ManageSessionsSection to the daemon session inventory invalidation signal so a kill
routed through another surface still reaches a section that no longer remounts.

Restates the read-count oracles as node identity plus non-growing read counts so a
future freshness fix does not read as a regression.

* test(settings): mock the Windows administrator section in the Terminal section-lifetime suite

* style: format Terminal section-lifetime reliability gate
2026-10-19 15:39:20 -07:00
Neil 93d89f57c8 perf(automations): release remote exit waits when a run stops being observed (#13641)
A remote-runtime automation whose terminal stays open after completion kept
its 24h terminal.wait and data stream alive, holding one of the server's 16
long-wait slots each. Abort the wait and close the stream on dispose.
2026-10-19 15:37:45 -07:00
Neil 3d880e5182 fix(session-tabs): a held paired-tab close survives an undecided re-pair, and a dropped one shows its tab at once (#30843)
* fix(session-tabs): a held paired-tab close keeps hiding through an undecided re-pair, and its expiry redraws the tab

* fix(session-tabs): dropping a held close also clears its hide under the current pairing

* fix(session-tabs): redraw only the worktree a dropped close un-hid; inventory frames see the redraw mark

* fix(session-tabs): a re-pair proven the same machine asks for the snapshot its moved closes wait on

* fix(session-tabs): a dropped close clears only the current-pairing hide it laid

* fix(session-tabs): every un-hide of a held close lets its worktree's current snapshot redraw
2026-10-19 15:36:46 -07:00
Neil 90797aebca fix(sidebar-inputs): a failed domain read is retried by the next flush or phone read (#30866)
* fix(sidebar-inputs): a failed domain read is retried by the next flush or phone read

A failed producer read used to clear the domain from the dirty set, so it stayed
stale until its own source announced another change. It now stays dirty without
a new mark: the next flush or phone read retries it, and nothing schedules one.

* docs(sidebar-inputs): flushDirty reads what was dirty; a failed read stays unpublished
2026-10-19 15:35:41 -07:00
Neil 209e468a7c fix(ssh): an SSH server update counts terminals again just before it stops the server (#30874)
* fix(ssh): an SSH server update counts terminals again just before it stops the server

The SSH update passed the terminal census taken before the artifact upload and no recount,
so a terminal opened during the upload was missed and, on a host running terminals inside
orcad, ended by the restart. The recount is now a required deploy option, so every runtime
(SSH, WSL, first deploy) supplies one and the stop step always recounts.

* fix(ssh): a recount that throws reads as unverifiable and defers the update
2026-10-19 15:34:16 -07:00
NeilandClaude 0f5c3a4a91 fix(renderer): restoring or re-selecting a workspace keeps the host it was selected on (#30839)
* fix(renderer): re-activating a workspace keeps the host it was selected on

A failed sleep, an automation's focus restore and a rename event re-activated
the workspace by id alone, which cleared activeWorkspaceExecutionHostId. For a
same-id twin (local + SSH) that left the active workspace ambiguous, so owner
resolution refused terminals and writes or guessed this computer. Each site now
passes back the host captured before the change.

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

* test(sidebar): sleep restore assertions name the restored host argument

* test(automations): type the selected host without an assertion

* fix(renderer): same-workspace re-selection keeps the selected host; restores yield to a newer pick

Link clicks and surface reveals inside the active workspace re-selected it by
id alone, clearing the selected twin's host. They now pass it back through one
getActiveWorkspaceExecutionHostId helper (the owner module's private copy moved
there). The failed-sleep and rename restores skip when the user picked another
workspace during their awaits.

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

* refactor(renderer): one re-select helper keeps a single-argument call when no host is selected

* fix(ipc-events): a rename restore also yields to a twin copy picked during the refresh

* refactor(renderer): capture and restore the workspace selection as one id+host value

Every restore site (failed sleep, automation focus restore, rename) now goes
through captureWorkspaceSelection / isWorkspaceSelected / restoreWorkspaceSelection,
so the host can't be dropped or replayed over a newer pick.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 15:16:24 -07:00
Brennan Benson 3d0973d0d4 A chat's visuals and sent images live as long as the chat (#30413)
* fix(native-chat): a chat's visuals folder lives as long as the chat

A chat's scratch folder was deleted once the chat had gone 30 days
unused, so an old chat's inline visuals turned into "Visualization
unavailable" while the chat and its history stayed. The common pattern
keeps a chat's files for as long as the chat and never deletes them on
an "unused for N days" timer.

The sweep now removes only folders no chat record maps to, plus
leftover .expired folders older builds parked. Everything that existed
only to make the age rule safe goes with it: the per-folder save lock
and its expiry candidates, the session-lane idle check, the journal
last-activity query, and the migration's fresh-window timestamp. The
visuals skill no longer tells agents the folder is temporary.

* fix(native-chat): an image sent in a local chat lives as long as the chat

A local chat's pasted images lived in one paste folder that deleted
every file older than 30 days, sent or not, so an old chat's images
turned into label chips. Paired-server chats already keep pastes in the
host's attachment store, where the send that names a file claims it for
its chat and only unclaimed files are ever swept: the common pattern.

A local composer paste now goes into this machine's own attachment
store (starting the local chat host first), so its send claims it the
same way. The old paste folder is no longer written or swept: messages
sent before this change may name its files. Restored drafts still read
it.

An unclaimed upload now waits 30 days instead of one before the sweep
takes it. A draft names its image by this path, so the window is how
long an unsent draft keeps its image; 30 days is what local drafts had,
and it outlasts the host's send-id window so a resent draft still finds
its image.

A chat folder an older build set aside as unused (<hash>.expired) goes
back to its chat instead of being deleted.

* fix(native-chat): a terminal-backed chat's pastes are never swept

A terminal-backed chat has no journal, so no send claims its pastes and
nothing can tell a sent one from an abandoned one. Its pastes therefore
stay in the paste folder, which is no longer swept, rather than in the
attachment store, whose sweep would take them after 30 days unclaimed.
Only a local structured chat's paste (forStructuredChat) goes to the
store, where its send claims it.

* fix(native-chat): keep the attachment owner's literal kind for structured local chats

* test(ipc): expect the local chat store option on the clipboard registrar

* fix(native-chat): preserve attachment ownership and restore held files safely

* fix(native-chat): settle restored image checks independently

* test(native-chat): assert restored image outcomes without batching

* Re-check draft images when the attachment sweep removes their upload

A draft checks its pasted images once, when its composer comes back. Every
chat in an open workspace mounts its composer at launch, so that check ran
before the store sweep (60 s after host start, then every 6 h) deleted an
expired unsent upload, and nothing checked again: the chip lost its picture
with no Not kept feedback and Send stayed enabled.

The sweep now reports each removal attempt; the main process forwards it to
its window, and the draft store starts a fresh finite check for every loaded
local draft image in that folder, mounted or not. A notice is a reason to
check, never proof of absence.

* Let the app effect remove the draft re-check listener it adds

startNativeChatDraftLoad dropped the unsubscribe for the upload-removal
listener, so each recovery through the root error boundary's Retry remounted
App and added another listener. Return it so the effect's cleanup removes it.
2026-10-19 15:11:24 -07:00
Neil 1529b0f73b fix(sidebar): a status or order drag writes the card you dragged, not its twin on another host (#30844)
The drag carried bare worktree ids, and the drop read each id's host from the
id-keyed worktree map, which keeps one row per id. When the same worktree is
published by two hosts (a local and an SSH copy), dragging one card could set
the status or manual order on the other host's copy.

The pointer and native drag sessions now record the dragged cards, and their
visible lineage children, as (id, host) copies; the lineage expansion runs by
host identity so a twin's children are not pulled in. The shared drop-commit
context hands the copies to every status, reorder and board-drop commit.

Every write then resolves through one choke point, dropWriteRows: a dragged id
writes only the dragged copies (read fresh; a copy gone by drop time is
refused), and any other id (a re-ranked sibling, a drop with no drag session)
only when a single host publishes it. The board drop indicator reads the same
rows, so the preview matches the commit.
2026-10-19 14:47:02 -07:00
Neil a201eacd06 fix(projects): link a cloned or imported folder into a GitLab or generic-remote project (#30785)
Setting a project up on another host (a cloned or imported folder)
only knew how to link the folder through a GitHub identity. A project
keyed by its Git remote (GitLab, Bitbucket, a self-hosted host) was
always refused with "Imported folder does not match the selected
project identity". A fresh clone's remote is not probed when it is
registered, so it never projected into that project by itself.

The link now probes an unprobed folder's remote. If the canonical key
of that remote is the project's, the remote is recorded on the repo, so
the folder projects into the project. SSH and HTTPS forms share a key.
A GitHub project is still linked by stamping upstream, and a remote
that names another repository is still refused.
2026-10-19 14:43:33 -07:00
Brennan Benson 4a33cd8e73 fix(native-chat): a usage limit reads as a pause with its reset time and a Continue, not a failure (#30396)
* fix(native-chat): a usage limit reads as a pause with its reset time and a Continue, not a failure

Claude, Codex and Grok chats that hit the account's usage limit now end the
turn on one warning row that names the limit and, in the reader's clock,
when it resets, with Continue to pick the turn back up. Each adapter reads
its protocol's typed limit signal into a `usageLimitReached` failure fact;
older clients keep the host's sentence.

* fix(native-chat): keep the failure words and chat session files within their line budgets

Moves the attachment-problem sentences to their own file and hands the composer's send straight to Continue.

* fix(native-chat): drop a check the shown branch already rules out

* fix(i18n): ship the usage-limit failure words in the runtime catalog

* fix(native-chat): reconcile usage failures by owning turn and typed cause

* fix(codex): keep snapshot import type-only

* fix(claude): open refusal turns without opening empty placeholders

* fix(native-chat): bind usage Continue to the latest idle failed turn

* test(native-chat): isolate stale Continue from ordinary delivery progress

* test(native-chat): use a braced journal assertion

* test(native-chat): build failure rows through their word factory

* fix(claude): retain terminal failure evidence through result reconciliation

* fix(native-chat): complete usage Continue through the composer sender

* test(native-chat): provide the composer options surface in Continue coverage

* test(native-chat): use typed failure words in renderer fixtures

* test(native-chat): match the remote send payload apart from transport options

* fix(native-chat): replace the previous sender notice on Continue

* fix(native-chat): clear superseded Continue notices before rendering

* fix(native-chat): preserve ordinary composer send arguments

* fix(claude): resolve terminal ownership and preserve resultless quota failures

* fix(claude): keep repeated terminal detail unchanged and complete test fixtures

* fix(claude): resolve deferred terminal replays after owning journal writes

* refactor(native-chat): share tombstone append within the sink line budget

* fix(claude): retain both input and echo correlation on the live turn

* fix(claude): preserve unowned result completion after terminal resolution

* fix(claude): allow unassociated successful goal completion

* fix(claude): keep terminal completion on the live turn

* fix(claude): keep typed notices separate from quota failures

* fix(claude): preserve context-limit failures as ordinary errors

* fix(native-chat): keep quota failures visible in collapsed turns

* fix(native-chat): return keyboard Continue focus to the composer

* test(native-chat): keep quota fixture context stable

* test(native-chat): hoist quota context to module scope

* fix(i18n): use one Chinese word for the agent in both Continue tooltips
2026-10-19 14:43:16 -07:00
Neil b03ac56c00 fix(git): forward stdin on buffered Git reads (#30634)
gitExecFileAsyncBuffer never passed stdin to the child, so a buffered read
that needs it (cat-file --batch, hash-object --stdin) waited for EOF until
its timeout. Add the option and forward it, with a real-Git regression
test.
2026-10-19 14:18:03 -07:00
Neil a388005706 fix(i18n): stop extracting <style> bodies as translatable copy (#30765)
The localization audit treated the CSS inside three feature-wall <style>
elements as rendered copy, so the extractor wrapped 7-10k character style
sheets in translate() and every locale carried them. A translation could
then rewrite the CSS, and no pack could translate them under the
per-string cap. The audit now skips <style> and <script> children; the
three sheets are plain strings again, and their keys leave all catalogs
and the runtime-required English catalog.
2026-10-19 14:17:57 -07:00
Jinjing a0d9457922 fix: distinguish history resumes from active-tab switches (#30871) 2026-10-19 14:12:04 -07:00
Neil 1902acd6e0 test(codex): pin the stopped-send receipt rule where #30403 moved it (fix red main) (#30865)
* test(codex): the stopped-send receipt guarantee lives in the journal since #30403

#28046's dispatch-boundary tests pinned that the Codex adapter drops an echo
for a stopped or refused send. #30403 moved that rule to the journal's queued
late-accept write. Pin it there through the host's Stop path, and keep the
adapter tests asserting what still holds at the adapter: no fallback or second
turn/start write, and the earlier send's receipt survives an abandoned wait.

* test: pin that a receipt after Stop adds no dispatch row
2026-10-19 14:00:39 -07:00
Neil 7bff50b457 fix(native-chat): a new chat in a twin folder, or a twin with no copy picked, refuses instead of starting on this computer (#30838) 2026-10-19 13:58:12 -07:00
NeilandClaude bd7633f6eb fix(runtime): a runtime worktree removal prunes the scan snapshots and label routes like the desktop one (#30845)
* fix(runtime): worktree removal from the CLI, a paired client or a headless host prunes the scan snapshots and label routes too

The desktop IPC removal dropped localhost label routes and pruned the persisted
cleanup and space-analysis scan snapshots; the runtime removal did neither, so a
worktree removed through the runtime could reappear in those snapshots. Both
paths now call one shared cleanup.

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

* test: drop casts from the runtime removal cleanup test

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 13:01:59 -07:00
NeilandClaude 2b07f0f3b6 fix(managed-server): snapshot capture and restore run on BusyBox before 1.30 (#30840)
The state mutation wrapper ran `timeout -s KILL 900 sh -c ...`, which old
BusyBox (`timeout [-t SECS] [-s SIG] PROG`) reads as running a program
named 900. The script never ran, the capture answered nothing and parsed
as failed, so every update stopped the server and rolled back. Probe the
syntax as the room check (#30618) already does.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 12:52:55 -07:00
NeilandClaude 9ab194fef6 fix(persistence): an id-only worktree write refuses same-id twins instead of taking the first writer's copy (#30841)
* fix(persistence): an id-only worktree meta write refuses twins instead of taking the first writer's stamp

resolveWorktreeMetaOwner returned the raw-id row's host stamp whenever one
existed. With same-id twins that stamp names whichever twin wrote first, so
first-work renames, id-only updateMeta and sort-order writes for one twin
landed on the other. Twins now refuse; a single-host repo still uses the stamp.

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

* fix(sidebar-inputs): a local pane of a same-id twin stamps the local copy

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 12:52:00 -07:00
NeilandClaude e6658fcadb fix(ssh): a bridged relay terminal's proven exit reaches the runtime from every close route (#30842)
* fix(ssh): a bridged relay terminal's proven exit reaches the runtime from every close route

The legacy relay bridge told only the renderer when the old relay proved a
bridged terminal exited. A tab close through kill or pty:kill, or a shell
exiting on its own, left the runtime's record live (terminal list, wait for
exit, worktree ps). Only stop-and-wait published the exit.

The bridge's exit callback now publishes every exit it proves to the runtime
as host-confirmed. Stop-and-wait learns whether the bridge saw the exit
('reported') or holds no such terminal ('gone') and publishes to the runtime
only in the second case, so the exit is never recorded twice.

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

* fix(ssh): a bridged exit keeps its operator-close cause

Codex review: a proven absence carries no exit code, so pass it as an
unreported cause instead of letting -1 read as an unconfirmed stop; and the
renderer's direct bridge close records the stop request first, as the kill
routes do.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 12:44:40 -07:00
e9fa0cf95c fix: a tab close by the handle a split returned finds the new pane (dark, layout-owner build only) (#30786)
* fix(renderer): list a pane main holds before the window mounts it, so a tab close by a split's handle finds it (dark, layout-owner build only)

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

* test(e2e): the split-close spec polls through the host's "can't answer yet" terminal list

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

* fix(runtime): a pane the window lists before main binds its terminal keeps main's terminal (dark, layout-owner build only)

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

* fix(runtime): a tab close by a main-side split's handle stands on main's own record of the pane (dark, layout-owner build only)

Replaces the window-side listing and the binding fill: the window's list lags main in three
ways (built before main wrote the pane, from it before the binding, or before the window mounts
it), and only main's own layout answers all three.

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

* fix(runtime): main's relisting for a close tells no clients (dark, layout-owner build only)

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

---------

Co-authored-by: E2E Test <e2e@test.local>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 15:41:18 -04:00
NeilandClaude a75748ae32 fix(orcad-migration): refuse before fencing a manifest no catalog request can carry (#30836)
Export accepted manifests up to the 768 KiB validation cap, but every catalog
request (stage, state, abort) refuses params over the host's sealed-frame
budget, about 1.1 KiB lower. A manifest in that window was journaled and
fenced, then every way forward or back refused before sending, leaving the
host fenced. Measure the request budget once, in the export module, and check
it before the fence and before a delta's journal.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 12:32:06 -07:00
Brennan Benson 43abd59f3d fix(native-chat): save a Codex message's receipt from either of Codex's reports of it, not only the first (#30403)
* fix(native-chat): save Codex's own reports of which message it received and which turn took it, with the turn's end

Codex's exact-ID echo of a message and its answer naming the turn that took a message are kept
until the journal holds them, saved at once and nonfatally, and any still unsaved go into the
turn end's transaction and every exit's settlement. Turn ends record who ended them, whether
receipt capture was complete, and a hook reason attributed to the turn.

* fix(native-chat): correct fact row narrowing and preserve sidebar sorting

* test(native-chat): pass the resolved-batch shape and new turn fields in tests

* fix(native-chat): any lost Codex receipt removes the completeness claim for the rest of that child

A per-turn record of lost receipts had to guess which turn a lost receipt belonged to, and an
older Codex can answer a send with one turn while recording it in another. Capture now keeps one
flag per child process: any loss (overflow, a receipt the journal can no longer take, a damaged
frame, a user item with no readable id, a cleared capture) means no later turn end of that child
claims every receipt was saved. Only a turn end that saves the held receipts with itself may
write the claim.

* test(native-chat): expect host provenance on turns the host ended

* fix(native-chat): save the reason when a Codex prompt hook stops a turn, not only when it blocks

* refactor(native-chat): keep only Codex's exact receipts; save each from any echo of it

Drop the completeness certificate, the answered-turn row, end provenance, the saved hook reason
and the retention map with its turn-end retry and exit carry; none has a reader any more.
A Codex echo now saves its send's receipt whenever the journal shows that send handed to this
child and still unsettled, so the second echo retries a failed first save.

* test(native-chat): read the retried Codex receipt from the host's saved record

* refactor(native-chat): decide a late receipt at its turn in the journal queue

Codex now reports either exact live echo of a send, and the journal writes the
acceptance only if the send is still unsettled when the write reaches its turn
in the queue. A second echo therefore retries a receipt whose first save
failed, and repeated receipts for any agent add no row or publication. This
replaces the adapter-side check that read the journal at echo time, with its
fence and handover conditions and the armed-entry fallback.

Adapter tests that pinned "an unarmed echo settles nothing" now leave that to
the journal, which ignores receipts for absent or settled sends.

* test(native-chat): drop the Codex exit test that pinned an unarmed echo settling nothing

An exact echo read after the child's exit is now reported like any other; the
journal decides whether the send still owes a receipt.

* fix(native-chat): type the late settlement's handover by either write it routes to

* style(native-chat): route the late receipt with a ternary
2026-10-19 12:28:40 -07:00
NeilandClaude 2dbcb8c5f2 fix(ssh): an overlapping managed-server update check sees the failure the first one recorded (#30835)
* fix(ssh): an overlapping managed-server update check reads the failure the first one recorded

recordedUpdateFailure read the caller's SshTarget, a copy taken before the
check queued on the host lock, so the in-lock re-read added in #30253 never
saw the other check's failure: both tried the update, and a skipped second
check lost the failure note. Read the stored row instead.

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

* fix(ssh): decide the failure clear on a read after the update, not before the lock

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 12:28:19 -07:00
Neil e04c2f9e62 fix(cli): --environment connects to the server a differently-cased name resolved to (#30834) 2026-10-19 12:18:31 -07:00
Jinwoo HongandE2E Test dd85af1434 fix(automations): headless keeps finished per-run sessions asleep instead of closing them (#24208) (#30639)
* fix(automations): headless keeps finished per-run sessions asleep instead of closing them (#24208)

* fix(automations): a declined sole-terminal sleep never counts as slept

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-19 15:09:20 -04:00
Jinjing feba21d54f fix: use consistent chat and terminal switch labels (#30829) 2026-10-19 12:00:58 -07:00
Brennan Benson 963731227c The workspace you're on disappearing lands you where a sidebar delete does, whatever removed it (#30428)
* The workspace you're on disappearing lands you where a sidebar delete does, whatever removed it

When the active workspace left the list for any reason other than a
successful delete from one of Orca's delete flows (a delete that partly
failed, git worktree remove, the orca CLI, another client, a workspace
opened while its own delete ran), Orca showed the empty "Select a
workspace" screen. The successor was chosen only by per-flow committers
prepared before each delete.

One App-lifetime store subscriber now remembers the last listed active
row and, once the selection is empty and that row has left the list,
focuses the same successor a sidebar delete picks: the most recently
visited other workspace in the project, else its main workspace, on that
row's own host. A selection the user cleared stays cleared; a process
exit's clear is marked automatic. The per-flow committers are removed.

Tracked in STA-8872.

* test: drop type assertions from the removal successor test

* fix: preserve selection intent across workspace removal
2026-10-19 11:56:03 -07:00
Jinwoo HongandE2E Test 40e7bd1db9 A pane main first sees when its terminal starts goes where the window split it (dark, layout-owner build only) (#30772)
* fix(terminal): a pane main first sees at its binding goes where the window split it (dark, layout-owner build only)

* fix(terminal): keep main's divider positions when taking the window's split tree

* test(terminal): main's divider above the window's new split is kept

* ci: the layout-owner switch-on job runs the hidden-terminal load spec that caught the split graft

* test(terminal): type the side-by-side fixture as a split so ratio spreads typecheck

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-19 14:52:07 -04:00
Jinjing e0a89bad8a fix(accounts): copy Devin commands through Orca clipboard bridge (#30826) 2026-10-19 11:42:56 -07:00
Neil eedca5ee1c fix(persistence): worktree meta and lineage writes take the row's host, not the id (R4-2b) (#30811)
* fix(persistence): worktree meta and lineage writes take the row's host, not the id (R4-2b)

* fix(persistence): review fixes for host-targeted worktree writes

* test: SAFETY notes on partial runtime store mocks

* test: drop imports duplicated by the rebase onto #30797
2026-10-19 11:39:54 -07:00
Neil 86ef00ad55 fix(projects): a project move names the row it moves; a paired server's twin by its SSH target (#30794)
* fix(projects): a project move names the row it moves; a paired server's twin by its SSH target

* fix(sidebar): pass the header's row without an assertion; e2e helpers and focus baseline follow the row-shaped move

* fix(sidebar): a header drop re-reads the dragged row and drops nothing if its group changed mid-drag

* fix(sidebar): read live repos through a stable store reader, not a ref written during render
2026-10-19 11:28:29 -07:00
Neil 69157847e3 fix(remote): a removed server's saved tab closes go with it; a same-machine re-pair keeps them (#30803)
* fix(remote): a removed server's saved tab closes go with it; a same-machine re-pair keeps them

* fix(remote): a re-pair move leaves expired closes behind
2026-10-19 11:16:41 -07:00
Brennan Benson 6285797284 fix(session-history): the "Reading N / M files" count reaches its total (#30683)
The scan's first pass announced its whole candidate slice (limit x 2) as files
to read, then stopped once it had enough sessions, and nothing took the unread
files back out of the total. The parse loop now owns its share of the total:
an unfiltered pass announces its list and takes back what it never read; a
filtered walk keeps announcing per selected batch.
2026-10-19 11:14:40 -07:00
Neil b99392e10b fix(orcad-migration): a pane saved in two tabs refuses on this desktop and names the tabs, not a server conflict (#30801)
* fix(orcad-migration): a pane saved in two tabs refuses on this desktop and names the tabs, instead of a server conflict

Fixes #30610

* test+copy: unique pane ids in migration fixtures; refusal names the tabs before the folder
2026-10-19 11:09:42 -07:00
a1ea2e3978 perf(orchestration): stop recompiling a mailbox lookup on every agent title (#30792)
* perf(orchestration): stop recompiling the remote-attachment lookup on every agent title

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

* chore(orchestration): state why the attachment row casts are safe

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

* fix(ci): run the mailbox owner test in the Node runtime project

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

---------

Co-authored-by: E2E Test <e2e@test.local>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 14:05:16 -04:00
zzr 95f544aedf fix(git): derive clone folder from scp-style remote path (#30763) 2026-10-19 10:58:05 -07:00