Commit Graph
13989 Commits
Author SHA1 Message Date
Brennan Benson 30909c77dd perf(worktrees): fetch the base while the host's create picks a name (#26007)
* perf(worktrees): look up a branch's PR only after a name collision on the host's create

The host's create (agent.launch, worktree.create, the phone and the CLI) called gh pr list for every
create, a 1-3 s network round trip, while the desktop create probes only after a branch collision. A
branch with an open PR exists on the remote, so the branch-conflict check already moves past it.

* test(worktrees): pin first-name PR lookup skip on the host's create

The runtime service specs still expected a PR lookup for a free first
name. Update them to the desktop-parity behavior: no lookup without a
branch collision, and a free push-target name is kept. Pin the exact
suffixed name, and that a later name with a PR is skipped.

* perf(worktrees): overlap the host create's base fetch with naming

Keep the first-name PR lookup (restores main's naming) and take it off
the critical path instead: start the base-ref refresh before naming and
await it after, as the desktop create does, so the lookup and the fetch
run concurrently. A refresh rejection while naming fails stays handled.

* test(worktrees): pin the overlapped origin fetch on the host's create

Cover the best-effort fetch origin path: it runs while the first-name PR
lookup is pending, and its rejection while naming fails stays handled.
2026-10-13 21:27:34 -07:00
Brennan Benson 04b4e962a7 fix: a Claude model that's gone switches to the list's default when you send (#27826)
* fix: start a new Claude chat on the listed default when its remembered model is gone

The remembered new-chat model was launched as-is even after Claude stopped
offering it, so every new chat failed its first message. The host create and
the desktop new-chat picker now derive the same replacement from the host's
saved model list: the listed default, effort moved to the nearest level it
offers. Explicit models (--model, held picks) run as given; with no saved list
the remembered model is kept.

* fix: read the host's model list only for a Claude create that needs it

The check read host.deps before ruling out other agents and explicit models,
so creates whose host carries no deps (and every non-Claude create) paid for
a lookup they never use.

* fix: picker derives the fallback only where the host would

A held model runs as picked on the host, so the picker no longer derives an
effort beside it. A cached list whose default marker was dropped for this
workspace keeps the placeholder instead of naming its first row.

* fix: the new-chat picker picks the replacement for a gone Claude model

The host-side check read the saved list separately from the picker, so the
two disagreed and a cold start never fired it. The desktop new-chat picker
now picks the replacement it shows, once, through the existing pick path,
so the first send runs it and the remembered selection heals. The host is
back to main.

* fix: decide a gone Claude model's replacement at send, from the list in hand

The open-time auto-pick raced the chat's create and the list's arrival, so
the first send could still launch the gone model. Now, like the common
pattern, the picker shows the replacement for a model the host's loaded
list lacks, and the send step applies it through the existing pick call,
awaited, right before the message goes out. A failed pick never holds the
send back.

* fix: one verified list decides a gone model's replacement

The picker, the send step and the option setter now all read the list the
picker shows, less the row a session adds for its own unlisted model, so
what is shown is what applies and an explicit pick from that list stays.
A listing that is still running, failed, or absent keeps the model. The
send step re-derives from the chat's state when it runs, so a second send
behind one that already moved the model applies nothing.

* fix: only rows a finished host listing names can replace a gone model

A single verified flag outlived the catalog it described: live options built
from the built-in fallback list, or a malformed or empty host answer, still
authorized a replacement the host would refuse. The state now keeps the ids
a finished, well-formed host listing names, and a replacement must be both
shown in the picker and named there.

* fix(native-chat): the send carries the composer's Claude model and effort

Every composer send (desktop and phone) carries the model and effort the
picker shows, as optional agentSession.send options, only to a host that
advertises agent-session.send-options.v1. The host writes a carried value
that differs from the chat's own through the existing pick path before
accepting the message, so the turn starts on it and the chat records it.
A queued card keeps its options and applies them when it goes out; a
launch's create call carries the replacement for its first message.

Replaces the separate awaited pick before each send.

* chore: drop an unrelated lockfile change

* refactor(native-chat): one helper names a send's control fields for client and host

* ci: rerun against current main

* fix(native-chat): a message runs on the model and effort it was sent with

The carried selection is now stored with the message (its submission row,
and a queued card's row) and applied through the shared option writer as
the delivery loop hands the message over, or before the start it needs.
A pick made after the message was accepted no longer changes the model it
runs on. Direct sends, queue drains and Send now share that one apply
point; the early apply before acceptance and the queue-only apply are gone.

The picker and the send read one effective selection: the replacement for
a model the host's finished listing lacks shows only for a host that takes
a send's selection, the phone validates picks against what it shows, a
send carries the default the composer shows, a resumed launch's first
message gets the same resolution, and only the listing proves a row (no
label heuristic).

* docs(native-chat): a submission's options are the host's alone

* fix(native-chat): an opening message keeps its model; a pending pick resolves like the picker

The opening message a create sends now stores the model and effort the chat
was created with, so a pick made before its handover no longer changes what
it runs on, and a card kept after a restart keeps them.

A pick on its way to the host resolves through the same option view the
picker shows before a send carries it, so a gone model it names is replaced
too; the send selection reads only the shown snapshot.

The option writer builds its own record-and-publish context, so the message
path no longer duplicates it, and the client and host share one function for
a send's fingerprint fields.

* ci: rerun against current main
2026-10-13 21:21:47 -07:00
Jinwoo Hong 6741f4f165 refactor(orcad): move the Node host adapters out of orcad-entry.ts (#28530)
Fixes main's lint: #28464 left orcad-entry.ts at 301 lines (max 300). No behaviour change.
2026-10-14 00:01:28 -04:00
NeilandClaude ed9ada5fe9 fix(orcad): move host account env restore out of orcad-entry to clear max-lines (#28529)
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-13 20:58:38 -07:00
Jinwoo Hong b9bd8dbe4a fix(agent-status): retire any restored agent row whose terminal died while Orca was closed (STA-10194) (#28475)
* wip

* refactor(agent-status): tighten the restored-status sweep after review

- reword preserveResumeIdentity to the real rule: the pane survives to resume into
- one isLocalHookConnectionId predicate for the sweep, removed-worktree cleanup, and runtime PTY sources
- the startup wrapper is file-private with a distinct name; probe catch simplified
- stale 'liveness reap' wording in the Claude roster comment and test
- tests: the Claude roster case is asserted through a later turn settling, and a hydrated
  resume remnant is pinned as never a candidate
2026-10-13 23:48:52 -04:00
Neil 03eee256f1 fix(wsl-host): let an app update reach a WSL host orcad (E1) (#28458)
* fix(wsl-host): read the running orcad's census before an app update replaces it

* fix(wsl-host): keep Force, the incumbent watch and its freshness through an update

* fix(orcad): recount terminals just before an update stops the incumbent

* refactor(wsl-host): move watch arming and pairing identity out of oversized modules
2026-10-13 20:38:33 -07:00
Jinwoo HongandClaude 5f134d8f01 feat(mobile): show and use the desktop's server workspaces on the phone (#26501)
* test(e2e): add a paired phone client helper and phone-mirror baseline specs

Later phone-mirror slices need one oracle that drives a real mobile-scoped device with the
phone's own capabilities against a serve host or a paired desktop. Pins #26022 across a serve
restart and today's gap: a desktop's phone does not list the desktop's server workspaces.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(mobile): show SSH host health on the workspace row host badge

Rows on an SSH host the desktop cannot reach looked healthy on the phone. The badge now
adds the desktop sidebar's own word ("devbox · Disconnected"), derived by the same shared
function from the connection status ssh.listTargetSummaries already sends; old desktops keep today's badge.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(e2e): compose the phone-mirror topology and check the cold snapshot

Slices need one composer for host, desktop and phone with a single teardown, so specs stop
copying setup. The serve-restart spec now reads the first tab-stream snapshot after the cold
start, which is what the phone builds its tab strip from.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(mobile): name an unhealthy SSH host even in a single-host list

The desktop card shows SSH status on any host count, so the phone now reads target summaries
whenever a repo is on SSH and badges an unhealthy host alone too. Labels and health share one
target-to-host keying, and a hook-level test pins the reply-to-row wiring.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(e2e): keep phone-mirror teardown errors from masking test failures

Teardown failures become a test attachment instead of replacing the real failure, and the
cold-snapshot poll reports stream errors together with the PTY list.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): clear stale SSH host badges and announce them without the dot

A catalog that no longer spans hosts or holds an SSH repo now clears the health and label maps,
so a host that left cannot keep badging rows. The badge gets a screen-reader label ("devbox,
Disconnected") instead of reading the middle dot.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(runtime): let a paired desktop register its phones as delegated mobile devices

A desktop paired to this runtime can now call pairing.delegatedMobileDevice.sync to make each of
its phones an ordinary mobile-scope device it owns, so the phone mirrors that host through the
desktop. Children are never pending, inherit the parent's reach, and die with the parent's grant.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(runtime): move the mobile device revoke primitive below dispatch

Dispatch binds the caller-bound delegated-device context, so the revoke path it needs now lives in
a layer under it as a pure move; this drops the abstract method seam. No behaviour change.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(runtime): keep a runtime grant until its delegated phones are revoked

A failed child cleanup now leaves the parent in place so a retry can still find the children, and
revokeRuntimeAccess lives beside the revoke it cascades to. Adds restart, rename and bounds coverage,
shares credential minting, and drops the timing-dependent census.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(runtime): fail a delegated sync whose dropped phone cannot be revoked

Matches the parent revoke: a child whose cleanup could not be saved is kept and the sync errors
before minting anything, so the desktop retries instead of believing the server matches its list.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(mobile): relay a paired phone's server-workspace requests through the desktop

A phone mirrors its desktop, including workspaces on the desktop's configured servers. The desktop
now forwards a request that names `executionHost: runtime:<env>` to that server over one socket per
(phone, server), signed in as the phone's delegated device; replies pass through untouched except
stream ids. Hidden until the phone client ships: the relay capability is not advertised yet.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): type the single-host health fixtures inline so the test tsc passes

Fixtures hoisted into consts widened their health maps to string, which the now-required
hostHealthById rejects under the tests-typecheck ratchet; passing them inline keeps the union.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* docs(mobile): state the desktop relay contract in its entry module

main dropped docs/reference, so the relay's contract lives beside the code. Also pins that a
server whose delegated sync fails (e.g. delegated_device_revoke_failed) is unavailable until a
later request syncs it again.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): pin the relay's configured-server adapter

The production hosts adapter was only typechecked; this pins that it resolves configured servers
by exact id only (never a name or a removed server), fences by pairing revision and runtime
identity, and stops reporting retirements once unsubscribed.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): re-record the repo-metadata goldens for SSH host health

The settled metadata state now carries hostHealthById, and an SSH-only catalog now reads the
target summaries so a lone unhealthy host can be badged. Scoped re-record via rpc-recording.mts.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile): tighten the desktop relay after elegance review

Reuses the shared E2EE socket opener, folds relay state into fewer fields, never sends a request
whose link ended while opening, passes failure codes through, relays only this desktop's own
phones, and drops the unused host-state reader until S2 needs one.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): bound relay frames and stop sticky relay verdicts

Restores the 8 MiB frame cap on every remote runtime socket, re-checks an update-needed server on
each new link so an in-place upgrade relays, answers a phone the server just declined without
syncing again, and ends a link whose mirrored capability update cannot be queued.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): run targeted desktop-owned calls on the desktop

The phone tags every call on a workspace screen with its execution host, so a
paired-desktop method carrying `runtime:<env>` now runs locally instead of
failing 'forbidden'. The Mac stays the only reader of the routing table.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(mobile): list the desktop's configured servers and their last workspace rows for the phone

The phone needs the same host list the desktop shows, including servers that are offline or
too old to relay to. mobileRelay.hosts.list/worktrees answer from the desktop's own status and
its own worktree.ps fetches, cached per server and fenced by pairing, never from relayed replies.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile): list server rows through one desktop fetch, with no side effect on listing

