* fix(terminal): report focus-out to ?1004 programs when their last viewer leaves
A viewer that vanishes (laptop sleep, dropped link, app quit) never sends the
ESC [ O its xterm would send on blur, so programs that armed focus reporting
kept believing they were focused. The daemon now remembers the last focus
report each PTY heard and sends one ESC [ O when its last client detaches.
orcad asks it to when the last device that viewed a PTY disconnects; a
resubscribe from a still-connected device is not a departure. A daemon that
predates the request rejects it, and the adapter stops asking that client.
Fixes#28350
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(terminal): keep the daemon router under its line cap and justify focus-loss test casts
Refs #28350
The subscribe-buffer test now expects the viewer device argument.
* fix(terminal): let a hidden remaining pane yield focus-out on device departure
Refs #28350
Build on main's pane-visibility signal: a remaining remote view whose
pane is hidden already blurred, so it no longer holds back the focus-out
for a departed device. Views that cannot report a hidden pane still do.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(remote): let Remove drop a managed server saved under two SSH aliases
When one managed SSH server was saved twice, Settings said to remove one
entry, but Remove refused any pairing that has a managed deployment. When
another saved entry still manages the same server (same host key), Remove now
unlinks this entry through the managed forget path: it closes this entry's
tunnel and releases its SSH claims without contacting the host, so the server,
its projects and live terminals stay with the other entry. A managed server
with no other managing entry keeps the guard.
Fixes#38604
* fix(remote): recheck the managed twin inside the lifecycle queue
Codex review: a Remove queued behind another lifecycle action could forget
the last managed entry if the twin was removed meanwhile, and the twin match
compared raw Base64 text. Removal now goes through a forgetRedundant action
that re-finds the twin inside the target's lifecycle queue and unlinks in the
same tick, and matches host keys with the canonical matcher.
Refs #38604
* test: cover redundant managed removal after the dispatch suite was removed
Refs #38604
A recipe VM workspace runs on its VM's own Orca server, so a phone, the CLI or
the desktop reaching it while the VM slept dialed a server that was not there
and failed. Only the host window's click ran the recipe's resume.
- The VM wake owner (WorkspaceSleepCoordinator) tracks the VM operations it
was asked for and answers where the VM stands: a pending wake or a running
suspend outranks the stored record.
- The host holds a request that opens the workspace (worktree.activate,
defined once in shared code) until the VM is up: three minutes for the
desktop and the CLI, and for a phone only as long as still lets the relay
connect inside the phone's 30-second deadline. A VM still up keeps a sleep
on its way from suspending it; a VM asleep or suspending wakes after what is
running, joining a wake already pending. The open answers waking only when
the hold runs out, wake_failed when the wake fails, and asleep when a Sleep
asked for while it was held won; that open then wakes nothing more. Other
requests answer waking or asleep instead of dialing nothing. A Sleep asked
for during a wake keeps its place in the lifecycle queue and starts once the
wake is done. A caller that gives up stops waiting and frees its queue
place; the wake goes on.
- An open of a server the user disconnected is refused before anything wakes
its VM, and cancelled if they disconnect while it waits. A wake carries the
SSH host's user hold from when it was accepted and every Disconnect since,
so its connect never lifts a hold; the VM still resumes and its new SSH
address is saved for the user's own Connect.
- A wake re-pairs a paired VM's server. The request that waited for it is
held to its caller's pairing as of before the wake (a stale caller is
refused before it can wake anything), then to exactly the pairing revision
the resume produced (including a re-pair its SSH reconnect made), whatever
process now serves it; any other change still
refuses it. A phone link still preparing for an open survives only the
re-pair its own wake made; any other retirement, or a user Disconnect, ends
it, and a link preparing for anything else is retired at once.
- The hold lives in the desktop's environment preparation, which its own calls
and subscriptions, the phone relay and the CLI all go through. A phone open
on a link to a server whose VM is not up gets a fresh link; the old link's
requests are answered waking or asleep. Further opens join a link already
waiting on the wake. A phone link's whole open (server preparation with any
hold, grant sync and the socket) shares one deadline inside the phone's
own, for every server.
- The CLI dials a VM directly, so `orca worktree activate` asks the desktop
(runtimeEnvironment.wakeRecipeVm) to open the VM and reads the server's
pairing again; the refusals (including runtime_manually_disconnected) keep
their codes over RPC, and a host without recipe VMs answers that one RPC
with method_not_found. Hosts that do this advertise
host.recipe-vm-wake.v1.
- A workspace this host runs on a recipe VM over its SSH target wakes on this
host's own activation, through the resume's explicit connect
(wakeWorkspaceVm -> resumeVm -> connectRuntimeOwnedSshTarget). That connect
runs whenever the VM is in SSH mode, even when its recipe had no resume step,
since suspend tore down the transport.
Fixes#36923
Co-authored-by: ggbdpq <ggbdpq@gmail.com>
* fix(terminal): end tail lines at CR CR LF instead of reading them as empty
Raw-mode programs end lines with CR CR LF. The output normalizer folded only
the CR directly before LF, so the leftover CR made each completed line read
as the empty text after it, and terminal.read returned blank lines. A run of
CRs lands on the same column as one CR, so treat the whole run before LF as
part of the line ending.
Fixes#38667
* chore: shorten the CR run comment
Refs #38667
* fix(terminal): defer CR until kept output decides it
A CR followed only by discarded escapes and then LF (text CR SGR CR LF)
still read as an empty line, and a restore cut landing just before CR CR LF
seeded a blank first row. The normalizer now holds each CR until the next
kept output: before LF it ends the line, and runs or escape-split CRs act as
one. The restore anchor skips a whole CR-run line ending.
Refs #38667
* test: size the restore-cut case from the exported seed limit
Refs #38667
* fix(files): open a granted host file the way a workspace file opens
A plain click on a host file outside every workspace opened it in the
editor through the grant's whole-file text read, so a video, a large
image or any text past 512 KiB failed with "too large to open from its
host". Granted files now go through the same reads as workspace files:
media streams to its viewer by range, images and PDFs page past one
reply, and large text pages into the editor. A host file is addressed
either by workspace path or by its grant, and the host gains
files.statTerminalArtifact to pair with files.readTerminalArtifactChunk.
The host names a large binary as binary before refusing its size, as
files.read does, and a streaming player renews a lapsed grant the way
the editor's reads do. Hosts without these methods answer
method_not_found, which the paged reads already turn into an
update-the-host message.
Refs #35968
# Conflicts:
# src/main/media/media-preview-protocol.test.ts
* fix(files): page a granted binary with the grant that served it
A content read renews a lapsed grant without telling the tab, so a bytes
editor paged the file with the dead grant and failed on every reload. The
read now reports the grant that served it; the bytes editor uses that grant
and the tab keeps it, as a save that renews one already does.
Refs #35968
* fix(files): renew a granted preview on every host call and keep unsavable grants read-only
Review: renewal ran only before a media response returned, so a grant that
lapsed under a streamed chunk or a pdf.js range read over IPC failed instead.
Every host call for a remote preview now goes through one helper that renews a
lapsed grant once. A grant saves only text within one write, so a granted
binary or text past 512 KB now opens read-only instead of failing to save.
Refs #35968
* fix(editor): show an unsavable granted copy read-only in every view
Review: read-only reached only the edit surface, so a large granted notebook
or markdown file still opened its editable rich view. The editor panel now
treats the copy as a read-only tab, so rich views fall back to the raw source
view as they do for other read-only tabs. A renewed read-only grant also reads
as read-only and stays off the tab.
Refs #35968
* fix(files): renew a granted bytes read whole and never splice versions mid-stream
Review: a bytes editor opened after its grant expired kept failing, since its
paged read never renewed. It now renews and restarts the whole read. A stream
or PDF range that renews mid-read now continues only if the renewed grant sees
the same file version, so a rewritten file can't splice into one response.
Refs #35968
# Conflicts:
# src/main/media/media-preview-protocol.test.ts
* fix(files): keep each preview response on its own grant and pin a bytes read's pairing
Review: a stream crossed to a grant another request had renewed without
checking the file version, and a bytes read that renewed during a re-pair
could read from the new host. Each response now keeps the grant it started
on and moves to a renewed one only after checking the version it serves, and
a bytes read pins its pairing before the first call.
Refs #35968
* fix(files): bind a preview's stat to the grant that saw it
Review: a stat cached from the old grant could start a response on a grant
another request had just renewed onto a rewritten file. The cache now keeps
each stat with the address that produced it, a response starts on that
address, and a renewal checks the version by stating the renewed grant
itself.
Refs #35968
* fix(files): keep a pinned read's pairing on its paged address
Review: the paged-read address dropped a caller's pinned pairing, so a re-pair
between a grant renewal and its retry could send the retry to the new host.
The address now carries the pinned revision.
Refs #35968
* test(e2e): give the granted video spec's timeout its reason
Refs #35968
* test(media): keep the granted preview renewal tests in their own file
Main trimmed media-preview-protocol.test.ts to local previews, so the tests
for renewing a granted preview's lapsed grant (burst, streamed chunk, rewritten
file, PDF range over IPC) now live in media-preview-protocol-granted.test.ts.
Refs #35968
* feat(cli): add orca host rm for saved SSH targets
orca host rm --host ssh:<target-id> calls the runtime RPC ssh.removeTarget,
the same removeSshTarget the Settings UI reaches, so the tombstone keeps the
passive ~/.ssh/config import from adding the alias back.
Also document how the import treats Match blocks and Include files.
* fix(ssh): clear a removed target's live state and rename in main for every removal path
Removing an SSH target through ssh.removeTarget (orca host rm, paired clients)
left open tabs with stale reconnect state and kept the host's rename, because
only the Settings dialogs cleaned up after their own IPC call. The shared
removal mutation now drops the display-label override and tells the renderer,
which clears the target's state once for all callers.
Fixes#37949
* fix(ssh): clean up after a removed target in the teardown every removal ends in
A recipe VM's cleanup removes its SSH target through removeRegisteredSshTarget
directly, so the window kept that target's live state. The broadcast and the
rename drop now run there, after the target is gone.
Fixes#37949
* test(cli): keep host rm's retarget-flag refusal next to its other cases
Its old file was removed on main.
Fixes#37949
* refactor(cli): give the host, managed-server and VM handler groups their own module
Keeps the handler manifest under the file-length limit.
Fixes#37949
---------
Co-authored-by: Florin Timbuc <florin@sowild.design>
* fix(sidebar): keep the selected card's host pill outline visible in dim themes
#38841 tinted the host pill outline 40% of the sidebar text into the
sidebar. A theme whose sidebar text is only 4.5:1 leaves that outline at
about 1.6:1 against the selected wash, which matches the pill's fill, so
the pill loses its shape.
When an app theme is painted, its colors are now concrete, so compute
once per theme the smallest mix (from 40%) that gives the outline 2:1
against the selected wash, the same floor focus rings use, and paint it
as --worktree-host-pill-outline-mix. main.css falls back to 40%, so
Orca's built-in themes and any theme bright enough keep today's look.
The tinted sidebar resets it, like the row wash. The wash's CSS fallback
joins the fallback table so the check (and the token reference) read
what main.css paints.
Refs #38641
* fix(sidebar): floor the host pill outline in the tinted left sidebar too
The tinted sidebar paints the app's text on its tint mixed into the app
background, so the theme's measured outline mix doesn't apply there and
it fell back to 40%: a dim theme (#919191 on #2a2a2a) at the default 8%
tint left the outline at 1.6:1 on the selected wash. Measure the tinted
colors once whenever the app theme or tint settings change, paint the
result as a root variable, and have the tinted sidebar read it. Share the
measurement (hostPillOutlineMixOver) with the theme path, and make its
comment name the actual invariant instead of the pill label's contrast.
Refs #38641
* refactor: group structured session modules by owner
* test: retire status history work-count oracle suite
* refactor(main): organize journal storage and projection owners
* Consolidate restart recovery fixture imports
* Consolidate concrete session option model imports
* fix(cli): keep read-only commands working when environment usage can't be recorded
The CLI recorded each environment's last-used time with a strict store write,
so a profile whose directory refuses chmod (EPERM) failed commands whose reply
had already arrived. The desktop already skipped such failures in one path.
Move that best-effort recorder to shared code and route every desktop and CLI
usage write through it.
Fixes#38673
* test: assert the usage write failure, not call counts
Refs #38673
* test: skip the chmod EPERM case on Windows and drop a cast
Refs #38673
* fix(terminal): a host with a window keeps a pane a client moved to a new tab
Dragging a split pane onto the tab strip in a paired worktree whose host runs
the desktop app with a window flickered and snapped back. The client draws the
new tab and calls session.tabs.movePaneToNewTab, but the host answered
not_held whenever it had a window, because the window owns the layout and only
headless hosts committed the move. The client reverted, and even without the
revert the host window's next snapshot would have rebuilt the old tab.
Main now asks its window to make the move and waits for the answer, the way
other client requests reach a window-owned host. The window moves the pane
with its own drag-out path under the tab id the client already drew, at the
client's drop position, mounting a parked source tab first. A pane the window
cannot reach is refused, so the client puts it back; a host without the
window bridge still answers not_held. Older clients and hosts are unaffected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(web): rely on the optional pane-move bridge instead of no-op stubs
The web client never receives host pane-move requests, and the bridge
already skips registration when these members are absent; dropping the
stubs keeps web-ui-api.ts under the line limit after rebasing on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(e2e): a window host keeps a pane a paired client moved to a new tab
The window-host case asserted the old refusal, where the client drew the new
tab and put the pane back. It now drags a pane out on a paired client and
checks the pane stays in the tab the client drew: the host window holds the
leaf alone under that tab id, no snapshot pulls it back, and the source keeps
one pane. It fails on main, where the tab count falls back after the drop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(terminal): refuse a pane move the host window cannot answer
When a paired client asks a host with a window to move a pane to a new
tab, the host relays the request to that window. If the window is gone,
closes, or does not reply within 10 seconds, the relay rejects. The
host returned that rejection as an RPC error instead of a move result.
The host now answers { status: 'refused', reason: 'pane_unavailable' }
in that case. The client already puts the pane back on a refusal, and
this is the reply the relay timeout was sized for.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(terminal): fence a window's pane move once the client heard it timed out
The relay told the client "refused" after 10s but left the window's move
running, so a late commit moved the pane on the host after the client had
put it back. Main now fences the commit: a move the client was told timed
out commits nothing, and a commit answers the client at once, before the
deadline can turn it into a refusal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(terminal): keep the pane-move fence Electron-free and let a claimed commit answer
The leaf-move IPC is shared with the plain-Node runtime, so the fence moves to its own
Electron-free module. A window that goes away mid-commit no longer refuses a move its commit
landed, and a second move cannot replace a fence a timed-out move still holds.
Fixes#37082
* chore(terminal): follow moved shared modules after rebasing the pane-move relay
Refs #37082
* fix(terminal): hold a client's pane move's source tab mounted until the move settles
The timed split-mount lease could expire while the window was still moving the
pane, so cold parking could unmount the source tab after main had already told
the client the move landed. The move now holds the tab mounted for its whole
duration and releases it when it settles.
Fixes#37082
* fix(terminal): don't toast the host window for a pane move another client asked for
Fixes#37082
* fix(terminal): relay a client's pane move only to a window that owns the layout
Where main owns the layout (the dark ORCA_LAYOUT_OWNER=main path), the window's
move goes back through main, which names its own tab, so the client's tab id
could not be kept. Main answers not_held there, as before this change.
Fixes#37082
* fix(terminal): name the renderer a relayed pane move belongs to
Fixes#37082
---------
Co-authored-by: Seunghwan <hyunsh.cycle@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(antigravity): report last-known quota for every saved account
agy has one shared login, so Orca never switches it to read another
account. Each successful usage read is now verified against the native
login before and after the fetch, attributed to the matching saved
account, and kept in the vault per runtime with its original read time.
A login change mid-read discards the reading. Settings, the status bar
usage popover, and orca account list --json show each saved account's
last reading, or unknown when it was never read.
Fixes#31664
STA-11623
* chore(antigravity): add saved-account quota copy and status bar fixture
* fix(antigravity): clear the sign-in hold when agy signs in again to the same account
Comparing logins by Google subject meant a fresh sign-in to the same
account never reported a credential change, so the meter stayed on
"Sign in with agy". A new refresh token now counts as a change; agy's own
token refresh keeps the refresh token and still doesn't.
* test(antigravity): attribute a WSL runtime's reading to that runtime's saved accounts
Antigravity launched in a WSL guest reads ~/.gemini/antigravity-cli/settings.json
inside the distro, but trust preflight skipped every non-Claude WSL launch, so
first-launch agy workers stopped at the trust menu. Write the guest's Linux
path into the distro home's settings (through its cached UNC home), and record
ownership under the host workspace path so worktree removal prunes it.
STA-8154 (CR-P2-002)
Per-pane terminal zoom lived in a ref owned by a single TerminalPane mount
and keyed by the runtime pane id, so any remount (hidden-tab parking, the
former Agents view) dropped it back to the global font size.
Store the override in a session-only map keyed by the durable leaf id and
make it the only per-pane font size store. The zoom handler and the
appearance pass read it by leaf id, and it is cleared on a real pane close
or when the tab is gone at mount cleanup, so detached/moved leaves keep
their zoom.
Co-authored-by: Neil <neil@stably.ai>
* Show workspace and branch identity below the chat composer
* Use chat context for composer workspace identity
* Read composer hosts from unified tab records
* Keep composer behavior fixtures focused on input flows
* fix: reuse guarded workspace reader in chat footer
* fix(chat): preserve folder identity and composer focus ring
* Show machine and workspace identity below the chat composer
* Avoid unrelated host focus invalidating composer identity
* Correct composer context and runtime fixture types
* Isolate machine-name lookup in composer flow fixtures
* Simplify composer submission bindings
* Keep the composer session options result together
* Match the composer machine icon to the sidebar
* Keep composer types and session options in their owning modules
Paired clients now publish the chat actually selected on the desktop as the active tab; saved layouts, CLI-created chats, closes and restarts keep it consistent. Carries community PR #34526.
Co-authored-by: Jurassic Knights <jurassicknights@gmail.com>
When Orca's local-account runtime is WSL on Windows, the Antigravity usage
meter still ran the host `agy`, so it showed the wrong account or "not
installed". Route the probe through runWslProcess for the selected distro
(never booting a stopped one), key the unsupported-CLI latch per runtime,
tag readings with the runtime as provenance, and drop another runtime's
last reading instead of keeping it as stale. Settings changes retarget
the fetcher and reread usage.
A WSL target without a distro now names the default through
resolveWslDefaultDistro (registry DefaultDistribution) rather than
listWslDistrosAsync()[0], which skips docker-desktop, falls back to
registry order, and caches the list for the process lifetime.
Ported from #31542 onto current main: reuses main's antigravity fetch
generation (#34165) instead of adding a second one, follows moved WSL and
provider-account-services paths, and keeps only the tests that cover the
new behavior.
Co-authored-by: CAVIN <35207870+zzz163519@users.noreply.github.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
* fix(codex): remove Orca-created pre-trust entries when a worktree is removed
Records which [projects."<path>"] tables Orca's preflight trust created in a
managed Codex config (with the exact bytes written), and on worktree removal
deletes only those, inside the existing trust-config mutation queue. Removal
paths await the purge so a removal is never reported before the entry is gone.
Ported from #24803 (commits 88d5469c, c1ffc5a4, 11b0bd74, f85606b3) onto
current main: conflicts resolved, stray re-export wrappers dropped, and tests
trimmed to AGENTS.md rules (ordering/timing, private-state, Cartesian and
lower-layer duplicate cases removed).
Co-authored-by: Neil <neil@stably.ai>
* fix(antigravity): drop Orca-added trustedWorkspaces entries on worktree removal
Generalizes the Codex pre-trust ownership ledger into a small factory
(agent-trust-ownership-ledger.ts) and uses it for agy: when Orca appends a
worktree path to ~/.gemini/antigravity-cli/settings.json trustedWorkspaces, it
records the exact entry, and worktree removal removes only that recorded entry.
Entries that already existed before Orca's grant are never recorded or removed.
* fix(trust): keep folder-workspace trust on session removal; isolate trust test userData
Only checkout-deleting removal paths drop Orca-created agent trust now.
Folder-workspace sessions and forgotten orphans leave the directory on
disk, so removing one must not strip the grant siblings still rely on.
Also point the execution-host trust test at a temp userData so the new
ownership ledger never writes to the real Orca config, and make the e2e
removeWorktreeMetadataAndHistory stub return its Promise.
---------
Co-authored-by: ggbdpq <ggbdpq@gmail.com>
* fix(antigravity): switch agy accounts through the Linux Secret Service keyring
agy stores its Linux login with go-keyring's Secret Service provider
(service=gemini, username=antigravity, login collection or default alias)
and falls back to its credential file without a session bus, in containers,
SSH and WSL. Add a secret-tool/gdbus adapter that mirrors that collection
choice, refuses locked keyrings with a clear error, and keep the file store
for headless hosts.
Fixes#32818 (STA-11911)
* test(antigravity): avoid casts in Linux keyring adapter test
Share one language-aware unicodeHighlight builder across the file editor,
the standalone diff viewer, and combined diff sections. Markdown skips
ambiguous-character highlighting so full-width punctuation no longer
triggers the warning banner; invisible-character detection stays on
everywhere. includeStrings is pinned to true because Monaco otherwise
resolves it to false in trusted workspaces.
Fixes#33860
Co-authored-by: QTom <qt@truesightai.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
An ACP agent's TMPDIR sat inside the data folder. On macOS that path made
a socket such as datacloud-mcp-dataAgentKit-antigravityide.sock 131 bytes
long, over the 103-byte limit, so child programs could not bind it.
When a socket under the data-folder root would not fit, use a short
per-user folder in /tmp (mode 0700, owner checked via ensureOwnedPrivateDir,
keyed by a hash of the data root so Orca copies never wipe each other), and
clear the old data root once. Roots that fit, and Windows, keep the current
behavior.
Ported from #33948 onto current main (acp-private-temp now uses
launchPreparationFailure; daemon-socket-endpoint-path moved under
daemon/endpoint/publication; the launch-parity test it touched was removed).
Co-authored-by: Manan Chataut <thefrikidude@thefrikidude.local>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* feat(cli): select and remove any saved agent account from `orca account`
`orca account select` and `orca account rm` accepted only OpenCode and
Devin. They now cover every provider with saved accounts — Claude, Codex,
OpenCode, Devin and Antigravity — through the runtime methods the
status-bar switcher and Settings already use (accounts.selectClaude,
selectCodex/selectCodexForTarget, selectData, antigravitySelect and the
matching remove methods). No runtime or wire change.
`--account` takes an id, an email/label (case-insensitive) or `system`.
A name shared by several accounts is refused with their ids rather than
guessing which subscription to bill. `system` is refused for Antigravity
(no system default), for Claude from the WSL bridge (the RPC has no
target and would reset the Windows host), and always for `rm`.
`orca account list` now prints each account's id and a `system` row,
accepts `--agent` for every provider, and adds the Antigravity block when
the host can read it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(cli): assert the host refusal for Antigravity accounts from WSL
The host rejects every client-selected WSL target for Antigravity account
management, reads included, so the test now mirrors that refusal and checks
that no select call is made instead of asserting an impossible success path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(cli): keep only the account-selection tests that guard this change
Moves the guards into account.test.ts (the handler layer that owns them) and
drops layer-duplicating, copy-pinning, Cartesian and mock-only cases per
AGENTS.md test rules; un-exports resolveAccountId (test-only export).
---------
Co-authored-by: Maxime AUBURTIN <m@hellomax.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(runtime): fail agent readiness fast when the program reports a sign-in block
agy shows its login menu when signed out and reports OSC 7501
state=blocked:kind=auth. tui-idle treated that as pending, so worker-start
waited out its whole readiness timeout and reported a vague
'Agent did not become ready'. Rank a fresh program sign-in block as a
blocked verdict (agent-sign-in-prompt) so worker-start fails at once with
Agent startup blocked: agent-sign-in-prompt and never types the task into
the login menu.
Fixes#37056 (STA-13300)
* fix(runtime): require an agy sign-in block to persist before failing readiness
agy flashes its sign-in splash at startup and then signs itself in (#28348), so a fresh OSC 7501 blocked kind=auth is not yet final. Hold the verdict pending for 3s from the first auth report (repeats keep the first stamp; idle/working supersede it) and recheck when the window ends.
* feat(github): filter Tasks PRs by state and by who, like GitLab MRs
GitHub PRs in Tasks get the same two chip groups as GitLab MRs: state (Open, Merged, Closed, All) and who (Everyone, Mine, Reviewing). The chips read and write the search query, so labels and typed text survive a click and nothing new is saved. A combination equal to one of the old presets is applied as that preset, so saved views, prefetch and the phone see the same strings as before.
Tracked in STA-14084.
* fix(github): keep right-click on Open saving the open-PRs default view
* fix(github): wrap the PR state and who chips as two groups
* feat(native-chat): show details on link cards for any repo on a known host
A chat link card for a pull request or issue in a repository other than the
workspace's own showed only its name, because the lookup answered only for the
workspace's configured remotes. Previews now read any repository on a host those
remotes use, with that host's sign-in, as the common pattern does. Other lookups
keep the exact-repository check, and an unknown host still gets no credentials.
* fix(native-chat): open link cards for other repos on the chat repo's host
The hover card still admitted only the chat repo's own links, so the wider
lookup was never reached. Links to any repo on a host the chat repo uses now
open the GitHub card. When an older host or a repo the sign-in can't read
leaves it unanswered, the card falls back to the web preview, or just the
link's name, as such a link showed before, instead of a failure.
* fix(native-chat): keep another repo's web preview until its details arrive
A link card for another repo showed the GitHub skeleton first and only fell back
once the lookup failed, so older hosts got two loading stages and a change of
shape. It now shows the web preview it always showed until the details arrive,
and keeps it when none do.
* fix(native-chat): show just the name for another repo's link while its details load
With web previews off, a link card for another repo showed the GitHub skeleton
and collapsed to the name when no details came. It now shows the name from the
start, so nothing collapses when an older host or an unreadable repo answers
with nothing.
* feat(native-chat): show a card for pull requests a chat turn opened
When a turn's own command runs gh pr create and prints the new URL, the chat
shows an "Opened orca#3" card under that turn with the PR's state, title and
changed lines, read live from GitHub. The eye-slash control hides the card kind
and a toast links to the new Settings switch.
* fix(native-chat): satisfy lint in the opened pull request card
* fix(native-chat): keep opened pull request cards through tool runs and failed rereads
The card now rides a turn's work run, a clipped create output is always read
whole (retried when the chat is shown, never while hidden), older Codex JSON
output is decoded, rereads keep the last answer and restart past an in-flight
read, failing lookups back off, and a failed hide save says so.
* fix(native-chat): detect pull requests Codex opens through PowerShell, and wait for clipped output
Codex on Windows runs commands as pwsh -Command '...', which detection now
unwraps (and cmd /c), with tests on Claude's and Codex's recorded calls. A
clipped create output is decided from its whole text, each message is scanned
once, rereads keep their bytes counted, a merged card rereads after an edit,
and the header names another repo's pull request in full.
* fix(native-chat): retry each clipped pull request output until its host answers
Each create output cut short on the wire keeps its own backoff while the chat is
visible; a host whose support is unknown stays pending, and only one that says
it can't give outputs whole is decided from the head. PowerShell options and
backtick escapes are read as PowerShell does.
* fix(native-chat): read PowerShell create commands in PowerShell's own quoting
Decoding a tool call's script moves to its own module. PowerShell launches are
lexed with its quoting (single quotes keep backticks literal, double quotes and
bare text escape with them, here-strings and comments are text) and launch
options follow the common pattern's tables; what can't be read whole opens
nothing. A cancelled retry no longer records a failure.
* fix(native-chat): decode a launch wrapper inside a PowerShell tool call
* fix(native-chat): read Codex's escaped quotes in a Windows PowerShell command
Codex on Windows writes a double quote inside a double-quoted -Command as \",
so a create with an apostrophe in its title was skipped. Tests use the three
calls recorded from Codex on Windows.
* fix(native-chat): read a PowerShell launch line in the quoting of what ran it
Codex joins argv in POSIX quoting even on Windows, splitting one word into
differently quoted pieces, so a script ending in a backslash or holding both
$ and an apostrophe showed no card. The launch line now goes through the same
POSIX word decoder as the login-shell preview, and a PowerShell tool's own
line through PowerShell's quoting.
* fix(native-chat): find a PowerShell or cmd launch anywhere in a command list
A Bash call such as `pwsh -Command 'gh pr create --fill' && echo done` lost its
card once launch lines were read as POSIX words, and Codex's joined
`cmd.exe /c 'script'` never had one. Each command in a POSIX list is now read on
its own, through one launch reader shared with argv, while Node's Windows
`cmd.exe /d /s /c "script"` form keeps cmd's own quoting. PowerShell lexing
moves to its own module.
* fix(native-chat): unwrap a PowerShell or cmd launch only when it is the whole line
Rebuilding a command list around a nested launch could pull one out of a comment
or heredoc into a false card, and an unreadable launch hid a real create beside
it. As the common pattern does, only a line that is one launch is unwrapped;
any other line is analyzed as written. A PowerShell tool's line and Node's cmd
form must likewise be a single launch.
* fix(native-chat): unwrap cmd and PowerShell-tool lines only when they are one launch
Node's cmd form now unwraps only a /c script that is one quoted string with no
quote inside, since cmd escapes none, and a PowerShell tool's line ends at a
bare line break. Text after either launch can no longer pass for its script.
* fix(native-chat): read a PowerShell line continuation as a word break
A backtick before a line break in a PowerShell tool's line joined the next line
into the current word, so an argument on the next line could pass for a
command after the launch.
* fix(native-chat): unwrap only the launch shapes agents are recorded writing
Hand-written readers for cmd launches and for a launch nested in a PowerShell
tool's script kept letting printed text pass for a create. Only argv, a shell's
whole -c line and Codex's Windows pwsh -Command line are unwrapped now; argv is
never space-joined, and detection reads a shell line as whole POSIX words rather
than through the preview's lenient prefix. cmd launches and nested launches are
intended misses until an agent is recorded using them.
* fix(native-chat): read an escaped separator as text when finding a create
An escaped \; stayed in the analyzed commands and passed for a separator, so
printf '%s\n' \; gh pr create showed a card though only printf ran. Escaped
characters are now text, like quoted strings and comments.
* fix(native-chat): remove a line continuation when finding a create
Blanking escapes also turned a backslash-newline into a word, so a create on
the line after a continuation no longer started a command and lost its card.
A continuation is now removed, as the shell removes it.
* fix(native-chat): treat only backslash-LF as a line continuation
Bash removes a backslash before LF but not before CRLF, where the backslash
escapes CR and LF still ends the command. Removing both lost the card for a
create on the line after a CRLF continuation.
* fix(editors): launch Open In editors by id from settings
* test(editors): drop settings casts by narrowing the shell store type
* fix(editors): import shell-open types from their moved path
* test(editors): type the shell test settings instead of casting
* fix(web): confirm before a pairing link replaces a saved server
* test(web): drop the stored-environment cast by narrowing the pairing decision input
* i18n(web): translate the replace-saved-server prompt
* 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>