Commit Graph
15739 Commits
Author SHA1 Message Date
Kelvin Amoaba b051519599 refactor(dictation): run the desktop dictation controller on a shared session state machine (#39284)
* refactor(dictation): run the desktop controller on a shared session state machine

* test(dictation): assert session machine behaviour, not run numbering

* refactor(dictation): allow a new start from the error phase

* test(dictation): drop the error-restart case no desktop path depends on yet

* refactor(dictation): derive stop-during-start from the phase and share the start teardown
2026-11-07 04:53:39 +00:00
Neil ab6d40a9df revert: onboarding Antigravity installer (#39172) (#39282)
This reverts commit 88fa7ed2a9.

Also drops the agyInstall strings a later locale sync added to the
non-English locale files, so no orphaned keys remain.
2026-11-06 20:50:07 -08:00
Neil 121e8ba354 fix(muse): keep panes working while spawned background subagents run (#38911)
Hold the lead Stop through the shared child-work fold while UUID children
are live between SubagentStart and SubagentStop. A child that sends no hook
for 10 minutes stops holding the row. Interrupt (subscribed async, as Muse
requires), SessionStart and SessionEnd clear tracked children, and the
existing transcript-poll replay of the held Stop then reaches done.

Fixes #28090
2026-11-06 20:36:59 -08:00
Neil 4ced677eba Stop polling a Muse session log that has gone silent (#38902)
* fix(agent-hooks): bound Muse transcript polling when the session goes silent

A Muse session that ended without Stop kept its 1s session-log poll alive
until the pane closed or another hook arrived. Idle polls now probe the
owner's process after 60s without log growth (an exited owner is reconciled,
which clears the poll) and stop after 30 min of silence; the next hook
re-arms polling.

Linear: STA-8313

* fix(agent-hooks): re-arm the Muse poll after a presence probe restates the row

A live owner's suspension flip (Ctrl-Z/fg) makes the idle presence probe write
a new row; the next poll tick saw a superseded row and stopped, and no hook
re-armed it. Re-arm on the restated row, keeping the idle clock, unless a newer
hook already owns the pending poll.

Linear: STA-8313

* fix(muse): back off quiet transcript polling safely

* fix(muse): replace the presence probe with a 5s quiet-log back-off

Back a quiet Muse log off from 1s to a 5s cadence instead of probing
presence; log growth, a status change, or any hook for the session
(including ignored ones) restores 1s. Removes the idle presence probe and
the row-restatement re-arm, which Muse rows never needed: Muse hooks carry
no trusted process identity, so the probe always returned early.
2026-11-06 20:36:52 -08:00
Kelvin Amoaba 93187e7d66 feat(mobile): show a thin refresh line under the header while cached data refreshes (#39170)
* feat(mobile): show a thin refresh line under the header while cached data refreshes

* fix(mobile): start the refresh line still under unknown motion and key it by execution host

* fix(mobile): draw the refresh line only once the motion preference is known

* fix(mobile): start the refresh sweep at the left edge on each reveal

* refactor(mobile): drive the refresh line from the screen's own refetch and keep its timers in the header

* test(mobile): give the Changes content fixture the new refreshing field
2026-11-07 04:05:35 +00:00
193dd5726f Git Graph tab: all branches, commit details and file changes (#6746)
* Add Git Graph view showing all branches and commit history

Adds a full-pane Git Graph tab that visualizes the whole repository —
all local branches, remote-tracking branches, tags, merges, and HEAD —
instead of only the current branch's linear history shown in the Source
Control sidebar today.

- Add an opt-in allRefs scope to the shared git-history loader so the
  graph query enumerates --branches --remotes --tags HEAD; the default
  current-branch path is unchanged. The flag is forwarded through the
  local IPC, SSH provider, and runtime RPC boundaries so it works in all
  environments.
- New git-graph tab content type with a full-pane GitGraphPane that
  reuses the existing graph SVG/row components, virtualizes rows, and
  opens a commit's combined diff on click. Wired into the tab strip as a
  chip (activate/close/keyboard-cycle) mirroring the simulator pattern.
- Entry point: an Open Git Graph button in the Source Control commits
  header (Cmd/Ctrl-click opens in a split).

Closes #4521

Co-authored-by: Orca <help@stably.ai>

* Address review: virtual-tab guards, nav-order, truncation copy

- Treat the Git Graph (and mobile emulator) tab chip as a virtual tab:
  add an isVirtual contract to EditorFileTab/EditorFileTabContextMenu so
  rename, copy-path, copy-relative-path, and reveal-in-finder are disabled
  for tabs whose filePath is a display label, not an on-disk path. Prevents
  a rename on the synthetic 'Git Graph' path running a real file operation.
- Forward gitGraphIds through the legacy reconcileTabOrder fallback in
  getActiveTabNavOrder so keyboard tab navigation does not drop git-graph
  tabs when no split-group layout exists yet.
- Make the truncation banner explicit: 'Showing first N commits across all
  branches' (was 'of many'), across all locales.
- Tests: virtual-tab hides path actions; git-graph survives the legacy
  nav-order fallback.

Co-authored-by: Orca <help@stably.ai>

* Forward allRefs over SSH relay and web runtime paths

Two boundaries re-destructured git.history params and dropped the new
allRefs flag, leaving the repo-wide Git Graph inert (linear default-branch
history) for SSH and web-runtime worktrees:

- src/relay/git-handler.ts: the relay host's git.history handler forwarded
  only { limit, baseRef }. Add allRefs so the SSH host enumerates all refs.
  Adds an end-to-end relay test (a feature-branch commit absent from linear
  history appears only when allRefs is forwarded).
- src/renderer/src/web/web-preload-api.ts: the web preload history shim
  destructured only { worktreePath, limit, baseRef }. Forward allRefs.

Also fix a request-id race in useGitGraphHistory: bump requestIdRef before
the null-context early return so an in-flight fetch from a prior worktree
can't resolve and overwrite the 'idle' state after the worktree clears.

Co-authored-by: Orca <help@stably.ai>

* Support filtered and pinned Git history

Resolve branch and tag roots on the host, keep paging tied to the chosen revision set, and echo the applied filter across Git transports.

Co-authored-by: Matheus Breguêz <matbrgz@gmail.com>

* Add the Git Graph tab and commit details

Open the all-branch view from Source Control or the command palette, preserve older clients' layouts, and keep the sidebar HEAD-only.

* Restore files the main merge left at stale versions

The earlier merge of origin/main kept older or missing copies of 61 files
unrelated to the Git Graph (CI script tests, reliability gates, shard
timings and five terminal runtime files). Take main's version of each so
the PR changes only its own files.

* Limit pinned history to explicit graph scopes

Keep All and HEAD snapshots free of redundant ref names, and filter ref searches before nested-tag peeling. Default history requests retain unpinned paging without evicting graph tokens.

Co-authored-by: Matheus Breguêz <matbrgz@gmail.com>

* Preserve canonical placement for older graph clients

Pass graph-free record deltas through without rebuilding layouts, retain existing empty groups, and translate content creation into canonical groups while preserving hidden graph tabs.

* Retain Git Graph browsing state for the tab lifetime

Own history retrieval, selection and scroll in one renderer controller that retires on close or owner/filter changes. Accumulate parent pages before publishing, share action-time workspace navigation, serialize ref searches, and scope refreshes to the workspace.

* Apply Git Graph branch selections on picker close

Keep checkbox changes local until dismissal, then normalize and apply one chosen ref set. Allow picker reads to use a qualified host scope.

* Keep Git Graph navigation bound to its tab owner

Retain the tab's qualified owner and Git connection across workspace focus changes. Newer selections and refreshes supersede pending parent navigation without losing fetched pages.

Report stale workspace switches and let the commit menu handle empty space in the Branch / Tag cell. Cover twin-owner retention, superseded navigation and picker apply-on-close with renderer regressions.

* Translate desktop layout commands against canonical state

Resolve older desktops' visible groups, tab gaps and split paths inside the canonical mutation, including detach and cross-partition moves. Keep initial admission visibility-aware and cache graph presence for binding-only frames.

* Refine Git Graph picker and owner transitions

Discard branch drafts on Escape or owner changes, follow updated tab ownership, and report workspace lookup failures for the current owner. Keep parent navigation queued behind pending history loads.

* Drop the redundant default from the exhaustive layout command switch

* Align Git Graph with changed-code quality rules

Use typed fixtures and an event guard, keep primitive styling on content wrappers, and prune loaded-commit bookkeeping after render.

* Route Git Graph opening through the existing editor action

Use workspace record selectors for stable owner inputs and remove two
loading labels orphaned by the graph changes from all locale catalogs.

* Preserve Git Graph selection and keep row content visible

Refresh and token recovery retain loaded depth and selection in one
accumulating operation. Keep the published view until it settles, and
only fall back to the first commit after history is exhausted.

Prioritize current and open-workspace refs, reserve their markers, clip
wide graphs and retain filenames when long paths need truncation.

* Bound Git Graph refresh and allow graph column resizing

Restore the loaded depth without searching for vanished selections, retain the published result on retrieval errors, and keep newer selections visible. Allow the bounded graph column to expand to every lane through the existing resize mechanism.

* Retain Git Graph history for errors without a message

Detect failed retrieval independently of its message and cover an empty-message rejection in the existing partial-failure regression.

* Retain Git Graph widths and preserve shifted selections

Separate committed and live resize widths, retaining the chosen width for the graph tab lifetime. Allow one bounded extra history page for selections shifted by new commits, and give the resize separator a translated label.

* Preserve nullable Git Graph owner fallback

* Fix Git Graph layout imports after the RPC method move

* Route the Git Graph owner connection through the source-control connection helper

* Keep the Git Graph button idle in hidden Source Control sections

* Re-probe Git Graph support when the owner's runtime status changes

---------

Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Matheus Breguêz <matbrgz@gmail.com>
2026-11-06 19:59:47 -08:00
Neil 944ed9f2ca refactor(terminal): organize remaining pane features by owner (#39270)
* refactor: organize remaining terminal pane features by owner

* refactor(terminal): consolidate replay guard type imports

* refactor(terminal): retarget notification mocks and generated consumer import

* refactor(terminal): repair benchmark owner lookup and stale mock export
2026-11-06 19:45:07 -08:00
Kelvin Amoaba bd88cdeca8 perf(mobile): trim the page bundle back under its old budget (#39182)
* perf(mobile): trim the page bundle back under its old budget

* build(mobile): simplify the route entry anchors and zod table guard

* fix(mobile): import zod as a type in the RPC result reader
2026-11-07 03:28:04 +00:00
Brennan Benson 7270350443 fix(chat): shrink composer workspace footer and center its text on icons (#39261) 2026-11-06 19:09:50 -08:00
Brennan Benson 299a47af4a Use one shared Hide button for chat cards (#38999)
* feat(native-chat): one Hide button and toast for chat cards; PR card setting beside Changed files

The opened-PR card now uses the changed-files card's Hide control and Hidden
toast through a shared NativeChatCardHideButton, and its switch moves to
Settings > Chat UI > Appearance next to Changed files summary, where Reset
restores both. The saved key is unchanged.

* refactor(native-chat): read the opened-PR card switch through its predicate

* test(settings): Reset turns both hidden chat cards back on

* Preserve the existing opened pull request Hide aria label

* fix(native-chat): keep the gap before the PR card's Hide button
2026-11-06 19:09:12 -08:00
Brennan Benson 04bb0d0810 test(agent-launch): import resolveQueuedInitialCwd from its new home (#39269) 2026-11-06 18:52:05 -08:00
Jinwoo Hong 66cd756f91 Hooks admit the lead's final reply with its turn; the launcher records undelivered prompts and proven exits (#38239) 2026-11-06 21:46:08 -05:00
Brennan Benson f308486115 Show Source Control for folder workspaces (#38831)
* Separate editor tab placement from content ownership

* Show scoped Source Control sections for attached worktrees

* Preserve active diff refresh and validate section catalogs

* Complete folder section test worktree metadata

* Use contextual types for scoped Source Control actions

* Preserve optional scoped action parameters

* Preserve editor ownership through restore, mirroring and workspace removal

* Retire Source Control drafts only after confirmed owner removal

* Limit section work to visible expanded repositories

* Keep editor follow-up actions and bulk closes in their invoking views

* Track the consolidated editor order writer in its owning module

* Use complete typed fixtures for repair contracts

* Preserve live editor placement during layout readback

* Pass projected editor placement directly to serialization

* Read attached worktree lineage from shared graph module

* Retain inferred owner checks and scope placement retirement

* Preserve selection in untouched workspaces during purge

* Exercise editor placements in existing launch and lifecycle fixtures

* Preserve shared buffer prompts and editor strip order

* Pause inactive section context subscriptions

* Keep section recovery and file actions in the folder workspace

* Describe attached Source Control in folder workspace CLI help

* Attribute Git metadata signals once per dispatch

* Watch mounted source control sections outside the warm budget

* Resolve section watches only for served filesystem roots

* Restore restart-stable editor identity across SSH opens

* Apply section terminal routing to explicit Chat choices

* Include cross-store descendants in folder workspace panels

* Keep cross-host recovery on its existing workspace route

* Start cross-host section agents in their workspace without navigation

* Update memoized Source Control labels on language changes

* Remove unused terminal close-to-right implementation

* Suspend inactive Source Control body observers

* Retain base ref query while section observation pauses

* Cover retained base ref search across section visibility

* Resolve note menu ownership from the retained snapshot

* Preserve section agent working directories and background placement

* Keep Source Control sections active while their overlays are open

* Read current settings when Source Control commands execute

* Adapt editor and browser test stores to view subscriptions

* Preserve bound store hooks for ordinary app views

* Trust agent starts outside the placement workspace

* Trust the section worktree through transient launch ownership

* Extract editor follow-up file actions from the panel

* Fail section launches when trust capability lookup fails

Preserve explicit unsupported-host fallback, propagate lookup failures to the existing launch error path, and let the existing transport retry refresh rejected capability probes.

* Follow the moved pane launch test in web configs

* Preserve section file ownership during refresh and close

* Share tab order removal across editor close paths

* Verify companion editor close through the real store

* Close all workspace split copies for legacy mobile file requests

* Keep unmatched mobile close requests within their workspace

* Reuse the agent menu render snapshot

* Keep attached folder roots scoped to their resolved host

* Apply repository trust decisions to the launch trust owner
2026-11-06 18:25:57 -08:00
Brennan Benson d6842fdb5f Keep reopened ACP sends moving while effort options load (#39223) 2026-11-06 18:17:40 -08:00
Brennan Benson c6f07596ad Phone chat: empty Cmd/Ctrl+Enter steers the first queued message, and holding Cmd/Ctrl shows what it will do (#39215)
* fix(mobile): Cmd/Ctrl+Enter on an empty chat composer steers the first queued message

Matches desktop (#38732) in the paired web page: with nothing typed, the
chord steers the head waiting card through that card's own Steer, only
where its other way is steer.

* fix(mobile): empty Cmd/Ctrl+Enter sends the first queued card after a Stop, as desktop does

Desktop's rule (#38877) also sends the head card when no turn runs, and
goal mode keeps the empty chord inert. The queue list's handle now runs the
head card's own Send when idle, and steers mid-turn only where the chord's
other way is steer.

* test(mobile): type the queued-card handle refusal cases

* feat(mobile): holding Cmd/Ctrl in the phone chat composer shows what Cmd/Ctrl+Enter will do, as desktop does

Desktop's held-modifier preview moves into src/shared so the phone web
composer runs the same rule: Send becomes Queue, Steer or Steer queued
and the head queued card lights while the chord's modifier is held
alone on a hardware keyboard. The queue's empty-chord rule is one
function the preview and the press both read.

* fix(mobile): build the held-chord preview with the phone's own React, and offer only presses that act

The shared hook now takes its React hooks from the caller, as the shared
attachment preview does: the phone bundle resolves a different React copy
than the repo root, so importing react from src/shared broke every phone
web composer (Invalid hook call).

The preview also matches what a press does: one pill press per hold (a
double-click can't send a card after the draft went), a picked /plan-style
skill counts as a message, a head card whose own action is on its way shows
no Steer queued, and the lit state lives in the tray so a chat switch keeps it.

* fix(mobile): held-chord pill takes one click like desktop, keeps busy state per chat, lights only the head row

- The pill ignores a click whose detail is above 1, as desktop does, so a
  deliberate later click in the same hold still acts.
- In-flight card ids are kept per chat with functional updates, so a chat
  settling after a switch can't clear the current chat's record.
- The lit state is a small store only the head row subscribes to, so a hold
  re-renders one row, not the whole queue.
- The desktop preview test moves back beside the desktop wrapper it drives.

* refactor(mobile): one owner for queued-card actions, read by both the list and the held-chord cue

The queue list owned its in-flight guard per mount and the tray mirrored it
for the composer's Steer queued cue; each mirror leaked (cross-chat and
same-chat remount overwrites, an all-chats copy per change). As desktop's
queued-messages controller does, the tray now owns one action store: pending
keys per chat, admitted synchronously, released per action, published by
copying only the changed chat. The list takes those actions as a prop (it
keeps its own only when mounted alone), and the empty chord presses the head
through the same run, so the cue and the press can't disagree. The list's
imperative handle goes away.

Also replaces a type assertion on the pill's click detail with an in guard.

* test(native-chat): desktop chord-preview wrapper re-exports the hold delay its test reads
2026-11-06 18:17:24 -08:00
Brennan Benson a5b267e130 Show a spinner on the native chat fork button while the fork opens (#39255) 2026-11-06 18:16:59 -08:00
Kelvin Amoaba 8038fed16c feat(dictation): stop desktop dictation after a minute of silence (#39131)
* feat(dictation): stop desktop dictation after a minute of silence

A toggle dictation left running keeps the mic open indefinitely. Stop it
a minute after the last speech, with a toast, like the phone's tap mode.
A held push-to-talk key is never stopped; its release ends the session.

Mobile's timer moves into src/shared so both apps use the same rule, and
the shared meter now records when it last heard speech.

* fix(dictation): gate the silence stop on hold mode, as the phone does

* refactor(dictation): let the desktop silence hook own the hold-mode rule, as mobile's does

* refactor(dictation): move the capture meter into its own hook

* refactor(dictation): initialise the capture meter ref without a render write
2026-11-07 02:11:20 +00:00
Neil 7823b122f1 refactor(renderer): organize terminal pane lifecycle and layout owners (#39242) 2026-11-06 17:52:55 -08:00
Brennan Benson f712c0161d fix(native-chat): never draw a focus ring around the conversation (#39214)
* fix(native-chat): drop focus ring that boxed the whole transcript

* fix(native-chat): keep the transcript focus ring for Tab focus only

* fix(native-chat): show the transcript focus ring after keyboard focus, not clicks

* fix(native-chat): keep the transcript focus ring as it was across app switches

* fix(native-chat): never draw a focus ring on the conversation
2026-11-06 17:42:25 -08:00
Kelvin Amoaba 25b288f8b0 fix(web): dictate in the paired web client through the host (#39126)
* fix(web): dictate through the paired host in the web client

The web client's speech API was the fallback proxy and its voice settings were
local-only, so the mic stayed disabled even when the host had dictation set up.
Send audio over the speech.dictation RPCs the phone uses, and follow the host's
voice enabled/model, which the host now publishes in settings.get.

* fix(web): cancel failed starts and keep heard text on a dropped chunk

* fix(web): keep heard text when finish fails and read host voice only from settings.get

* refactor(web): tighten the web speech adapter

* fix(web): keep the focus-read count by reusing the settings already read

* fix(web): import the uuid helper from its new home

* fix(web): cancel the host dictation when finish fails

* fix(web): stop waiting on a stalled chunk backlog after 10 s

* fix(web): match feedAudio's cap result now that the desktop bridge reports it
2026-11-07 01:31:58 +00:00
Kelvin Amoaba 9dacf1e6db fix(mobile): make dictation start reliably on a slow or sleeping desktop (#39142)
* fix(mobile): keep speech said while dictation starts and fail fast on a dead host

* fix(mobile): let a tap or long-press end dictation while it starts

* fix(mobile): wait for the host's cancel before the next dictation starts

* fix(mobile): let a start wait out the previous start on the host too

* refactor(mobile): track host dictation work per client, not per screen

* test(mobile): read the start's dictation id without a cast

* fix(mobile): finish instead of cancel when a held mic is released while starting
2026-11-07 01:25:34 +00:00
Neil e841431af9 feat(web): use E2EE v2 for the pairing token on plain-http pages (#39220)
* feat(web): use E2EE v2 for the pairing token on plain-http pages

Plain-http pages cannot hold a device key, so their sockets always sent
the pairing token over the legacy framing. They now send it over E2EE
v2 (with the usual client capabilities) to hosts that accept it:

- A reconnecting socket tries v2. A host that refuses the v2 hello, or
  refuses capabilities on v2 token auth, is noted and the socket
  reconnects with the legacy framing, as the device-key migration
  already does for older hosts.
- After a v2 token sign-in succeeds, the host's pinned key is kept in
  local storage and every socket to it uses v2; it is never downgraded.
- One-shot and Crew sockets, which cannot absorb a refused attempt, keep
  legacy framing until the host has proven v2. Keyed (https) pages are
  unchanged.

* feat(web): recover a latched host that was downgraded, without a legacy downgrade path

A plain-http page that had signed in to a host over v2 kept retrying v2
forever if the host was later downgraded below v2 token auth. When a
latched host refuses v2, the next socket now only reads the host's status
over legacy framing (keyed to its pinned key), the way a key migration
socket works. If the host's own status lacks
pairing.e2ee-v2-token-capabilities.v1, the latch is released and the page
reconnects over legacy; otherwise it stays on v2. A refusal faked on the
path cannot produce the host's answer, so it cannot force legacy.

The token framing logic moves to web-token-framing.ts.
2026-11-06 17:24:34 -08:00
Kelvin Amoaba 1740dc3e34 fix(dictation): space dictated text from existing text and copy what can't be inserted (#39111)
* fix(dictation): space dictated text from its neighbours and keep text that can't be inserted

* fix(dictation): don't report a contentEditable insert as landed when nothing was inserted

* refactor(dictation): read caret neighbours per block and trim the spacing helpers
2026-11-07 01:24:19 +00:00
Neil 0efe201fbf feat(remote-runtime): use E2EE v2 for desktop and CLI connections to remote runtimes (#39218)
* feat(remote-runtime): use E2EE v2 for desktop and CLI connections to remote runtimes

Desktop connections to remote runtimes (orca serve, orcad, managed SSH,
browser bridge) and the orca CLI now use E2EE v2 framing for hosts that
support it, and fall back to the legacy framing for hosts that do not.

- One-shot requests try v2 first. A host that refuses the v2 hello
  (hosts from before v2) or refuses capabilities on v2 token auth gets
  one transparent legacy redial; nothing but the handshake is sent before
  authentication, so the redial is safe.
- After a v2 sign-in succeeds, the host's pinned key is remembered in the
  profile (shared by the app and the CLI). Every connection to that host,
  including long-lived sockets, then uses v2 and never falls back.
- Long-lived sockets keep legacy framing until a host has proven v2, so
  they never see an older host refuse a v2 hello.
- Hosts accept clientCapabilities on direct v2 token auth, so desktop and
  CLI clients keep their capability negotiation on v2. Relay token auth
  stays exact.

* feat(remote-runtime): recover a latched host that was downgraded, without a legacy downgrade path

A host this profile had signed in to over v2 was refused for good if it
was later downgraded below capability-carrying v2 token auth. Hosts now
advertise pairing.e2ee-v2-token-capabilities.v1. When a latched host
refuses v2, a one-shot request asks the host over legacy framing (keyed
to its pinned key, so only the real host can answer) for status.get:

- no capability: the host itself says it was downgraded; the latch is
  dropped and the request goes ahead over legacy;
- capability present, or no answer: the request fails with an error
  that says to update the host or check the network path. Only the
  read-only status check ever ran over legacy.

A refusal faked on the path cannot produce the host's answer, so it
cannot force legacy framing for a latched host.
2026-11-06 17:14:56 -08:00
Neil 89fb7c8abe feat(settings): show which paired devices still sign in with older encryption (#39222)
The host now records, per paired device, whether its most recent
sign-in used the legacy E2EE framing (set on a legacy sign-in, cleared
on a v2 one). The field rides on the existing deferred lastSeen write,
so it adds no extra registry saves, and older builds keep unknown row
fields as they are.

Settings shows a notice under such rows:
- Mobile paired devices: "Uses older encryption. Update the Orca app on
  this device."
- Runtime access links: "Last used with older encryption. Update Orca
  where this link is used." (one link can be shared by several clients)

Local only: nothing is sent anywhere, and no connection is refused.
2026-11-06 17:06:25 -08:00
Neil 090945007b Ask once per repo before pre-trusting agent folders (#38946)
* Ask once per repo before pre-trusting agent folders

* Route the trust prompt's repo lookup by the launch host; use dialog primitives as designed

* Bound the first-launch trust hold and honour the answer for the launch

* Move agent folder trust settings into their own settings group
2026-11-06 17:02:46 -08:00
Neil 3c866a31db feat(serve): refuse public source addresses on orca serve unless allowed (#39207)
* feat(serve): refuse public source addresses on orca serve unless allowed

`orca serve` listens on every interface. It now accepts connections from
loopback, private LAN (10/8, 172.16/12, 192.168/16, 169.254/16, fc00::/7,
fe80::/10) and Tailscale (100.64/10, fd7a:115c:a1e0::/48) sources, and drops
public-source TCP connections before HTTP parsing or the WebSocket upgrade.

Public sources stay accepted when:
- --allow-public is passed (orca serve, orcad, serve service install);
- a paired device has authenticated from a public address (persisted flag);
- --pairing-address is a hostname or a public IP literal;
- any device paired before this server first ran the check is still paired.

The startup log states which rule applies; the first refused public address
each hour is logged with the flag name. Desktop listeners and Orca Relay are
unaffected.

* feat(serve): accept on-link public addresses and add --refuse-public

A public source inside a prefix of one of this host's own interfaces (os.networkInterfaces, re-read at most every 5 s) is a same-network peer, such as a LAN client using global IPv6, and is now admitted. Prefixes wider than /16 (IPv4) or /48 (IPv6) are ignored.

--refuse-public (orca serve, orcad, serve service install) refuses public addresses regardless of grandfathered pairings, a remembered public connection or the pairing address; it cannot be combined with --allow-public.
2026-11-06 17:02:15 -08:00
047e113783 feat(cli): pair clients with a running Orca via orca pair (#36469)
* feat(cli): pair clients with a running Orca via `orca pair`

A headless Orca could only print a pairing offer at startup, so pairing a
phone with `orca serve service install` meant stopping the service. `orca
pair` asks the running runtime for an offer over the owner's local socket
through a new `pairing.createOffer` method (pairing-admin, never granted to
paired clients or phones). It reuses the desktop's offer logic, now shared
by the Settings IPC handlers: mobile offers stay LAN-only unless --relay,
and the first network pairing widens a loopback bind as the desktop does.

Also honor an explicit ORCA_SERVE_RUNTIME=orcad on packaged macOS. Electron
serve needs a desktop login and aborts in an SSH session with nobody logged
in; orcad is plain Node and serves there, at the cost of no in-app updater.

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

* refactor(cli): move the serve-restore and pair handler groups out of the manifest

handler-group-manifest.ts was already at the 300-line limit on main, so
adding the pair group failed lint. Group the two serve/pairing entries in
their own file, as jira, linear and reference already are.

* test(mobile): pin the desktop pairing IPC handlers' replies and runtime calls

Characterizes mobile:getPairingQR and mobile:getRuntimePairingUrl across
every branch (explicit and default address, LAN-only with no address,
Relay without an address, relay failures, this-computer reach, widening
and its failure, unavailable offers). The same file passes unchanged on
main and on this branch, so moving the offer logic into
pairing-offer-requests.ts did not change the desktop path.

* test(mobile): type the QR stub instead of casting it

---------

Co-authored-by: Office exitnote <h@Offices-MBP.lan>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
2026-11-06 17:01:32 -08:00
Neil f46103acc9 fix(pairing): a registry save can no longer put back a device revoked while it was writing (#39192)
Device registry saves re-read the file, merge and rename. A save that read
the file just before another writer removed a device (an admin script, the
desktop, orcad) and renamed just after would write the revoked row back, and
its token would keep working. Saves now publish only if the file is still
exactly what they read (compare-and-swap via the staged write's last check),
and otherwise re-read and re-merge. A file caught half-written by a
non-atomic writer is re-read after a short wait instead of being treated as
corrupt and overwritten.
2026-11-06 16:59:56 -08:00
Brennan Benson ad4bb83fa1 Open the chat model picker without walking every worktree (#38660)
* fix(native-chat): list Claude and Codex models per account, not per folder

Opening the chat model picker rebuilt every worktree row to work out which
folder the read was for, because Claude and Codex model lists were keyed by
folder. Only the default model/effort depends on the project, so lists are now
keyed by account, WSL and the resolved CLI invocation and listed from the
account folder; the CLI is still resolved where the chat would launch. The
selected workspace is derived directly from its selector, and its own project
settings decide whether the account's default model and effort are shown.
The worktree projection and its cache are removed.

* test: merge duplicate node:os import

* fix(native-chat): list from Orca's probe folder, keep cwd-relative CLIs in their folder, one project-settings pass

- List Claude/Codex models from Orca's empty probe folder so a home folder's settings never read as another account's project settings.
- A command line naming files relative to the chat's folder lists there and is keyed to it.
- Read project settings once per binding for both the model and effort answers; per-model effort-only Claude settings keep the account model.
- A running chat's catalog withholds the account's configured choice where its project may pick its own.
- Bound the picker's workspace resolution so a stalled floating folder cannot hold it.

* fix(native-chat): catch quoted ./ arguments, list prewarm from Orca's probe folder, fake-timer the shared-listing test

* perf(native-chat): parse each project settings file once for both model and effort

* test: catalog e2e expectations list from Orca's probe folder; phone saturation keys per folder

* fix(native-chat): share a listing while any reader wants it; list custom command lines in the chat's folder; resolve a floating folder within the read's own wait

- A model listing shared by several folders keeps running while any reader still waits on it, queued or running, and stops (never starts) once the last one gives up.
- A Claude or Codex command line with user arguments or a relative command lists in the chat's folder; Claude reads only user settings there so project settings never narrow the account's list.
- A waiting read resolves a floating folder within its own deadline; a read that doesn't wait returns at once and starts the listing when the folder resolves.

* fix(native-chat): every reader of a shared model listing owns it until its own deadline; floating reads list their folder within their own budget

- Readers of the same request share one lookup that lives until the last reader's deadline, so later readers neither repeat CLI/settings setup nor lose the listing when an earlier one ends or is cancelled.
- A shared listing's requests carry no single reader's deadline: the provider timeout bounds them and the last reader leaving stops them.
- A floating picker read that doesn't wait starts its folder's listing under its own deadline and retirement, and says the listing is in progress so the picker follows with a waiting read.

* fix(native-chat): a shared model listing survives the chat that started it; unreadable workspaces list as the account

- Each reader of a shared lookup fences its own request, so one chat closing never ends another chat's listing; the lookup's budget is the latest deadline among readers still waiting, and its listeners are released when it ends.
- A picker for a workspace this host can't inspect (remote, WSL, ambiguous, stalled) lists as the account does from Orca's probe folder, never from wherever Orca itself runs.

* refactor(native-chat): readers of one model request share an ownerless setup and each own only their claim and the listing

- Same-request readers share one setup (folder, account, CLI scope and the folder's own default settings) that belongs to no reader: its budget is fixed when it starts, it ends with the service or the agent's retirement, and it is reused while it runs and while the listing it found runs.
- Each reader keeps its own claim, deadline and cancellation; a binding holds scope facts only and never cancels another reader's claim or a shared listing, so one chat closing never ends another chat's listing.
- A floating picker read that doesn't wait no longer asks to list the unresolved folder.

* fix(native-chat): shared catalog setups end on their budget or a workspace change, readers settle on cancel, and defaults and account seeding happen once

- A shared setup that runs out of its budget, or whose workspace or agent is retired, answers nothing at once and leaves the cache, so a reader with time left resolves afresh (a moved folder never reuses the old path).
- Cancelling a reader, or stopping the service, ends its wait at once.
- A folder's own default settings apply to its binding whenever they arrive; the account list is seeded once per shared setup, not per reader.
- A reader shares a setup only with readers whose own chat agrees on whether it may list.

* fix(native-chat): a settled catalog setup serves its whole listing, and a folder's default settings stay bounded and reach every reader

- A setup that has answered stays reusable while the listing it found runs, past its first reader's deadline, until it is ended or its agent retired, so later readers don't redo CLI and project-settings work.
- The setup's budget holds until the folder's default settings have answered too; settings that outlive it are read again while a reader still has time, and apply to the request's current binding whichever reader asked.

* refactor(native-chat): the catalog service owns each model refresh within one fixed budget; pickers only wait

A refresh (account, folder and CLI resolution, the folder's default settings,
admission and the listing) now belongs to the catalog service with one 30s
budget fixed before setup. Readers join it and wait only until their own
deadline or cancellation; none extends, shortens or ends it. Expiry withdraws
queued listings, stops the process and drops late answers. Defaults apply only
to the refresh's own binding, so a moved folder's late settings never describe
the new folder. A waiting read that used its whole budget is final.

Replaces per-reader claims, shared setups and reader-counted listing demand.

* fix(native-chat): retiring an agent or a chat's folder ends its catalog refresh at once, and a shared setup never rechecks the chat that started it

- Retiring an agent ends a refresh still stalled in setup, so a waiting picker
  answers at once instead of after the refresh's budget.
- A listing that runs in a chat's own folder stops, and its answer is dropped,
  when that folder's command context retires; listings shared across folders
  stay independent.
- Scope resolution uses only the passivity its readers had when they asked,
  so another chat's record going away mid-setup no longer turns it saved-only.
- A reader that stops waiting unsubscribes from the listing at once.

* fix(native-chat): one workspace's retirement no longer stops a catalog listing another workspace shares

A listing is keyed by the folder it lists in and the CLI invocation, so moving
one workspace never makes it describe the wrong folder; stopping it there
cancelled a listing another workspace on the same folder still needed. Agent
retirement and shutdown still stop listings at once.

* test(native-chat): a folder change keeps another workspace's shared catalog listing, and never answers the new folder with the old one's

* test(native-chat): a folder agent's listing survives another workspace on its folder moving

* test(native-chat): type the shared-setup harness's held stages without casts

* fix(native-chat): Claude and Codex pickers show the account's default in every folder

The picker no longer reads a repo's own model or effort settings for Claude and
Codex: they show the account's list and default everywhere, and the CLI still
applies the repo's settings at launch. OpenCode, OMP and Pi keep their project
checks. The model/effort split and the peek withholding go back to main's shapes.

* fix(native-chat): a Codex listing in the chat's folder never loads that folder's project config

A Codex command line that may name files in the chat's folder still lists there,
but now marks that folder and its parents untrusted for the listing, so a repo's
own model catalog can't replace the account's. The exported project-settings
check also answers no for Claude and Codex.

* fix(native-chat): Codex lists models only from Orca's probe folder

A Codex listing no longer starts in the chat's folder for custom or relative
command lines, so a repo's own config can never change the picker's list, as
other Codex hosts do. Chats still launch with the user's command line and folder.
Drops the untrusted-folder override.

* fix(native-chat): Codex listings never read project config above Orca's folder

With no project-root markers, Codex reads project config only from the listing's
own folder, so a trusted repo that contains Orca's profile can't supply the
account's model list.

* docs(native-chat): say what Codex listing isolation covers, and what managed config can still restore
2026-11-06 16:59:49 -08:00
NeilandAnddy Sahir Agudelo 5edd3346c0 feat(settings): add Antigravity IDE to the Open In Apps presets (#39237)
* feat(settings): add Antigravity IDE to the Open In Apps presets

Launch Google's Antigravity IDE through its bundled `antigravity-ide`
launcher (`agy` is the separate Antigravity CLI agent), and make the
setting searchable by "antigravity".

Supersedes #5636.

Co-authored-by: Anddy Sahir Agudelo <AnddyAgudelo@users.noreply.github.com>

* chore(i18n): add German Antigravity IDE entries for parity

---------

Co-authored-by: Anddy Sahir Agudelo <AnddyAgudelo@users.noreply.github.com>
2026-11-06 16:59:09 -08:00
Brennan Benson 5d1c2a8910 Honor agent defaults when setting up later (#38028)
Set up later saves the applicable visible Chat/Terminal and agent choices, preserves unvisited saved defaults and Blank, then opens the default agent through ordinary workspace activation in the captured workspace and host. First use and Help replay share the behavior, and failed saves remain retryable.

Independent reviews accepted the main-parent update. All 27 focused tests and final-head CI passed, including twelve unit shards, static/typecheck, changed E2E and packages. Real Claude/Codex Chat and Terminal, saved Blank and graceful-restart QA carries across the main merge. The final local changed-check prerequisite timed out in the resource queue before checks ran; this limitation is documented in the PR.
2026-11-06 16:53:48 -08:00
Brennan Benson 885ee1ee84 perf: avoid repeated publisher scans during workspace purge (#39232)
* perf: index workspace purge publishers by host

* test: construct host-qualified purge targets
2026-11-06 16:45:59 -08:00
NeilandJose Espana 1edd32de9a feat(runtime): cap unauthenticated WebSocket connections per source and record client source class (#39206)
The runtime WebSocket listener bounded connections only globally (256 TCP,
128 WebSocket), so one host holding unauthenticated sockets could take every
slot. Each source (one IPv4 address, or one IPv6 /64) may now hold at most 64
connections that have not authenticated; loopback is exempt, and a source
that already holds an authenticated socket is never refused.

Authenticated direct connections now record the class of their source
(loopback, private, tailnet, public) on the device row through the existing
deferred last-seen write, and the first authenticated public-source
connection persists allowPublicSources in userData. Nothing reads it yet.

Co-authored-by: Jose Espana <jose.espana@perseusoft.tech>
2026-11-06 16:43:54 -08:00
Jinjing 1f1eb76593 fix(i18n): translate recent desktop and mobile copy (#39233) 2026-11-06 16:33:34 -08:00
Brennan Benson 46575a257f Open fresh desktop profiles in a ~ folder workspace (#37898)
Fresh desktop profiles start in an ordinary folder workspace at the OS home directory, with the initial repo group named ~. Onboarding Continue saves the selected default agent and launches it in that workspace through the normal activation path. Existing profiles and user-renamed groups remain unchanged.

Independent reviews and prior real Claude/Codex QA are complete. Refreshed-head CI is green, including all unit shards, changed E2E, static/typecheck, packages, persistence and WSL. All 54 focused regression tests passed; three bounded local check timeouts are documented in the PR.
2026-11-06 16:29:36 -08:00
Neil 93589dc6f8 fix(shell): open linked scripts in the editor and reveal programs instead of launching them (#37930)
* fix(shell): show linked scripts in the editor and reveal programs instead of launching them

Markdown file links that would run code no longer go to the OS default
app. Main inspects the target (following symlinks, checking every name
in the chain plus the executable bit and the file header): readable
scripts open in an Orca editor tab, binaries and bundles are revealed in
the file manager, and everything else opens as before.

* fix(shell): reveal linked archives instead of unpacking them

Zip, tar, gzip, bzip2, xz, zstd, 7z, rar, cab and zip-based packages
(.war, .ear, .apk, .ipa, .xpi, .crx, .vsix, .nupkg) are revealed in the
file manager. Archive headers are also treated as binary content, so an
archive under a script name or an extensionless executable name is
revealed rather than shown as text.
2026-11-06 16:25:12 -08:00
AdolaniumandNeil b10f90c8f9 fix(files): restrict staged saves to their owner (#36343)
* fix(files): restrict staged saves to their owner

* fix(files): remove a temp restriction cannot keep

writeSecureFile publishes the file before it reports that owner-only permissions failed, and nothing else tracked it.

* fix(files): reject a staging directory whose mode does not stick

chmod can succeed on DrvFs without making the directory owner-only. Non-Windows setup now refuses unless the directory mode is 0700.

* fix(files): keep staged saves working where owner-only modes don't stick

Create the temp owner-only at creation (wx + 0o600) instead of through
writeSecureFile, and treat a staging folder whose mode or ACL cannot be
tightened as a one-time warning rather than a save failure, so network,
FAT and some WSL mounts keep saving.

---------

Co-authored-by: Neil <neil@stably.ai>
2026-11-06 16:24:28 -08:00
Neil bb5f37c916 feat(pairing): unscanned desktop pairing codes expire (QR 10 min, link 60 min) (#37957)
* feat(pairing): unscanned desktop pairing offers expire (QR 10 min, link 60 min)

Headless serve/orcad mint offers without a TTL, so they never expire.
Paired rows and rows without offerExpiresAt are unaffected.

* feat(pairing): refresh the mobile QR before it expires and show runtime link expiry

The QR re-mints a minute before expiry while visible; an unused runtime
link shows its expiry and, once expired, is replaced by a notice. Web
clients rejected while pairing now suggest asking for a new link.

* feat(mobile): say a refused pairing code expired or was replaced

A first pairing the desktop refuses (direct or Relay) now asks for a new
code instead of showing the raw transport error.

* refactor(pairing): replace type assertions in code-refusal detection and tests

* test(mobile): re-record pairing race golden for refused-code error

* fix(pairing): keep pairing codes valid across sleep and stale registry writers

- QR refresh rechecks the wall clock every 15 s and on window focus, so a
  computer waking from sleep never keeps showing an expired code.
- A registry save from a process with an older copy can no longer reset a
  paired row back to unscanned (which would let its offer expire).
- The QR caption says how long a copied code works.

* test(pairing): annotate the IPC handler stub cast
2026-11-06 16:23:44 -08:00
Neil a745884b17 fix(automations): store the approved setup hash and skip setup when a run's branch differs (#37979)
* fix(automations): store the approved setup hash and skip setup when a run's branch differs

Saving an automation with setup run records the hash of the content the user
approved; headless, chat and desktop-dispatched runs send it with the create,
so a branch whose setup differs skips setup (logged on the host). Writes that
set the decision without a hash (CLI --setup run) and older automations keep
today's behaviour.

* fix(automations): show on the run when setup was skipped because the branch differs

Runs record setupSkippedReason 'setup-changed' when the new workspace's
orca.yaml setup differs from the hash saved with the automation (headless,
chat and desktop-dispatched runs), and the run page says so with what to do.
The note survives the run's completion update.

* test(automations): pass run history to the setup-skipped run details test
2026-11-06 16:23:20 -08:00
Kelvin Amoaba 768df33d84 fix(dictation): handle cloud dictation's 10-minute cap, stalled uploads and bad API keys (#39114)
* fix(dictation): bound cloud uploads, stop at the 10-minute cap, check the OpenAI key on save

* refactor(dictation): share the OpenAI error reader
2026-11-07 00:22:24 +00:00
Kelvin Amoaba ea32ccb0ec fix(dictation): show Listening while the speech model loads, and link start toasts to Voice settings (#39136)
* fix(dictation): say Listening once the mic is recording, and translate start toasts with an Open Settings action

* refactor(dictation): derive the indicator label from the listening flag
2026-11-07 00:21:33 +00:00
Kelvin Amoaba a3a0e68380 fix(packaging): require macOS 13.4, the minimum the on-device speech engine runs on (#39135)
* fix(packaging): require macOS 13.4, which the bundled speech engine needs

* fix(updater): keep the macOS requirement title clear of the close button

* fix(packaging): check out the Darwin floor table for adhoc builds and raise the cask floor

* refactor(updater): reuse the parsed Darwin floor for the macOS requirement

* fix(updater): use a margin to keep the macOS requirement title clear of the close button

* fix(updater): say the macOS requirement comes from Electron and the speech engine
2026-11-07 00:18:50 +00:00
Kelvin Amoaba efc27ee8ee fix(terminal): finish a paste in its terminal when dictation or overlays move focus mid-paste (#39112)
* fix(terminal): finish a paste when focus moves to a non-input control mid-paste

External dictation tools and overlays can leave focus on a control outside the pane while the clipboard read is in flight, and the paste was thrown away as stale. Only cancel when another paste target (terminal, input, frame) took focus.

* fix(terminal): count Monaco and rich editors as paste targets

* test(e2e): a paste survives focus moving to a non-input control mid-read

* refactor(terminal): one focus rule for terminal and popout paste

* test(e2e): scope each focus-move script so later cases actually move focus
2026-11-07 00:18:12 +00:00
Kelvin Amoaba 58c739b75c fix(speech): add a dictation language setting that Whisper, SenseVoice and OpenAI use (#39102)
* fix(speech): send the dictation language to Whisper, SenseVoice and OpenAI

The voice language setting was stored but nothing read it, and its 'en' default never came from the user. Replace it with dictationLanguage (default auto), pass it to every engine that accepts one, and add a picker that says when the selected model ignores it.

* test(settings): add dictation language fixtures

* refactor(speech): keep each model's dictation languages on its catalog entry

Language support belongs to the model, not its engine type, so an English-only Whisper build can't be sent another language. Also reuse SettingsRow and cache Intl.DisplayNames.

* fix(speech): let SenseVoice users pick Cantonese

* test(speech): drop type assertions from the new language tests
2026-11-07 00:17:37 +00:00
Brennan BensonandWooseong Kim ef68fa9bb3 Shrink oversized pasted images to fit instead of failing with "Clipboard image is too large" (#39050)
* fix(clipboard): shrink re-encoded paste images to fit size limits instead of rejecting

Pasted images are re-encoded as PNG before saving, and the size cap was applied to
that output, so a retina screenshot or copied photo that grew during re-encoding
failed with "Clipboard image is too large". Downscale until it fits at every
re-encode site (desktop clipboard image, Windows copied files, renderer conversion).

Fixes #37855

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>

* fix(clipboard): keep image copies full size; only pastes shrink to fit

Copy image shares the PNG conversion with paste. Copies keep rejecting an
oversized PNG instead of silently copying a smaller image, and canvas
downscales use high smoothing quality.

* test(clipboard): match macOS paste tests to downscale-to-fit

Image mocks gain resize; the over-pixel-cap rejection case is removed since
those pastes now downscale (covered by the handler regression test).

* fix(clipboard): let pasted non-PNG images above the pixel cap downscale in the renderer

* test(clipboard): drop web paste test for the removed over-pixel-cap rejection

* fix(clipboard): let pasted PNGs above the pixel cap downscale instead of rejecting

* fix(clipboard): route web PNG pastes through the fit-to-limit converter

---------

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
2026-11-06 16:11:45 -08:00
Brennan Benson d84abfb4f5 Offer terminal sign-in recovery in native agent chats (#39012)
* Offer terminal sign-in from native chats and retain refused worker tasks

* Handle sign-in terminal failures and complete failed-start cleanup callers

* Read retry task identity from its dispatch context

* Assert retained transcript identity through the bounded worker read

* Remove obsolete private structured retry teardown assertion

* Use real signed-out Pi and OMP error frames in regression coverage

* Route chat sign-in through explicit host terminal creation

* Keep paired agent launch owner explicit in the tightened terminal contract

* Keep focus baseline pruning limited to the removed reads

* Keep terminal login recovery available for unresolved model notices

* Keep the Grok diagnostic assertion scoped to its refused delivery

* Limit chat sign-in subscriptions to eligible failure rows

* Pass the execution host in terminal creation fixtures

* Scan Pi OAuth failures linearly and verify shared sign-in bounds

* Assert failure metadata and diagnostic routing without translated copy

* Bound diagnostic matcher steps at the Pi failure boundary

* Type the diagnostic matcher receiver explicitly

* Check sign-in button elements with a DOM type guard

* Recognize Pi credential failures and keep one recovery action

* Keep Pi sign-in diagnostics behind failure details

* Verify recovery action changes while refusal wording stays stable

* Use the recorded refusal sentence in the recovery fixture

* Type the recovery fixture as a submission rejection

* Make chat sign-in actions large primary controls

* Reuse primary action styles without duplicate notice subscriptions

* Keep translated primary chat actions within narrow notices
2026-11-06 16:06:14 -08:00
Jinwoo Hong 555bd481bc Let multi-repo folder workspaces parent and nest workspaces, including on other machines (#38904)
Multi-repo folder workspaces can parent workspaces on this machine and on paired servers, and their children nest under them in the sidebar and on the phone. Task parents are inferred at creation instead of on every lineage refresh.
2026-11-06 19:03:51 -05:00
Brennan Bensonandlydiassssz 548ba48c32 Upload images before publishing GitHub drafts (#38928)
* Upload GitHub images when pasted or dropped

Keep image bytes out of issue and review bodies by uploading from the owning composer and inserting hosted attachment URLs.

Co-authored-by: lydiassssz <60514233+lydiassssz@users.noreply.github.com>

* Preserve GitHub image intake across editor and thread states

* Keep image placeholder boundaries and ordinary HTML paste intact

* Match the native replace overload in the image projection fixture

* Use typed element queries in the image intake fixture

* Narrow textarea elements at the fixture boundary

* Show failed GitHub draft images as filename links

* Keep GitHub draft attachment links current across updates

* Supply the repository address in the attachment preview fixture

---------

Co-authored-by: lydiassssz <60514233+lydiassssz@users.noreply.github.com>
2026-11-06 16:03:20 -08:00
Jinjing a5da3ed41e Highlight already open structured agent panes (#39219) 2026-11-06 15:49:44 -08:00