The phone now always lists a server's rows through mobileRelay.hosts.worktrees, so the
desktop's own fetch is both the live source and the cache, and stamping stays in main.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): fetch a server's full workspace list within a bound, and forget removed servers

The desktop now asks for the phone's full-catalog limit, answers within 5s with the last rows
when a server hangs, drops a server's rows when it is removed or re-paired, and checks the
server's identity after the fetch so a reply from before a re-pair is never served.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(mobile): name a server workspace's host on the phone's calls

A phone reaches a server's workspace through its desktop only if each call names the host; the
direct, cloud-relay and shell-bridge transports now carry `executionHost`, streams keep it for
replay and unsubscribe, and an older shell (no `executionHost` grant) refuses rather than strips it.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): list managed servers as the desktop sidebar does, and hide rows mid-delete

A server Orca manages on an SSH host now lists once under that host's name, or not at all while
the host is reached over the SSH relay, using the sidebar's own folding. Rows the server is
deleting are left out at reply time, as a phone asking the server itself would see.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): keep a disconnected server's last rows, and never join a re-paired server's fetch

Retirement also fires on a manual disconnect or managed-server update, which wrongly emptied
the phone's list; rows are now pruned only once a server is no longer configured. Fetches are
shared per server identity, so a poll after a re-pair asks the new server right away.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(mobile): list the paired desktop's server workspaces on the phone

