Commit Graph
56 Commits
Author SHA1 Message Date
Kelvin Amoaba 0b8e94aa5c feat(speech): run GGUF models on transcribe-cpp with a CPU fallback (#39326)
* feat(speech): run GGUF models on transcribe-cpp, falling back to the CPU

The speech host child can now load a second engine. A model whose files
are GGUF runs on transcribe-cpp (Metal on Apple silicon, Vulkan on
Windows and Linux, CPU otherwise); everything else stays on sherpa-onnx,
which remains the only engine on Intel Macs.

A GPU that fails to load, errors, stalls for 25 s or crashes the process
moves the dictation to a CPU-only process (TRANSCRIBE_BACKENDS=cpu) that
replays the audio the GPU had not decoded yet, so no words are lost or
repeated. A GPU that crashed or would not load is remembered per
transcribe-cpp version and OS release, and voice.speechGpu = 'off' keeps
it on the CPU.

Packaging copies transcribe-cpp, koffi and the target's native package
into resources/node_modules outside asar, fails when a target's package
is missing, and ships the MSVC runtime beside transcribe.dll on Windows.

No catalog model is GGUF yet, so nothing runs on transcribe-cpp until
the catalog change that follows.

* fix(speech): keep the speech queue running after a failed task, and drop audio frames after a discard

* fix(speech): keep the engine's file reads off the main thread's hot paths

The engine lookup and the GPU failure marker are read once per process
instead of on every dictation start and mobile catalog request, the marker
is written asynchronously, and the child reads the hotwords file with
fs/promises. The baseline moves the existing engine presence checks from
stt-worker-paths to the shared package table and adds the once-per-process
marker reads.
2026-11-09 16:37:03 +00:00
ggpepperstechandKelvin Amoaba 041b542a93 feat(cli): list and reload installed browser extensions (#39613)
* feat(cli): list and reload installed browser extensions

* docs(browser): document extension command contracts

* docs(browser): describe extension test fixture factory

* refactor(cli): validate extension reload once and trim list output

The RPC params schema owns the ID-or-path and ID format checks. Reload
drops --profile and the constant scope field, since it always reloads
every profile where the extension is enabled. List no longer returns
icon data URLs or permission lists.

* test(browser): keep five extension reload tests at the command owner

* fix(cli): route orca extension through the packaged Linux launcher

* fix(browser): refresh Settings after an extension reload from the CLI

* test(browser): type the reload fixture list without a cast

---------

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-11-09 02:08:29 +00:00
Neil 51fcb15154 refactor(browser): organize automation, cookies and session policy owners (#40002)
* refactor(browser): give agent-browser implementations concrete owners

* refactor: give cookie import and storage policies concrete owners

* test: target the concrete Chromium registry edge in cookie fixtures

* refactor: organize browser profile identity and permission owners

* refactor(browser): import input and cookie types from defining owners
2026-11-08 16:04:41 -08:00
Neil 3a0e9a27d0 refactor: organize backend runtime by defining owner (#39625)
* refactor(main): organize runtime implementation owners

* test: retire wall-clock CLI title recovery case

* refactor: route new folder mutation to runtime store owner

* test: import folder mutation store contract from its owner
2026-11-07 22:24:34 -08:00
Brennan Benson fd334f1e99 Show CI results in the chat that opened or pushed to a pull request (#39020)
Shows CI result lines in the chat that opened or pushed to a pull request. They're hidden in the terminal overlay and on the phone until the phone app gets the setting.
2026-11-07 16:34:21 -08:00
OrcaWinandOrcaWin 4243da77d5 test: remove 414 low-value tests (#39539)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-07 08:41:24 -08:00
OrcaWinandOrcaWin 5c5c7ca235 test: remove 283 low-value tests (#39533)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-07 08:11:30 -08:00
Kelvin Amoaba 11cad93421 fix(speech): run on-device dictation in its own process so an engine crash or hang can't take Orca down (#39297)
* fix(speech): run the on-device recognizer in its own process

A native crash in sherpa-onnx took Orca down with it, and a decode stuck
in native code could not be stopped. The recognizer now runs in an
Electron utility process (a forked child under orcad), is killed when a
stop or cancel goes unanswered, and a dead process ends the dictation
with an error instead of a silent stop.

* fix(speech): let a loading model finish when its dictation is canceled

* fix(speech): never leave a speech child behind or crash main on its fatal error

* refactor(speech): drop the teardown message now that terminate kills the process

* fix(speech): fork the orcad speech child through process-host with a typed request
2026-11-07 05:28:41 +00:00
OrcaWinandOrcaWin 480842c82a test: remove 1186 low-value Git and IPC tests (#38812)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-05 20:00:50 -08:00
Neil ebd724b2f8 refactor(main): group browser runtime code by defining owner (#38576) 2026-11-05 07:44:10 -08:00
NeilandBrennan Benson 688adc4b4c fix(serve): give headless hosts the services the desktop window path started (#37633)
Headless orca serve (orcad and Electron --serve) was missing services that only
the desktop composition or a window attach started.

- orcad now wires the orchestration environment transport, so a headless Run
  home can place workers on its paired servers again. The transport moves into
  a shared factory both hosts use. Its routing graph broadcast to renderers
  through BrowserWindow directly; that now goes through the desktop-surface
  port, so the graph runs on plain Node.
- orcad also resolves paired-device names, which was a declared gap.
- Usage polling runs from the account composition every host builds. Polls
  fire while someone watches: the focused desktop window, or a live
  accounts.subscribe stream. A headless host's remote readers now get the polls
  that stream promises, and unwatched hosts spend no usage budget.
- The composition parity test now accounts for every service a window attach
  starts, so a new window-only start can't silently skip headless hosts.

Fixes #36853
Refs #37256

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-11-04 17:43:35 -08:00
OrcaWinandOrcaWin b17646a5d9 test: remove 2191 low-value test declarations (#38113)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-04 03:40:25 -08:00
Brennan Benson c3212f0377 Preserve chat HTML appearance in file previews (#37890)
* Preserve chat visual appearance in file previews

* Deliver visual theme once and retain updates through canceled navigation

* Assert visual guest subscriptions stay bounded on repeated loads

* Bundle sandboxed file guest preloads independently

* Match preview media theme and keep build validation import safe

* Cover preview media handoff and retirement contracts

* Apply saved visual appearance before document scripts
2026-11-03 21:36:33 -08:00
Neil f758771364 refactor: group browser lifecycle and extension implementation owners (#37910) 2026-11-03 13:04:11 -08:00
Neil a82538c3be refactor(runtime): organize session records and runtime modules (#37070) 2026-11-01 08:59:05 -08:00
Kelvin Amoaba 0dcc811e07 fix(notifications): leave dispatched workers' finished turns to their coordinator (#36968) 2026-11-01 12:09:15 +00:00
Neil 89f9ce08de refactor(shared): organize session contracts and projections (#36979)
* refactor(shared): organize session contracts and projections

* chore: tighten dropped-path flaky pattern allowance

* docs: preserve current agent instructions with valid formatting
2026-11-01 03:14:46 -08:00
Neil abbbdd7714 feat(plugins): isolated plugin frames, off by default (#36813) 2026-11-01 01:03:13 -07:00
OrcaWinandOrcaWin de2dac447a test: remove 512 low-value tests (#36794)
* test: remove low-value suites and cases

* test: remove orphaned preamble snapshots

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-31 23:01:58 +00:00
OrcaWin 0d9e02754e test: remove low-value suites and cases (#36766) 2026-10-31 14:10:00 -07:00
OrcaWin 10e35fd76d test: remove 226 low-value tests (#36737) 2026-10-31 18:55:29 +00:00
OrcaWin 1da1099dad test: remove 14 low-value suites and 241 individual cases (#36693) 2026-10-31 14:07:33 +00:00
OrcaWin 63fc1f11c5 test: remove 19 low-value suites and 200 individual cases (#36681)
* test: remove low-value suites and cases

* test: shrink stale timeout baseline after removing cases
2026-10-31 13:29:29 +00:00
OrcaWinandOrcaWin 07142b50a2 test: remove 29 low-value suites from audit batch 847 (#36446)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-30 14:04:36 -07:00
OrcaWinandOrcaWin ff93859b66 Validate Web Store download destinations before following redirects (#35286)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 17:04:24 -07:00
Brennan Benson bb1b05e3bd Clear Google cookies from a browser profile, as often as you need (#34158)
* Add repeatable Google cookie clearing for browser profiles

* Bind Google cookie clearing to its successful import target

* Repair cookie-clear contract test boundaries and positive flows

* Correct cookie-clear test fixture typing

* test: use checked runtime bindings in browser profile RPC fixture

* test: provide page registry to sidecar forwarding fixture

* fix: use selected browser profile owner for cookie clear menus

* test: provide confirmation context to link materialization fixture

* test: classify cookie clear session channels

* refactor: consolidate browser command property guards

* test: update cookie clear fixture for merged registry API

* feat: move Google sign-in recovery to browser settings

* Freeze Settings Google cookie clearing intent and refresh availability

* Restore baseline browser import disclosure coverage

* Pass the selected browser profile into cookie settings

* Localize Google cookie settings search keywords

* Update cookie restart fixture policy exports

* Keep localized browser search results visible

* fix: make Google cookie clear a red right-aligned setting that explains when to use it

* Explain why Google cookies are handled separately in the clear row caption
2026-10-28 14:40:57 -07:00
OrcaWinandOrcaWin 136721fcb9 test: remove53 low-value cases and8 suites (#34264)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-26 23:45:26 -07:00
ad2156c358 test: remove 140 low-value cases and 49 suites (#34062)
* test: remove 140 low-value cases and redundant browser probes

* fix: remove unused upstream chat menu import

* test: pin the host-split layout fixture branch

---------

Co-authored-by: Orca Test Audit <test-audit@stably.ai>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-26 15:16:27 -07:00
OrcaWinandOrcaWin de93f326ea test: remove low-value inventories, passthroughs and redundant cases (#33796)
* test: remove low-value inventories, passthroughs and redundant cases

* style: format test suites after pruning redundant cases

* test: remove obsolete workflow and flaky-test references

* test: prune deleted builder test from Windows shim allowlist

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-25 23:41:49 -07:00
OrcaWinandOrcaWin 4687e90a16 [comment audit] Continue main-process comment cleanup (#32946)
* [comment audit] Shorten daemon, hook, and emulator comments [skip ci]

* [comment audit] Shorten VM, conversation, SQLite, and theme-import comments [skip ci]

* [comment audit] Tighten Git execution and source-control comment contracts [skip ci]

* [comment audit] Clarify repository probes and maintenance comments [skip ci]

* [comment audit] Shorten status, diff-cache, and checkout comments [skip ci]

* [comment audit] Clarify worktree lifecycle and WSL routing comments [skip ci]

* [comment audit] Shorten forge identity and review-refresh comments [skip ci]

* [comment audit] Tighten hosted-review client comment contracts [skip ci]

* [comment audit] Clarify GitLab compatibility and review comments [skip ci]

* [comment audit] Tighten hook ownership and watchdog rationale [skip ci]

* [comment audit] Condense host and integration compatibility comments [skip ci]

* [comment audit] Clarify IPC authority and remote-routing rationale [skip ci]

* [comment audit] Finalize file-import and containment comment cleanup

* [comment audit] Fix stale Jira type imports blocking full CI

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-23 16:27:37 -07:00
Kelvin Amoaba 846851237c feat(browser): per-repo or per-workspace browser profile for new tabs (#32039) 2026-10-23 15:20:11 +00:00
Jinjing 8fc1698ebb Add Resource Manager PTY inventory and checked terminal termination (#31593)
* feat(resources): end local terminals with checked identity checks

- Add a PTYs view to the Resource Manager that lists local terminals and can end one. Before stopping, the host confirms the terminal's incarnation, agent ownership, and attachments. This prevents ending a same-ID replacement that started after the list was read.
- Bump the daemon protocol to v50 and gate the new fenced kill on it. Older daemons ignore the extra fields, so the client refuses End instead of sending an unchecked kill.
- Admit diagnostic reads (PTY listing and memory snapshot) only while the Resources view is visible, so a hidden window does no background scanning.
- Report incarnation and host on spawn and exit events so the renderer can match them to the terminal it showed.
- Replace the popover's terminal-session list with the new inventory and move memory polling into the Resources view.

* Show unrouted daemon PTYs in the Resource Manager

* Simplify PTY controls and clarify safe termination

* Format local build compatibility protocol lists

* Add PTY resource management localization strings

* Harden PTY resource manager test fixtures

* Use typed IPC event in PTY kill test fixture

* Refine local PTY resource view model typing

* Refine local PTY resource view model typing

* Include local host identity in PTY lifecycle event tests
2026-10-22 12:20:01 -07:00
Neil fece03b5ac fix(orcad): voice dictation on headless orca serve hosts (#31217)
* fix(orcad): voice dictation on headless orca serve hosts

Mobile dictation against an orca serve host answered speech_unavailable
since serve moved to orcad: orcad never installed the speech service
factories, and the model manager and download imported Electron.

The model download now takes a host request opener (Chromium net on the
desktop, Node https on orcad, with redirects mapped to the same event),
the STT worker ships in the orcad slot, and orca serve hands orcad the
installed app's on-device recognizer. A host without a recognizer (an
SSH or WSL managed slot) lists only cloud models and sends a phone that
picked an on-device model to the setup sheet. Mobile names the fix when
an older server still has no speech at all.

The parity test now requires both roots to install speech.

Reported-by: delorean2020 (@delorean2020)

* fix(orcad): retry Node download drops, stage the recognizer on Windows, review fixes

- Node https errors (ECONNRESET, ETIMEDOUT, ...) are retryable, so an
  interrupted orcad model download resumes (Codex P2).
- On Windows orca serve hands orcad a per-version copy of the recognizer
  in the profile, so a loaded addon never locks the app's files against
  an update (Opus).
- Drop a recognizer candidate path that could never match; one SAFETY
  helper per test file instead of repeated casts; comments.

* fix(orcad): cloud speech is ready only with a key this host can decrypt; prune superseded recognizer copies

Codex round 2: a desktop-sealed OpenAI key exists on disk but orcad's
secret store cannot open it, so the catalog reported cloud models ready
and dictation failed at the end. orcad's model manager now proves the
key decrypts. The desktop keeps its existence check (no keychain prompt).

* fix(orcad): never prune staged recognizers before the profile lock; copy linked packages as real files

Codex round 3. ref-oss check: versioned copies are cleaned only by the
process that owns them, so the pre-lock prune is dropped rather than
patched.

* fix(orcad): remove a partial recognizer copy when staging fails (Codex round 4)

* fix(speech): never hand out another store's ciphertext as the OpenAI key (Codex round 5)

* refactor(orcad): no recognizer copy on Windows; Windows orcad keeps cloud dictation only

Design check after review round 5: every remaining finding came from the
Windows copy that dodges addon file locks. Dropping it shrinks the
surface to a path lookup; Linux and macOS serve keep on-device models.

* fix(speech): name the fix when an old server refuses a mic tap; always set the recognizer env; retry ENOTFOUND (final Opus review)

* fix(managed-server): probe install completeness relative to the slot dir

CI: shipping stt-worker.js added one more absolute slot path to the
install-complete probe, pushing it further past the exec-size budget.
Testing each artifact relative to the slot (inside a subshell cd) cuts
the probe from ~12.2 KB to ~3.6 KB, so it drops off the over-budget
list instead of raising a ceiling.

* fix(mobile): pin host.platform's read to a platform type, independent of which zod copy resolves

With mobile's own dependencies installed (which CI does for any PR that
touches mobile/), the e2e program resolves two zod copies, and the
salvaged platform arm from src/shared inferred as {}. The schema now
narrows through an explicit platform guard.

* fix(mobile): keep the host.platform read line intact for its RPC mutant; narrow in a second step

Mobile verify: the recorded-RPC mutant anchors on the read line, which
the previous guard rewrote, so the mutant could not apply.

* refactor(cli): app-executable resolution in its own module; drop the platform guard now zod is aligned

launch.ts went one line past max-lines with main; resolveAppRoot,
resolveForegroundOrcaExecutable, getExecutableAppArgs and
stripElectronRunAsNode move to orca-app-executable.ts (re-exported, so
callers are unchanged). With mobile and root on one zod (#31260), the
host.platform schema no longer needs the extra narrowing step.

* refactor(cli): import app-executable helpers from their module; no re-exports from launch.ts
2026-10-20 19:54:55 -07:00
Jinwoo Hong 3051b7b671 One background-process read of "Notify About Hidden Workspaces" for phone pushes and the host unread flag (#30571)
* refactor(notifications): one host read of Notify About Hidden Workspaces, used by phone pushes and the host unread flag

* fix(notifications): folder chat phone pushes read the folder's name; skip the hidden check with no Filters switch on
2026-10-19 01:30:52 -04:00
NeilandOliver Mee cf460aba5a fix(browser): switch client-hosted profiles by staging the replacement guest (#30452)
* fix(browser): switch client-hosted profiles with staged guest replacement

Squashed from #30351 by @oliver-mee onto the current main.

Fixes #30350

* fix(browser): fence staged profile guests, release switch grants, carry host profile metadata

- A fence during automation retirement now revokes and stops the staged guest.
- A failed or late-settling switch releases its execution-host grant so the page can be recreated.
- Staging cleanup failures before handover stay visible to executor close.
- createPage/restorePage carry host-owned profile metadata to profile-switch-v1 desktops,
  so recovery and restore can rebuild a switched page.

* fix(browser): roll back a switch whose replacement died, and report a lost old guest

* fix(browser): resolve client-hosted pages before the server-local target path when switching profiles

* fix(browser): a staged profile-switch guest owns downloads it starts while loading

* fix(browser): keep the original placement until a timed-out switch settles

* fix(browser): rebind the original guest before releasing a rolled-back staged guest

* test(browser): type the harness guest release mock

* fix(browser): release the replaced generation's download transfers when a switch commits

* fix(browser): admit file transfers from a staged profile-switch guest while its switch is pending

* fix(browser): release only the replaced generation's download transfers after a switch

* refactor(shared): import BrowserNetworkExecutionHost from its own module, no re-export

* fix(browser): adopt a profile switch the client finished after the caller timed out

---------

Co-authored-by: Oliver Mee <102673257+oliver-mee@users.noreply.github.com>
2026-10-18 17:48:57 -07:00
Jinwoo Hong 9586a3fbb5 feat(sidebar): main serves workspace icons as small data URLs for the phone (dark) (#30046)
* feat(sidebar): main serves workspace icons as small data URLs for the phone, unused until the phone switch

* fix(sidebar): settle review findings in the icons RPC and the manual-redirect port fix

* docs(sidebar): scope the icon fetch-slot bound to a lone read

* fix(sidebar): gate the icons RPC behind the sidebar-inputs switch; prove re-encoding end to end

* fix(sidebar): restore two files the merge reformatted; one pure annotation for the icons params

* fix(sidebar): restore two files the pre-commit formatter rewrote during the merge

* fix(sidebar): icons fetch through their own manual-redirect request; shared desktop HTTP client back to main

* fix(sidebar): settle review findings in the icons manual-redirect request

* fix(sidebar): end a bodyless icon response at once; clearer fetchRepoIcon doc
2026-10-17 15:10:49 -07:00
OrcaWinandNeil 16eaceb6a3 fix(orcad): orca serve owns its Claude and Codex accounts again (#29209)
* fix(orcad): orca serve owns its Claude and Codex accounts again (#28962)

orcad never wired account services, so every accounts.* RPC failed once
orca serve moved onto orcad. Build the same account services the desktop
builds (now in provider-account-services.ts), made Electron-free, and
install them in orcad except on a WSL host, whose desktop owns its accounts.

* test(orcad): keep account-services test off the real userData when XDG_CONFIG_HOME is set

* fix(orcad): structured Codex chats and session discovery follow this host's managed accounts

Create orcad's account services before the runtime so its structured Codex launch/read resolvers and catalog-probe prep are constructor deps, as on the desktop. A client-sent account home still wins, and a WSL host keeps them unset so the desktop-pushed CODEX_HOME applies. AI Vault session discovery now includes this host's managed Codex homes.

* fix(orcad): automatic Codex resumes follow the account home their session was recorded in

Share the desktop's resume launch prep as a factory and wire it into orcad's PTY registration and AI Vault resume, so a pane restored after an account switch resumes from its origin home. A client-sent account home keeps resuming from itself.

---------

Co-authored-by: Neil <neil@stably.ai>
2026-10-15 17:38:52 -07:00
Jinwoo Hong 446cedc0e9 fix(notifications): hidden workspaces stop notifying unless you opt in (#28870)
* refactor(sidebar): one Filters-switch predicate, kind predicates in shared

The sidebar's Filters switches were applied as five separate filters plus a
sleeping sweep inside computeVisibleWorktrees. They now go through one
isHiddenBySidebarSwitch predicate, and the workspace-kind predicates
(default branch, automation, CLI, detached HEAD) move to
shared/worktree/workspace-kinds so main can apply the same rules. The git
identity display moves to shared with them. No behaviour change.

* feat(notifications): hidden workspaces send no notifications unless opted in

A workspace hidden by a switch in the sidebar's Filters menu (sleeping,
default branch, automation, CLI, other-client, detached HEAD) now raises
no unread marker, banner, sound, tray dot or phone push. A new setting,
Settings > Notifications > "Notify about hidden workspaces", defaults off
and restores today's behaviour when on.

The renderer gate sits first in resolveAgentAttention, which every
terminal completion, bell and structured-chat edge passes through before
the unread write and the notification request; the bell's own early
marker write checks the same predicate. For local structured chat, main
pushes phones itself, so that push applies the kind switches main can
evaluate from persisted UI and workspace records. Anything unknown,
unhydrated, or a hidden parent the sidebar still shows notifies.

* fix(notifications): hidden-workspace review fixes

- Main's phone push now honours "Hide other-client workspaces" for this
  host's worktrees and folder workspaces, through a shared
  isCreatedByAnotherClient that the sidebar's no-environment branch uses too.
- The renderer mute fails open until terminals reconnect: before then Hide
  sleeping reads every restored workspace as asleep.
- The settings row stays usable when notifications are off (it also governs
  unread dots), uses Title Case, and its copy names the Hide switches.
- The mute check moves to attention/, and the switch list behind its fast
  path is a full Record so a new switch cannot be skipped.
- A notification field the settings file predates is no longer a repair,
  so upgrades do not rewrite every profile.

* refactor(notifications): simplify missing-field repair check

* refactor(notifications): ask the sidebar pipeline whether a workspace is hidden

- The renderer mute check now runs computeVisibleWorktreeIds with the
  project and host filters lifted instead of re-walking lineage by hand,
  so lineage parents and same-id rows on other hosts follow the sidebar
  exactly. The pure pipeline moves to compute-visible-worktrees.ts so the
  store's attention slice can call it without an import cycle.
- Drops the switch-list shortcuts in favour of sidebarHasActiveFilters
  (renderer) and none (main), and the folder one-caller wrapper.
- Main reads notifyHiddenWorkspaces once, in deliver, next to its other
  eligibility checks; isStructuredAttentionWorkspaceHidden only answers
  whether Filters hide the workspace, and isWorkspaceHidden is required.
- Reverts the settings-repair generalisation; a one-time rewrite on
  upgrade is fine.

* test(notifications): wire the hidden-workspace check into e2e fixtures, and an e2e for the flow

The structured-attention e2e unit fixtures built the main phone-push
delivery without the now-required isWorkspaceHidden, so deliver threw and
no prompt reached the relay; they now pass () => false and carry the
notifyHiddenWorkspaces field. A new Playwright spec rings a real BEL in the
seeded primary checkout with Settings open and checks the unread dot and
the main-process notification request: present while visible, absent under
Hide default branch, and back with Notify About Hidden Workspaces on.

* test(e2e): type the hidden-workspace dispatch log as a declared global

* fix(notifications): exempt a hidden lineage parent only while a child is shown

* perf(notifications): answer the mute check per row before the full sidebar pass

A row the Filters switches show is visible, and a hidden row that no
lineage record names as a parent cannot be shown above a child, so only a
hidden lineage parent needs computeVisibleWorktreeIds. At 1,000 workspaces
a mute check drops from ~0.2-0.3 ms to ~6 us.

* Revert "perf(notifications): answer the mute check per row before the full sidebar pass"

This reverts commit 889eced7ff.

* docs(notifications): note the measured cost of the mute check's sidebar pass
2026-10-14 23:27:48 -04:00
Neil b84d371a9f refactor(runtime): compose typed browser command owners (#27576) 2026-10-14 01:02:56 -07:00
OrcaWinandm4air 398cb846ff fix(serve): report secrets unavailable instead of hanging on an unreachable macOS keychain (#27441)
* fix(serve): report secrets unavailable instead of hanging on an unreachable macOS keychain

* fix(serve): classify keychain probe by exit code, skip under a mock keychain, ratchet direct safeStorage imports

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 03:20:15 -07:00
Neilandm4air 57452c681f Fix cold proxy authentication in the desktop HTTP client (#26957)
* fix(network): answer authenticated proxy challenges on cold desktop requests

* fix(network): destroy a stalled proxy-fetch body on cancel

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 01:12:48 -07:00
Neil 1cda323b6a Report browser placement support from the installed provider (#26559)
* Report browser placement capabilities from the installed provider

* Run managed browser capability checks in the template-building CI lane
2026-10-08 20:51:50 -07:00
Brennan Benson 35e173a5b7 fix(native-chat): alert when a structured chat asks for approval or input mid-turn (#25766)
* fix(native-chat): alert when a structured chat asks for approval or input mid-turn

A structured Claude or Codex chat that stopped mid-turn to ask for approval or a
question raised no OS notification, no phone push and no unread dot: the host's
attention feed only fired when a turn settled. The host now announces each newly
pending prompt once, from the same feed, and owns the phone push for its own
structured sessions; desktops keep presentation only.

- host feed: a `prompt` edge per pending approval/question, opt-in on the
  existing stream; a clean settle that only restates an announced prompt is not
  re-announced to prompt-aware clients; failures always are
- host push: an in-process subscriber pushes the host's paired phones with
  labels from in-memory metadata, and withdraws a prompt's alert once answered
- one attention identity for desktop banner, phone push, dedupe and retirement;
  delivery dedupes on it instead of the per-workspace burst window
- agentSession.acknowledgeAttention routes reading a chat to its owning host,
  which retires what it pushed for that session via its dismissal record

* fix(native-chat): keep prompt alerts under their retirement identity

* Fix structured attention read and delivery lifecycle

* Allow later reads to retry failed attention retirement

* Prevent delayed prompt alerts after accepted reads

* Keep attention integration tests across process boundaries

* Check structured capability before cursorless attention reads

* Keep host retirement fixture in the Node typecheck boundary

* Keep attention test seams in the mock boundary

* Keep host notification formatting usable without Electron

* Declare agents in restored-host attention fixture

* Group session attention capability declarations

* Retire structured alerts on every read and when a remote prompt ends

- Mark read and Mark all read on a chat that was never opened, or is hidden,
  now cover what lit its row: the read boundary is the later of the transcript
  cursor and the newest edge the tab was handed, captured at the click. The
  one boundary closes the banners, retires relayed phone alerts and drives the
  host acknowledgement; an edge after the click stays live.
- An alert from a host older than journal cursors has no position, so a pane
  read retires it again, banner and relayed phone push, as before this change.
- A desktop-relayed prompt alert for a remote chat is withdrawn once that
  host's live status stops reporting attention (answered elsewhere, cancelled,
  host restart); cached status after loss of contact never settles it.
- The attention acknowledgement is agent-session.attention-ack.v1 with a
  required journal cursor and strict params; the cursorless no-op is gone.
- The dismissal store keeps a record whose origin a newer build wrote,
  dropping only that origin.
- Host reconciliation runs on restore and when a prompt leaves the pending
  set, not on every journal commit.
- Remove unused session-wide retirement (retire, retireMatching,
  retireMobileNotificationsMatching) and test-only store lookups.

* Settle relayed prompts only from host-dated status; reads default to view

- A remote chat's status and its prompt edges arrive on separate sockets, so a
  status row queued before the prompt could land after it and withdraw a
  relayed alert that was still pending. Settlement now needs a live,
  non-attention row whose host updatedAt (the journal's latest row time, never
  decreasing) is later than the prompt's host raisedAt. The owed prompt is
  cleared only once the settle call succeeds, so a failed call retries on the
  next qualifying row.
- An acknowledgement without captured reads now reads as a view. Mark read,
  Mark all read, the hover-card jump and both dashboard acks pass 'explicit';
  Activity's automatic per-turn read stays a view.

* Re-judge relayed prompt settlement after each call; dashboard watching is a view

- Settlement is one check of the current mirror, run on each status row, after
  each settle call returns, and when a prompt edge arrives. A row that lands
  while a call is out is no longer dropped, and an edge that arrives after its
  own resolution settles at once. A rejected call is retried only once the
  mirror holds a different row, so a failing call cannot loop.
- The Agent Dashboard's card click acknowledges as explicit; watching the open
  dialog acknowledges as a view, through both the in-window drawer and the
  pop-out relay, and no longer re-acks the state the click just read.

* Keep a reused read owner findable after its holders release it

A chat pane memoizes its read owner. When every holder let go in one commit and
the same owner was picked up again (React StrictMode's effect re-run in
development, or a pane remounted in one commit, as on a tab move), the release
dropped it from the registry and nothing put it back. The attention bridge then
found no owner, so a view read carried no cursor: the unread dot cleared but
the banner and phone alerts stayed. An owner now names its key again whenever
it gains an activation or a subscriber and the key is free, never over a
different live owner.

* Type the ack test's execution host ids

* Keep the turn-completion types in main's turn-completion wire module

Main moved these types into agent-session-turn-completion-wire.ts while this branch moved them into agent-session-attention-wire.ts. They now live at main's path with this branch's additions (journal cursor, prompt attention, prompt arm); the unused completion key is dropped.
2026-10-06 16:25:53 -07:00
Neil b29947d585 fix(secrets): stop telling Linux users to install a keyring they already run (#24013) 2026-09-29 23:01:06 -07:00
Jinwoo Hong 74cc9b5039 feat(desktop): native mobile push integration (2/3) (#19935)
* feat(desktop): integrate native mobile push delivery and lifecycle

* fix(desktop): preserve notification replay policy and review invariants

* fix(desktop): correct notification locale namespace and auto-ack tests
2026-09-10 20:16:56 -04:00
Neil 6815fed6d6 perf(worktrees): converge the trash sweep instead of re-walking doomed trees (#18429)
* perf(worktrees): converge the trash sweep instead of re-walking doomed trees

`transientLockRemovalOptions()` only asked for `maxRetries` on Windows, and
`removeHostTree`'s retry ladder was gated on `process.platform === 'win32'`.
A concurrent writer is not Windows-specific: Spotlight/`mds`, a scanner, or a
live process writing under the tree surface the same EBUSY/ENOTEMPTY/EPERM on
macOS and Linux. So on POSIX the startup sweep got exactly one attempt per
entry, failed, and re-issued the same guaranteed-to-fail walk on every launch.

- Extend the retry policy to every platform. Windows keeps its error set,
  its message fallback, and its delays; the message fallback stays
  Windows-only because POSIX always sets a code.
- Persist a per-entry failure ledger in the trash root so a repeatedly
  failing entry is retried on a 15m/1h/6h ladder rather than on every launch.
  Nothing is abandoned: the ladder clamps, records are pruned when the entry
  goes, and a torn ledger fails open to a full sweep.
- Defer the sweep behind first paint, so its recursive readdir/rm no longer
  competes with window creation and worktree-catalog hydration.

* fix(worktrees): keep Node's per-level rm retries Windows-only

Node's rimraf hands every child back to the retrying entry point
(`_rmchildren` -> `rimraf`), so `maxRetries` is applied once per directory
level and compounds: a permanently-failing leaf at depth d costs roughly
`retryDelay * 36 * 9^(d-1)`. Measured on macOS against one `chflags uchg`
file at depth 2, `{recursive, force}` rejected in 1 ms while
`{maxRetries: 8, retryDelay: 150}` had not settled after 5 minutes.

Handing those options to POSIX removals turned every `removeHostTree` on a
worktree residue (`node_modules/.pnpm/...`, a dozen levels deep) into a
promise that never settles -- wedging the serialized trash-deletion queue,
hanging the sweep on its first failing entry so no backoff is ever recorded,
and leaving the unregistered-worktree removal IPC pending forever.

Keep the cross-platform retry where this PR put it -- the bounded outer
ladders that re-issue one whole `rm` against the same already-chosen path --
and restore `transientLockRemovalOptions()` to Windows-only `maxRetries`.

Also guard the deferred first-window task: off whenReady's promise chain a
synchronous throw is an uncaughtException, which the pipe-error guard
re-throws fatally.

* fix(worktrees): make host tree removal see through Electron's asar shim

The 267 stranded trash entries were not a concurrent-writer race. Electron
patches `fs` so a `*.asar` file reports `isDirectory() === true`, so Node's
recursive `rm` descends into the archive, `rmdir`s a real file, and fails the
parent with ENOTEMPTY — deterministically, on every attempt. Every worktree
that has run `pnpm install` carries a `default_app.asar`, which is why every
residue stopped at the same path.

Route `removeHostTree` through `original-fs` (Electron's unpatched `fs`, with a
`node:fs/promises` fallback outside Electron) instead of retrying a failure that
can never succeed. `removalPath`, `rmOptions` and the Windows retry ladder are
byte-identical to `main`.

Reverts the POSIX retry ladder, the `isTransientRemovalError` widening, the
sweep backoff ledger and the inverted `does not retry host removal failures
outside Windows` ratchet — none of them were fixing the actual failure.

* fix(worktrees): drop the stray orchestration test and bundle the asar guard like production

`orchestration-statement-compilation.test.ts` belongs to #18420 and was swept
into this branch by accident. It imports `./prepared-statement-cache`, which
does not exist here, so `tsc -p config/tsconfig.node.json` failed on this
branch. Removed; typecheck is clean again.

The Electron asar guard pre-externalized `original-fs` in its own Vite build,
which is not what the shipped bundle does. Mirror `isExternalMainModule` from
electron.vite.config.ts instead, so the guard also proves the production
bundler leaves `createRequire(__filename)('original-fs')` as a runtime require
— if that ever became a static import or got folded, production would silently
degrade to the shimmed `fs` while the old test kept passing.
2026-09-03 21:04:54 -07:00
Brennan BensonandMerge Sim 5150c52045 fix(dev): skip blocking keychain diagnostic (#17877)
* fix(dev): skip blocking keychain diagnostic

* fix(dev): preserve forced secret protection report

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-01 11:02:54 -07:00
Brennan Benson ca0a6ec9be Stop the OS keyring probe from gating the first window on Linux (#16912)
* Stop the OS keyring probe from gating the first window on Linux

1.4.190 added an at-rest secret protection report and called it from the
`app.whenReady()` startup path, before the first window is created.
`describeProtectionGap()` asks Electron `safeStorage` whether the OS keyring
is usable, and on Linux that is a blocking D-Bus round trip to
`org.freedesktop.secrets`. A keyring that is present but locked with no unlock
prompter never answers, so the call sits until D-Bus times it out and the app
shows no window for over a minute.

Measured on Ubuntu 24.04 against a Secret Service that accepts the connection
and never replies, time to first window:

  1.4.188                          1.06s   (never contacts the keyring)
  1.4.190                         76.05s
  1.4.190 --password-store=basic   1.06s   (probe bypassed)

Nothing on the startup path consumes the report, so it now waits for the first
window's `ready-to-show`, with a timer fallback because that event can fail to
fire when the GPU cannot present and headless serve has no window at all. Same
build under the same hanging keyring: first window 80.48s -> 5.31s, with the
report still delivered.

STA-5765

* Pin the deferral the keyring-probe test exists to protect

The suite passed with the probe fired on browser-window-created instead of
ready-to-show, with setImmediate dropped, and with the fallback stretched to
10 minutes — every one of which reintroduces the STA-5765 stall. Drain the
queue before asserting and bracket the fallback so those mutations fail.

Also reformats the file to oxfmt.

* Report the keyring gap inline in headless serve

Deferring the probe to the first window is right for the desktop app, but serve
never opens one, so the fallback timer became its only path. That moved the
stall to after `printServeReady`: the runtime advertises itself, a relay or
mobile client pairs, and only then does the main thread freeze on the keyring —
stalling pings and PTY pumps, which a client reads as a dead host.

Serve now reports inline, which is the timing it already had, and blocks before
anything is advertised rather than under a live client.

STA-5765

* test(secrets): pin the once-guard against a late window reveal

The fallback can report first and the window reveal arrive after it; without
the guard that probes the keyring a second time, blocking the main thread just
as the user starts interacting. No existing case covered that order — removing
the guard left all six tests green.

* fix(secrets): keep the deferred protection report non-fatal, and pin the wiring

Deferring the report moved it off `whenReady`'s promise chain. A throw there was
an unhandled rejection the app survives; inside `setImmediate` it is an uncaught
exception, and `installUncaughtPipeErrorGuard` re-throws those fatally — so a
diagnostic the module documents as deliberately not fatal could kill the app.
Wrap the deferred call so it degrades to a warn. Serve keeps the inline posture.

Nothing outside index.ts referenced the scheduler, so reverting the call site,
or flipping `deferUntilFirstWindow`, left the whole suite green — including the
headless-serve regression an earlier review already caught once. Pin the wiring
as source text, following the host-port-bootstrap-wiring idiom with every anchor
bounded, and pin the two module gates that were only jointly covered.

* test(secrets): make the deferral wiring pin resist an inert call site

Round 2 of the review gamed the pin it had just added. Both anchors were bounded
against -1 but not against overshoot, and the marker matched anywhere in the
file — so nesting the call in a block, prefixing it with a guard, or commenting
it out all left three green tests standing over a call that never runs.

Bound the slice length, and anchor the marker to a statement at whenReady's own
indent. Commenting the call out, wrapping it in `if (...) schedule(...)` with or
without a block, and flipping the flag each redden now; previously only the
unused-import typecheck error caught the first.
2026-08-28 15:05:24 -07:00
Neil a7a4f5d987 style(secrets): trim backend probe comments (#16136) 2026-08-23 15:42:37 -07:00
Neil 7a739c6bf5 fix(secrets): probe the Linux-only storage backend defensively (#16046)
`safeStorage.getSelectedStorageBackend` is `@platform linux`, so it is genuinely
undefined on macOS and Windows — confirmed against the installed Electron 43,
where it reads `undefined` on darwin and `function` on Linux. The shipped code
called it behind a `process.platform === 'linux'` check, so it never threw, but
the guard was the only thing standing between that call and a startup TypeError.

The platform check now lives with the probe, alongside a typeof check and a
try/catch, and an unreadable or unknown backend reports no gap — claiming one we
cannot prove would be its own kind of lie.

The gap this closes is in the tests, not just the code: every suite here mocks
safeStorage with the method present, so the suite could stay green while the
shipped app threw. The new case deletes the member from the live mock rather than
re-mocking, because the module already holds that object and a later vi.doMock is
inert — the first version of this test passed against the unguarded code, which
is the failure mode it exists to catch. Verified against the expression currently
on main: three cases fail.
2026-08-23 00:38:28 -07:00