Fixes the phone missing a desktop's `orca serve` workspaces: rows come from the desktop's
mobileRelay.hosts.* with each server's health ("Update needed", "Disconnected"), open through the
relay, follow the desktop's hidden hosts, and the desktop now advertises the relay capability.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): pin that a desktop failing the hosts list leaves only its own rows

The page asks a relaying desktop for its servers; a refusal such as method_not_found must leave
today's list with no server rows and no catalog error.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): mirror the desktop's shown hosts exactly, one host included

Jinwoo's call: the phone follows visibleWorkspaceHostIds as the desktop sidebar does, so a
one-host list narrows the phone too instead of being read as a single-host focus.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): scope a server workspace at its route, never the desktop

Every screen under a server workspace's route now reads one context and
gets that server's client and a router that keeps pushes on it, refusing
to fall back to the desktop; pins write to the row's owner like the Mac.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): never open an unreachable server workspace, and prove the relay in e2e

A server workspace the phone can't reach now gets one explicit screen at its route
and no row tap or action-sheet entry opens it; native routes read the route's host,
a census pins every workspace route, and the e2e reads files and git on the server.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* feat(mobile): gate a server workspace on that server's own features

A targeted status.get now relays to the server, so a server workspace's route
provides that server's status to every screen under it and its action sheet,
while whether the phone can reach the server stays the desktop's answer.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(ci): run the remote-runtime WebSocket opener test on Node

Under the bun vitest project, `ws` resolves to Bun's built-in client, which
ignores maxPayload, so the frame-cap test hung. The opener only ships on
Node/Electron, so run its test in the Node runtime project like its siblings.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): give the file preview the workspace router, back to 128 page assets

The preview was the one workspace screen on the plain router, so the scoped router
was reached by a route set of its own and esbuild split it into an extra chunk
(129 assets); every workspace screen now uses it and it shares the route's chunk.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile-relay): derive the phone allowlist from the routing table

Adding a phone method now takes one edit (its route). A server-targeted call on a
legacy non-E2EE socket is refused instead of running locally, and only
execution-host methods enter the relay.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile-relay): drop unread freshness fields from server workspace lists

The phone reads only the rows, so stale, fetchedAt, totalCount and truncated
were dead wire weight. The desktop still serves a server's last rows while
the server is unreachable.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile): read only the rows of a server's workspace list

The desktop no longer sends a freshness flag, so the phone's schema drops it.
The relay capability list names the delegated-devices constant instead of
repeating its literal, which also retires a now-circular type check.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): show a server workspace as disconnected while its server is down

The phone read the desktop's connection state for a server workspace, so a stopped
server kept a green dot and "Loading tabs" spun forever. The workspace route now
narrows that state by the server's health from mobileRelay.hosts.list, reusing the
existing disconnected session UI and refetching tabs when the server returns.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile): share one desktop server-list feed between the host list and workspaces

A server workspace polled mobileRelay.hosts.list beside the host list's own read, so
the phone could ask the desktop twice per tick and the row badge and the screen could
disagree. One feed per desktop client now owns that read; both readers join it.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(e2e): read a server's reachability from the hosts list, not a dropped stale flag

eff410fe30 dropped `stale` from mobileRelay.hosts.worktrees, so the mirror spec's
schema never parsed and its first poll timed out. It now waits on the server's
relay/health from mobileRelay.hosts.list, the field the phone itself reads.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* build(mobile): re-measure the page's script sweep for server workspaces

Main already sat on its 78-script ceiling (re-swept: 78, table: 74); the workspace
route's shared modules split importer sets three ways more, reaching 81. Re-swept per
route prefix on this head, as #24257 did for its feature; the spread is now 1 to 11.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile): export the server-list feed's React reader beside the feed

useRelayHosts moves from workspace-server.ts into relay-hosts-feed.ts, so every reader (workspace screens now, the new-workspace sheet next) subscribes through the feed's own module. No behaviour change.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): keep polling the server list and read a server's reachability from relay

A hosts-list read slower than the poll stopped the feed for good, since the next tick was
scheduled when the read started; it is now scheduled from the finish. A server workspace now
reads connected only when the desktop relays to it, so a server with no status yet is not shown up.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): show a reached server as connecting and a too-old one as disconnected

A server the desktop has reached but whose status is not answered yet is moments from relaying,
so it reads connecting; a server too old to relay to never will, so it reads disconnected.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(shared): move the account capabilities into their own group module

Main's merge put protocol-version.ts at 301 lines, over the 300 cap. The account group moves out
like the relay and skill groups did, same strings in the same order; the page's script sweep is
re-measured because the split changed its chunking (81 to 79 at 16 routes).

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile): name a server workspace through the request envelope

The server name now rides the generic request envelope from #27815 instead of
its own executionHost option, bridge grant and per-transport plumbing. Server
rows show only where the transport carries an envelope (native, or a shell
that grants rpcEnvelope), so an older app keeps today's list.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): pin the server-call wire and the no-envelope deep link

A server workspace call still reaches the desktop as the same top-level
executionHost frame, on the direct and relay wires, and a deep link into a
server workspace under an app without rpcEnvelope shows the unreachable screen.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile): read a relay stream's envelope slot in sorted key order

Equal envelopes name one cleanup slot whatever their key order, the scoped view
passes its client's envelope answer through as it does the generation, and the
direct wire harness is shared instead of rebuilt.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): a phone's new chat in a server workspace follows the desktop's chat setting

A relayed agent.launch ran on the server, which picked chat vs terminal from its
own setting. The desktop relay now stamps its own default as nativeChatDefault;
new servers use it in place of their setting (feasibility still decides), older
servers strip it. Outside the replay fingerprint.

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

* refactor(mobile): move the relay's delegated phone grant cache into its own module

Keeps mobile-desktop-relay.ts under the line limit once #27749's page relay lands on it.

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

* fix(mobile): show the early terminal tab from the relaying desktop's chat default

The early-tab preflight read the server's own chat setting while the launch
used the desktop's stamped nativeChatDefault, so on a windowed server with
chats off a relayed chat launch flashed a terminal tab. Both now read the
settings through readAgentLaunchModeSettings(runtime, nativeChatDefault).

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-13 23:35:33 -04:00
OrcaWinandm4air f50c23cc35 fix(ssh): let the stdio bridge clear an ssh -L an earlier Orca left on its port (#28509)
Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-13 20:32:48 -07:00
Neil 13a75d3939 [CI] Reduce redundant test work and import existing helpers directly (#28365)
* [CI] Reduce redundant test work and import existing helpers directly

* [CI] Preserve provider guards and pin the mobile browser test core

* [CI] Fix CI test setup and verify retained contracts

* [CI] Compare native media chunks without boxed byte arrays

* [CI] Detect shifted native media reads with position-sensitive bytes
2026-10-13 20:25:00 -07:00
Neil abe2e8118d perf(ci): reuse import analysis and run localization coverage with Bun (#28463)
* perf(ci): share import analysis and run localization coverage with Bun

* test(ci): recognize the shared import guard in parallelism contracts
2026-10-13 20:17:23 -07:00
701e9f2bdf feat(new-workspace): add "None" agent option that opens only the setup script (#25617)
* feat(new-workspace): add "None" agent option that opens only the setup script

New workspaces previously required choosing an agent or a blank terminal,
so a setup script that launches its own agent always ran next to an extra
shell. The quick-create agent picker and Settings > Agents now offer
"None (setup script only)": when setup runs, the workspace opens with just
the Setup tab focused; without a setup script it falls back to a blank
terminal.

The default is stored as defaultTuiAgent 'blank' plus a new optional
newWorkspaceSetupOnly flag, so clients and servers that only know 'blank'
degrade to a blank terminal instead of rejecting an unknown value.

Closes stablyai/orca#25247

* fix(new-workspace): honor the "None" default for CLI-created workspaces

"None (setup script only)" only took effect in the new-workspace dialog.
A workspace created with `orca worktree create` still opened a blank
terminal first and left it selected, because the main process builds
those tabs and never read the setting.

The main-process tab provisioning now skips the blank terminal when the
default is "None" and a setup script runs in its own tab, so Setup is the
only tab. The same rule applies to CLI creates that activate the
workspace, and to dialog creates the user navigated away from before
they finished. A split setup or repo default tabs still get their shell.

The setup-only check moved to src/shared so main and renderer share it.

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

* fix(new-workspace): register the CLI activation caller and stay under the line limit

The CLI activation path now passes providesInitialSurface for the "None"
default, so it has to appear in the test that lists every caller using
that option; the full test run failed without it.

Merging main pushed worktree-creation-flow-execute.ts to 301 lines against
the 300-line limit. Naming the background-create condition brings it back
to 300 with no behavior change.

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

* fix(new-workspace): open a blank terminal when the setup-only Setup tab fails to spawn

The "None" default skips the blank shell because the Setup tab will be the
only surface. If that spawn fails, the workspace was left with no terminal.

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

* style: format the setup-only fallback terminal call

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

* fix(workspace): save None through host settings and honor per-create choices

* fix(settings): clear None when legacy clients choose a blank terminal

* fix(settings): clarify None with split or skipped setup

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
2026-10-13 20:12:44 -07:00
Brennan Benson a622bb0f4b fix(native-chat): phone Stop reaches a message the agent hasn't answered; unreadable task list reports unconfirmed (#28243)
* fix(native-chat): phone Stop reaches unanswered sends; unreadable task list is unconfirmed

The phone named the running turn on every Stop, so a Stop pressed before the
agent echoed the message said "Stop not sent" and the agent ran on. Against a
host advertising agent-session.conversation-stop.v1 the phone now sends the
Stop that names no turn, as the desktop already does; the host stops whatever
the chat has in flight. A card's Cancel still names its turn, and older hosts
keep the turn-named Stop.

A background-task Stop whose host could not read the session's task list read
the failure as "no tasks" and answered cancelled:false. It now refuses as
outcome unknown, the refusal a timed-out background stop already returns.

Adds host tests pinning the Stop-before-echo races on the unnamed Stop: Codex
turns admitted or started before the echo, a later send just before the Stop,
a retried Stop, and a Stop after a failed one. All pass on main unchanged.

* fix(mobile): show Stop before the turn opens; a card's Cancel keeps its turn

The phone offered Stop only once a turn existed, so the Stop that names no turn
was unreachable while a send waited for the agent's echo. Against a host that
advertises conversation stop, a working chat now offers Stop.

A card's Cancel chose the untargeted form from the prompt after the
prompt-cancel capability filtered it out, so against a host with conversation
stop but no prompt cancel it sent a Stop naming nothing. It now always names
the card's turn.

The race test for an admitted turn that cannot take an interrupt accepts
success only once the host has let go of the agent, and otherwise requires the
unconfirmed refusal.

* ci: rerun against current main

* ci: rerun against current main
2026-10-13 19:50:06 -07:00
Kelvin AmoabaandBrennan Benson 464bd5a54b feat(native-chat): combine model and options into one picker (#27972)
* feat(native-chat): combine model and options into one picker

Setting a model and its effort took two menus that each closed on a pick.

* feat(native-chat): close the model picker on an effort pick

Effort is the last thing set before typing.

* fix(native-chat): write the picker's kept rows after commit

React Doctor rejects a ref written in render, and a discarded render could leave a row behind.

* fix(native-chat): open the picker once per /model or /effort, tooltip only when informative

The session kept a slash command's picker request, and the picker remounted
open from it whenever a question or approval card gave the composer back. The
request is now consumed when the picker sees it, and open state lives in the
popover, which unmounts with the pill, so it comes back closed.

The pill's hover tooltip only named the categories the pill already shows,
and the picker covered it on click. It now shows only the disabled reason or
the not-confirmed note.

Also drops the removed Options key from the locale test and covers the new
short effort labels.

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-10-13 19:27:05 -07:00
Neil 31e5003572 fix(accounts): launches a WSL host starts itself use the account selected for its distro (#28464)
* fix(accounts): launches a WSL host starts itself use the selected account

The desktop now pushes the account env it selected for a WSL host's distro
to that host's orcad (accounts.setHostLaunchEnv, capability-gated). orcad
lays it over every terminal create (CLI, orchestration, automations, bare
commands and presets), structured chats and AI commit text, as the
desktop's own launches into the distro do. `orca account` on the host now
points at the desktop that owns the accounts.

* fix(accounts): cover splits, child agents and restarts; skip SSH workspaces

Review follow-ups: terminal splits and structured chats' child agents take
the host account env too; an SSH workspace's terminals never get distro
paths; orcad saves the pushed env so a launch before the desktop reconnects
keeps the account; a failed push retries unless a newer one supersedes it.

* fix(accounts): retry a host account push when the host's capabilities are unknown

* test(orcad): the startup mock restores host account env

* refactor(accounts): serialize host account pushes with the shared keyed queue
2026-10-13 19:15:28 -07:00
Neil 89e8aafadc fix(accounts): a focused WSL host shows and switches its distro's accounts (#28465)
* fix(accounts): a focused WSL host shows and switches its distro's accounts

A WSL host's orcad has no account services, so the status-bar switchers,
Settings > Accounts and the Codex restart prompt asked it for accounts and
got 'Account services are not configured'. This desktop owns that distro's
accounts (the same ones a WSL pane uses), so those surfaces now route a WSL
host to the desktop roster for its distro, and WSL-host panes join the
distro's Codex restart lane.

* fix(accounts): reuse the localized WSL runtime label path for a WSL host
2026-10-13 19:15:17 -07:00
Brennan Benson 9db7b8b6b2 fix(files): never write through a dangling symlink (#28385)
* fix(files): never write through a dangling symlink

A path that doesn't exist yet was authorized by walking up to the nearest
existing folder. A dangling symlink fails realpath with ENOENT, so the walk
stepped over it and approved the link's own path; the write then followed
the link and created its target outside the allowed folders. The walk now
continues from the link's target, and returns that real destination.

* fix(files): refuse any dangling symlink in the missing-path walk

Following a dangling link by hand resolved relative targets against the
lexical parent, not the link's real folder: through a folder link the walk
could loop forever or authorize the wrong file. Refuse instead: realpath
can't say where a dangling link leads, but a write would follow it.

* test(files): drop the loop case that duplicated the dangling-link refusal
2026-10-13 19:08:15 -07:00
Brennan Benson efbe3210d6 perf(session-history): keep a timed-out scan's finished transcripts (#27937)
* perf(session-history): save finished transcripts while a scan runs, not only after it succeeds

* test(session-history): check the mid-scan save without relying on a cancel race

* fix: avoid cache checkpoints for unchanged scan batches

* fix: checkpoint committed session cache changes

* fix(ai-vault): retry failed in-flight cache save on flush
2026-10-13 19:07:10 -07:00
Neil b525a3ba91 refactor(wsl): reuse the WSL-host predicate, error helper and a shared owner lookup (#28447)
* refactor(wsl): reuse the WSL-host predicate, error-message helper and a shared owner lookup

- isWslHostRuntimeEnvironment becomes a type guard and replaces inline
  orcadDeployment?.kind === 'wsl' checks in main and renderer.
- WSL modules use shared/error-message instead of two local copies and three inlined ones.
- The workspace row-owner lookup moves out of the IPC bridge into lib/, since the terminal
  pane and host removal import it too.
- The per-terminal WSL unavailable selector returns early when no WSL host exists.

* test(wsl): the unavailable selector is quiet when no WSL host exists
2026-10-13 19:01:07 -07:00
OrcaWinandm4air e4824ce9c7 fix(ssh): a system-SSH host whose sshd refuses forwarding reaches its managed server over exec (#28408)
* fix(ssh): a system-SSH host whose sshd refuses forwarding reaches its managed server over exec

A host Orca reaches through system OpenSSH (ProxyJump/ProxyCommand, security
keys) and whose sshd runs AllowTcpForwarding no showed connected while no
managed-server traffic could pass: the ssh -L listener comes up before sshd is
ever asked for a channel, and the stdio bridge excluded system SSH.

The managed tunnel now dials once through a fresh system-SSH forward and reads
OpenSSH's own channel answer from its stderr. An administrative refusal closes
the forward and carries the tunnel over the stdio bridge on a system-SSH exec,
on the same persisted local port; a host where the bridge cannot run fails the
connect with that reason instead of reporting a dead forward as up.

Refs #24863

* fix(ssh): keep refusal logs under a quiet LogLevel; fail a forward that exits mid-check

* fix(ssh): an orphaned forward from before LogLevel=INFO still matches for recovery

* fix(ssh): confirm the persisted port is free before the stdio bridge binds it

* fix(ssh): record a forwarding refusal against the transport generation that answered it

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-13 19:00:54 -07:00
NeilandEvgenii 5517163679 fix: nest settings translation keys for language packs (#28251)
Reshape dotted translation keys into nested objects while retaining identical translation paths and values in all six locales. Keep the existing language-pack parser and its safety restrictions.

Co-authored-by: Evgenii <kumiro@me.com>
2026-10-13 18:56:10 -07:00
Neil f7235f6e2e feat(wsl-mover): accept local WSL projects on the migration wire, only in their own distro (D1b-2) (#28466)
* wip(wsl-mover): local-wsl manifest source on the wire (D1b-2)

* wip(wsl-mover): narrow SSH-only steps, cover WSL source parsing

* feat(wsl-mover): WSL host orcad accepts its own distro's projects and advertises it (D1b-2)

* fix(wsl-mover): bind a WSL source to its distro registration

* fix(wsl-mover): keep the manifest parser under the line limit
2026-10-13 18:44:38 -07:00
Neil a0f3e78815 fix(terminal): keep verified file links when another candidate rejects (#28272)
Settle terminal file-link candidates independently so one rejected path guess cannot discard verified neighboring paths. Preserve host authorization, cache scope, wrapped ranges and stale-buffer filtering.
2026-10-13 18:33:00 -07:00
NeilandMatheus Breguêz bda694181f fix(speech): release dictation window listeners after stop (#28260)
Release dictation window callbacks by their owner and identity when sessions finish or are replaced. Clean superseded, rejected and coalesced starts without removing a newer session callback or changing audio handling.

Co-authored-by: Matheus Breguêz <matbrgz@gmail.com>
2026-10-13 18:32:56 -07:00
Neil 4f5ffcb429 feat(wsl-mover): decide every piece of project state and add a losslessness check (D1b-1) (#28459)
* feat(wsl-mover): decide every piece of project state and add a losslessness projection (D1b-1)

* fix(wsl-mover): replace casts with checked reads

* fix(wsl-mover): tighten the projection after review

* fix(wsl-mover): second review round on the projection

* refactor(wsl-mover): keep the disposition tables in shared, out of the layout-writer scan
2026-10-13 18:32:33 -07:00
Neilandoliver-mee 7a5495493b fix: distinguish unreadable cookies from related import skips (#28269)
Count unreadable cookies separately from related cookies withheld by existing import safeguards. Keep import and clearing decisions unchanged and retain the legacy warning for older or invalid summaries.

Co-authored-by: oliver-mee <102673257+oliver-mee@users.noreply.github.com>
2026-10-13 18:25:06 -07:00
NeilandKalvin Mizzi 624a095993 fix(cli): warn when a workspace status has no board column (#28264)
Return optional warnings from the owning host when a requested workspace status does not match its configured columns. Preserve the requested write and older-peer behavior; show warnings separately from normal CLI output.

Co-authored-by: Kalvin Mizzi <kalvin@mizzi.com>
2026-10-13 18:21:09 -07:00
Neil 83d096b5f5 perf(lint): avoid unnecessary anti-slop scope construction (#28470)
* perf(lint): construct anti-slop scopes only for relevant expressions

* fix(ci): resolve extensionless mobile census entries as files
2026-10-13 18:19:50 -07:00
Neilandoutof-place 7b31e739d2 Fix Vercel recipe failures on stock macOS Bash (#28257)
Guard empty argument-array expansion so the bundled setup recipe runs under Bash 3.2 with unset-variable checks. Preserve exact arguments and cleanup exit behavior; normalize only test fixture line endings.

Co-authored-by: outof-place <contact@outofplace.space>
2026-10-13 18:18:50 -07:00
Neil 3f81541188 Support restarting Windows Orca as administrator (#28073)
* feat(windows): add administrator PowerShell terminal setting

* fix(settings): localize administrator terminal search keywords

* fix(windows): restrict elevated terminal host to Orca's runtime and requesting account

* fix(windows): harden administrator terminal routing, inventory and restore

* fix(build): build the Windows agent hook when electron-builder packages Windows

* fix(build): build the packaged Windows agent hook for the target architecture

* fix(windows): accept the development terminal runtime and test real elevation in CI

* feat(windows): show administrator status and restart instructions

* fix(settings): localize administrator mode search keywords

* test(settings): isolate the desktop permission section in shell picker tests

* Add Windows administrator restart and simplify Terminal settings

* Keep administrator restart silent and settings actions within narrow panes

* Keep Windows shell choices and permission guides usable at minimum width

* Prevent later restarts from replaying administrator terminal reset

* Fix CI route resolution and transient Windows fixture cleanup
2026-10-13 18:06:05 -07:00
Neil 69cbdca009 fix(packaging): include Codex TOML parser in copied runtime (#28416) 2026-10-13 18:05:05 -07:00
Neil 7b915d587e fix(orcad): keep Codex status hooks in a managed account home on a WSL host, and list its sessions (#28469)
* fix(orcad): put Codex status hooks into a client-sent account home and list its sessions

* fix(orcad): keep account-home-only hooks, check wire paths lexically, find account transcripts by id

* fix(orcad): keep Orca's Codex hook in an account home in place, as in ~/.codex

* fix(native-chat): an unlistable account home never blocks a transcript lookup

* test(codex): type the account-home hook test's hashes without a cast
2026-10-13 17:53:41 -07:00
Brennan Benson 9c113ed857 fix(native-chat): draw slash-menu command names at the chat's code size (#28461)
The command name was the only monospace text in the chat drawn at full
message size, so /electron looked larger than the surrounding text.
2026-10-13 17:51:25 -07:00
NeilandJoe Esposito ba07bd71b7 fix(terminal): retain CRLF split across output chunks (#28224)
fix(terminal): retain CRLF split across output chunks

Carry a final CR until the next chunk determines whether it is a line
ending or a redraw. Preserve normalized line content, cursor counts,
existing standalone redraw behavior, and bounded terminal tails.

Fixes #27245

Co-authored-by: Joe Esposito <joeespo@gmail.com>
2026-10-13 17:26:45 -07:00
Neil f4da672447 refactor(wsl): checked command-channel construction and the shared keyed queue (#28448)
- The WSL command channel and command loop keep the typed result of Object.assign instead of
  casting the bare Duplex through unknown to RemoteCommandChannel.
- The distro holder serializes per distro with runKeyedSerializedOperation instead of its own
  copy of the same never-rejecting-tail queue.
2026-10-13 17:25:36 -07:00
Neil 0a12d52c7c fix(wsl-host): orcad's own git sees the login shell's SSH agent, proxy and signing env (#28456)
* fix(wsl-host): orcad's own git sees the login shell's SSH agent, proxy and signing env

A WSL host's orcad starts under wsl.exe --exec sh -c, so no rc file runs and git, gh and glab
calls orcad makes miss what the user's shell exports. Adopt an allowlist of those variables from
the login shell before the terminal daemon starts. The real-distro lane now proves orcad's git
sees an rc export, and a terminal reaches Windows interop, long after the launching wsl.exe exited.

* fix(wsl-host): adopt login env before hook reconcile; honor the shell probe marker; cover git config and glab variables

* fix(wsl-host): give the login-env probe the PATH probe's 10 s budget
2026-10-13 17:25:10 -07:00
Brennan Benson ac03226b1e fix(agent-status): preserve open chat status when terminals close (#28188) 2026-10-13 17:25:03 -07:00
Jinwoo Hong 23bd828af1 fix(usage): usage requests answer authenticated-proxy challenges on a cold start (#28411)
* fix(usage): usage requests answer authenticated-proxy challenges on a cold start

Claude, Cursor, Gemini, Grok, Kimi, OpenCode Go (API key) and MiniMax (API key)
usage fetchers called Electron's net.fetch directly, which cancels every proxy
login challenge. They now go through the shared desktop HTTP client, which
answers the challenge with the configured proxy credentials (#26957), like
Codex and ZCode already do.

* test(cursor): route the wire fixture through the desktop HTTP client
2026-10-13 20:25:00 -04:00
Brennan Benson 36e1291aef perf(session-history): end a scan only when it stops making progress (#28434)
* perf(session-history): end a scan only when it stops making progress

A cold Session History scan of a large history was killed 130 seconds in
every time, however healthy it was, and every Refresh died at the same place.

The scanner child now reports progress for a running scan, at most once a
second and only when real work advanced: a directory listed, a batch of
files statted, a transcript chunk read, a candidate file parsed. Progress is
keyed to the scan's own cancel signal, so the search index or an
interactive read in the same child never extends it. The parent re-arms the
130 second window only for an active, uncancelled scan whose counter grew.

When a request deadline does fire, the child gets the shutdown frame and the
existing two-second grace to abort and save its parse cache before the kill.
Exit, disconnect, malformed-message and cancel-timeout faults still kill at
once. Startup, cancel and interactive bounds are unchanged.

* fix(session-history): keep the deadline module off child_process and let the orcad smoke skip progress frames
2026-10-13 17:17:56 -07:00
NeilandJoe Esposito 3308eb3d00 fix(process-host): accept string stdin in synchronous runs (#28225)
fix(process-host): accept string stdin in synchronous runs

Request raw subprocess output with null encoding so Node can encode string stdin as UTF-8. Preserve explicit text decoding and byte capture, with real-runner coverage for both output modes.

Co-authored-by: Joe Esposito <joeespo@gmail.com>
2026-10-13 17:16:03 -07:00
Neil 16efc33a91 Fix linked reviews on detached workspaces (#28228)
Fix linked reviews on detached workspaces
2026-10-13 17:15:21 -07:00
NeilandSamuel Vila 565e34081a Preserve significant spaces in Explorer contents search (#28222)
Preserve significant spaces in Explorer contents search

Co-authored-by: Samuel Vila <svila.smr@gmail.com>
2026-10-13 17:15:16 -07:00
Neil 52432e9952 refactor(wsl): register WSL host IPC only when the WSL host service exists (#28446)
* refactor(wsl): register WSL host IPC only when the WSL host service exists

With the internal gate closed (or off Windows) the preload exposes no WSL host API, so the
seven handlers and the settings listener were registered only to refuse. Register nothing
in that case and drop the per-handler gate checks that a live service makes unreachable.

* test(wsl): cover the settings listener, re-registration and non-Windows gate-open cases
2026-10-13 17:15:05 -07:00
NeilandEddie 03bf6b49d4 fix: refresh status bar and tray labels when language changes (#28226)
fix: refresh status bar and tray labels when language changes

Subscribe the memoized status bar to language changes and rebuild the existing tray menu after language or language-pack updates. Keep tray actions and attention state, and defer native menu updates off AppKit callbacks.

Co-authored-by: Eddie <tw199501@elf.tw>
2026-10-13 17:14:35 -07:00
Neilandoliver-mee 6920c6a002 fix(browser): reject ambiguous unscoped keypress targets (#28221)
fix(browser): reject ambiguous unscoped keypress targets

Co-authored-by: oliver-mee <102673257+oliver-mee@users.noreply.github.com>
2026-10-13 17:14:32 -07:00
NeilandMatheus Breguêz 0d1e367d05 fix(automations): format the jobs-file limit consistently (#28220)
fix(automations): format the jobs-file limit consistently

Use English number formatting in the English jobs-file limit error so non-English host locales still report 10,000 jobs.

Co-authored-by: Matheus Breguêz <matbrgz@gmail.com>
2026-10-13 17:14:05 -07:00
Brennan Benson d7f7474c5e Give Session History's rows one decision for open and resume (#28360)
A session row decided "can ▶ resume this" twice: once for the button's
disabled state and again inside its click handler, and the chat/CLI and
switch-to-pane offers were each gated inline. A second caller of those
offers would have had to copy the same conditions again.

- One projection, resolveAiVaultSessionRowChatActions, now decides every
  open/resume offer a row makes; ▶'s greyed-out state and its target come
  from the same value.
- The panel's host-scope menu and per-session resume lookups move into
  their own hooks unchanged, and the session-list request reads the
  panel's workspace through one selector.

The sidebar behaves exactly as before.
2026-10-13 17:06:06 -07:00
ed18c2c580 fix(ssh): Cancel on an SSH password prompt no longer brings it straight back (#28402)
* fix(ssh): a cancelled sign-in stays cancelled until the user reconnects

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

* test(ssh): drive the credential cancel through the IPC harness

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

* fix(ssh): an explicit server connect lifts the cancelled sign-in of its SSH host

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

* fix(ssh): the cancelled attempt may still offer a password; a successful sign-in lifts the decline

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

* test(ssh): build the managed-server link without the newer kind field

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

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-13 16:52:37 -07:00
Kelvin AmoabaandBrennan Benson 885d8a0046 feat(native-chat): show context usage in Codex chats (#28084)
* feat(native-chat): show context usage in Codex chats

Codex turn-row writes become journal revisions so the reading survives a turn end.

* test(codex): run the context-usage journal suite under Node

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-10-13 16:47:07 -07:00
Jinwoo HongandMax Hsu 8b0fa15e94 fix(notifications): remove read macOS notifications from Notification Center by pane thread (#28425)
* fix(notifications): remove read macOS notifications from Notification Center by pane thread

Orca could only take a delivered notification out of Notification Center
while main still held its Notification object, which it releases after five
minutes and loses on restart. Older notifications for a pane then stayed
forever.

On macOS each pane notification now goes in a thread named after its pane,
and reading the pane removes that thread. A native-chat alert has its own
identifier instead, and is removed only by a read that covers it. Windows
and Linux are unchanged.

Fixes #26732

Co-authored-by: Max Hsu <3001335+maxmilian@users.noreply.github.com>

* refactor(notifications): remove by pane thread inside the dismiss loop; only structured alerts by id

Only alerts posted with an identifier are removed by id, so renderer-rebuilt
ids no longer reach Notification.remove. Drops the extra pane-key accumulator,
the try/catch wrapper and redundant tests.

* docs(notifications): place the macOS removal rationale on the removal function

---------

Co-authored-by: Max Hsu <3001335+maxmilian@users.noreply.github.com>
2026-10-13 19:29:04 -04:00
Jinwoo Hong 099afc8962 fix(usage): show "Network issue" for any provider whose usage request fails on the network (STA-9799) (#28415)
A Codex usage read that failed on the network ("failed to fetch codex rate
limits: error sending request ...") was labelled "Limited" because only
Claude mapped failureKind 'network' and other providers fell through to a
rate-limit regex on the error text.

- The status bar shows "Network issue" for failureKind 'network' on every
  provider; a structured kind beats the message regex, which now only
  labels errors with no kind.
- Codex classifies app-server transport errors at the probe boundary;
  Claude, Gemini, Grok, Kimi and OpenCode Go tag requests that throw before a
  response through one shared classifier.
- A kept stale snapshot carries only the latest error's failure kind.
2026-10-13 19:10:57 -04:00