resolveCommand only checks what it resolves, so a file that also builds its
own wsl.exe exec no longer passes the import check on a resolveCommand call
alone. Note editor --remote wsl+<distro> launches as out of scope.
* feat(wsl): internal distro allowlist for WSL execs on shared test boxes
ORCA_INTERNAL_WSL_DISTRO_ALLOWLIST (comma-separated distro names) keeps every
wsl.exe call out of distros not on the list: process-host's resolveSpawn,
the main and daemon node-pty spawns, and the two codex-accounts calls that
bypass process-host. Pure list/status/version calls stay allowed; any other
form must name an allowlisted distro, and --shutdown or a default-distro exec
is refused. A malformed value refuses every distro and logs once. Unset, the
check reads the variable and returns, so behavior is identical to today.
* fix(wsl): cover every direct wsl.exe exec with the internal distro allowlist
- Restore #34525's worktree-create payload and captured-host test, reverted by
accident in the first commit.
- Check the allowlist in execFileCapture, the choke point for WSL Git, gh and
worker-thread captures, and before the WSL git-read probe, the Codex hook
path canonicalization and the CLI login spawn. The WSL watcher now spawns
through process-host.
- Stop scanning wsl.exe argv at the first positional argument, which wsl.exe
runs in the default distro.
- check-process-host-imports fails on any direct wsl.exe exec outside
process-host that does not call assertWslDistroAllowed.
* fix(wsl): check the distro allowlist where resolveCommand produces the wsl.exe form
Every Git, gh and glab path into a distro resolves through resolveCommand, so
gitExecFileSync, gitSpawn, git stream reads and wslAwareSpawn are covered too;
the execFileCapture check is now redundant and removed. The import check also
treats buildWslExecArgs as a wsl.exe exec and accepts resolveCommand as covered.
* ci: move secondary, scheduled and contract workflows to GitHub-hosted runners
Public-repo standard hosted runners are free under Enterprise. Release, build,
benchmark, e2e and unit workflows stay on Blacksmith. ci-xterm-cache seeds both
cache stores since its PR consumer's pool is undecided.
* test(pi): resolve the smoke bundle's external packages from the repo
#34148 pulled zod into the commit-message planner graph; the bundle in the
OS temp dir could not resolve it on any runner.
* ci(unit): use four workers on the hosted four-core x64 runner
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Hourly and daily builds publish unvetted dev channels on a schedule, so their
latency is not user-facing. Move the macOS build to hosted macos-15, the
Windows build to hosted windows-2022 (the release path), template
qualification to hosted lanes, and the small routing/publish jobs to
ubuntu-latest. Adhoc and release builds stay on Blacksmith.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
* test(wsl-host): native chat on a WSL host through the fake transport, including a stopped distro
* test(wsl-host): type the chat spec's focus host id
* test(wsl-host): harden the WSL host chat spec and re-run fake-connector specs on change
* test(wsl-host): scope the chat reply check to the transcript and test the fake's route
Find the prompt only in the transcript so the composer can't stand in for it,
re-check the boot-free stop window after unfocusing, right before the focus,
give the fake's distros.json temp file its own name so it can't collide with the
spec's, and test that the fake connector routes to both WSL host specs.
* ci: warm hosted runner caches and drop the serve-switch installer cache
* ci: seed only restored caches from hosted warm legs
* docs(ci): hosted dev builds and no larger runners
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Keep preflight and the unit-test matrix (the PR critical path) on Blacksmith; move routing, aggregation, contract, packaging and mobile checks to hosted standard runners, which are free for public repos under Enterprise concurrency.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* Stop unrelated host status publications rebuilding sidebar workspace state
* Follow sidebar selector lint conventions
* Scope sidebar status reads to mounted consumers
* Cover each unrelated sidebar status publication
* Fix sidebar isolation lint and formatting
* Type the composed sidebar header fixture without assertions
* Remove unused React imports from sidebar contract tests
* Reuse folder record selector and prune stale focus allowance
Removes 134 redundant test registrations across native-chat projection, sidebar ordering, and palette matching. Deletes two entire suites: notice appearance mapping and ordering scenarios whose test-owned Host supplies the behavior being asserted.
Retains 154 registrations covering state transitions, competing inputs, FIFO tool pairing, sidebar feed application, and search ranking. Production code is unchanged. Removes the two deleted suites’ timing entries and lowers the wall-clock baseline only for the deleted Date.now assertion.
Validation: isolated static registration/body preservation, formatting, lint, discovery, runtime routing, and metadata checks. Full PR CI must verify 275 expanded cases across 17 owning test roots, including the transcript-contract aggregator; 185 cases belong to retained selected sources and required counterparts. No local runtime tests were run.
* feat(mobile): light and dark themes with desktop's palettes
Settings → Appearance offers System, Light and Dark. The shell passes the
resolved scheme to over-the-air pages in its init frame.
Fixes#22184
* fix(mobile): draw your chat messages in desktop's user bubble
Desktop uses a faint surface with a hairline border in both themes; the inverted fill went black in light.
* test(mobile): re-measure the page script sweep after the shared theme hooks
Every route now imports the theme hooks while the entry does not, so they emit one shared script (96 for the tree). Re-swept every route prefix; the worst route step is now 15.
* test(mobile): read themed stylesheets in the text-input font-size census
The census only unwrapped StyleSheet.create({...}); inputs styled through themedStyleSheet(() => ({...})) came back unread.
* fix(native-chat): resting chat skill checks share one capped Codex process per account
A resting chat's slash list started one hidden codex app-server per
folder, each kept alive 70 s outside the two-check limit, and a scan
waiting in line for 30 s was treated as hung and doubled. With many
resting folders this kept about a hundred Codex processes alive.
- The catalog runs one scan per folder at a time; readers join it.
Failures retry three times, then wait for the TTL. Entries unread for
an hour are dropped.
- Codex folders that share an account, binary, arguments and
environment are answered by one app-server, batched into skills/list,
with at most two probe processes alive until their teardown ends.
A probe that cannot start backs off, and a refresh does not reset it;
an explicit refresh asks a live probe to reload.
- Batched replies are matched to their own folder.
Tracked in #34141.
* test: use fake timers for resting Codex probe cache checks
* fix(native-chat): bound resting discovery and retain process ownership
* fix(native-chat): close cancelled late directory acquisitions
* test(native-chat): complete cancellation discovery fixture
* test(native-chat): align fixtures with resolved workspace contracts
Read git ls-files and git diff --name-only with -z so non-ASCII paths are
not C-quoted and still match ast-grep's real filenames; otherwise new
flaky-test findings in such files were silently dropped.
Fixes#34395
Co-authored-by: Neil <neil@stably.ai>
* refactor(mobile): share the queued-card projection with desktop
Image-only queued drafts showed a blank card on the phone.
* fix(mobile): honor the host's Queue follow-ups setting
With it off, a mid-turn send from the phone still queued while desktop sent it into the running turn.
* fix(mobile): keep a queued-card edit when the card leaves the queue
The phone closed the editor and dropped the typing; it now goes to the chat box as on desktop.
* fix(mobile): hold /clear and /compact for background work and sends
The phone fired the command where desktop holds it, showed start failures twice, and left the agent unnamed in failures.
* fix(mobile): hand a queued edit back to the chat it came from
* fix(mobile): scope a command's send hold to its own chat
* fix(mobile): read a published null queue as empty
* refactor(mobile): share the returned queued-card caption rule
* test(mobile): drop queued-card tests the shared rules already own
* refactor(native-chat): share the needs-attention rule and drop leftover wrappers
* fix(mobile): type the structured sends mock, drop the moved queued-cards path and ship queued edit copy at boot
* fix(native-chat): drop the queued card's unused image-label import after the rebase
* fix(mobile): order the phone's queue reorder preview by position, as the shared cards sort
* test(mobile): keep the queued-messages test within its line budget
* fix(mobile): settle terminal chat working state and turn timing with desktop's rules
Move desktop's live-status merge, Stop suppression, hook-state resolver and
terminal turn timing to src/shared/native-chat. Mobile reads the transcript
lifecycle the host already sends, so a provider turn end settles Working the
same way desktop does. Terminal chats now get the turn bar, timer and folding,
and the streaming preview uses the shared deriveNativeChatStreamingText.
* fix(mobile): send desktop's terminal Stop keys, a second Escape only for OpenCode
Desktop sends one Escape on Stop. It adds a paced second Escape only for
OpenCode, which confirms an interrupt that way. Claude Code and Codex read
Esc-Esc at an idle prompt as rewind. The rule moves to
native-chat-agent-support, and the phone now paces the second Escape at
desktop's answer step instead of 80ms.
* fix(mobile): word Orca-stop and usage-limit notices like desktop
Move desktop's Orca-stop sentences to src/shared with an English table that
desktop translates. The phone names the Orca host it pairs with, and when
that host is unnamed it says "the remote server". Usage-limit rows rebuild
desktop's sentence from the failure fact. The usageLimitLineText fallback,
which only the phone used, is deleted.
* test(mobile): pass the overlay's host name in the prompt controller harness
* test(mobile): re-record Stop goldens at desktop's step, split status-notice tests, pin the turn clock and ship notice copy at boot
* fix(mobile): offer structured Stop while a send is out and read queue-sends-next as working
Move desktop's structured Stop rule, its Stop-joining helpers and the
queue-sends-next rule to src/shared. Both sides now use them. The phone
counts its in-flight sends, so a conversation-stop host offers Stop before
the turn opens. Between queued turns the phone reads as working, and
commands that can't wait in line hold like they do on desktop. Untargeted
Stops are no longer joined on legacy hosts.
* fix(mobile): offer terminal Stop again when its Escape went nowhere
Stop hides Working on the phone like it does on desktop. The phone's Escape
can fail outright, though, while desktop writes to a local PTY. When no Escape
could have landed, the phone now drops that suppression. The streaming preview
also treats a pending echo as the transcript's tail, as desktop does, so an
earlier reply can't hide a new preview.
* fix(mobile): hold structured Stop while a send it covers is in flight
On desktop, Stop's held and Stopping state depends on whether the chat can
stop, and that includes a send still in flight. The phone now does the same.
Once Stop is pressed before the turn opens, the button holds and repeated
presses send no more untargeted cancels.
* fix(mobile): end the terminal Stop hold when a new message is sent
Desktop clears Stop's working suppression on an optimistic send, so a new turn
that keeps the same hook epoch (Claude with working subagents) still offers
Stop. The phone now does the same when a new pending echo appears.
* refactor(mobile): trim chat turn wiring after the desktop rule move
Drop the controller's unread nativeChatAgentWorking field. Re-render only when
a chat starts or stops having a send out. Keep the structured turn indicator
stable across stream frames. Type the Stop test table instead of casting.
* refactor(mobile): track structured sends in flight once for Stop and command holds
* test(mobile): re-record Stop goldens now that Stop settles with its Escape delivery
* fix(mobile): escape terminal image paths like desktop drops
* fix(mobile): keep launch-draft prefill over older user turns
* fix(mobile): rank @ file matches with desktop's fuzzy Quick Open search
* fix(mobile): use desktop's composer triggers for mid-prompt / and $ picks
Picked @ files are now quoted when the path has spaces.
* fix(mobile): send picked skill paths on structured chats
* fix(mobile): list the host's disk-scanned skills in the / and $ menus
The phone names the workspace and the host resolves its folder, refusing SSH
workspaces as desktop does; hosts without skills.discover-workspace.v1 list none.
* fix(mobile): forget a picked skill once its token is deleted
* fix(mobile): drop a picked skill as soon as its token is deleted, and retry failed skill scans
* fix(mobile): treat a picked leading skill as chat and stop failed skill scans from refiring
* test(mobile): drop the menu ranking case the shared picker tests own
* refactor(mobile): keep skill picks on the draft record and reuse shared send rules
A rejected send now keeps its picks and leading-skill origin on both send paths.
* fix(mobile): replace a re-picked skill's file and restore picks with a rejected send
* fix(mobile): read the chat agent for the skill catalog from the active resolution
* style(native-chat): format the composer autocomplete files
* fix(native-chat): import the picker's apply and send-classify rules from the shared composer module
* refactor(mobile): import the composer's draft-store hooks once
* style(mobile): format the notice row's style import
* fix(mobile): resolve terminal chat agents in desktop's order
Mobile now uses the shared detected-then-launch precedence and transcript-readability check; the phone's remembered agent only fills in after them.
* fix(mobile): hide Codex foreground commands from the background-task strip
* refactor(native-chat): share diff-card gutter, basename and task count
* fix(mobile): show clipped tool output sizes in KB/MB like desktop
* refactor(native-chat): share the tool-run header rules with mobile
* fix(native-chat): share tool-line detail rules; skip unsafe-only search hits
* fix(mobile): copy message text with desktop's rule
Copy now leaves out roster and background-task fallback twins and wordless provider frames.
* refactor(native-chat): memoize shared tool-line view and output sizes; share result preview
* fix(native-chat): read a lone result's preview from its narrowed body
* chore(i18n): ship chat tool and stop copy in the runtime English catalog
* refactor(native-chat): move the pending-send echo rule to shared
Mobile reuses it instead of its own matcher.
* fix(mobile): match chat echoes after their send boundary, sink unmatched ones
Uses desktop's rule: rows before the send no longer retire an echo, a send that
never lands no longer lets later glued sends retire, and an unmatched echo sits
below later turns.
* fix(mobile): keep glued photo captions and folded boundaries in echo matching
* fix(mobile): give image preview echoes the send time the occurrence rule reads
* fix(mobile): match echoes on image-normalized rows and cap open sends at eight
Same input and bound as desktop; each open send rescans the transcript.
* test(mobile): drop a pending-order case the tail test already covers
* refactor(mobile): read the send boundary once and skip echo normalization when idle
* test(native-chat): type the occurrence case's pending send
* test(mobile): drop the pending-echo wall-clock entry from the flaky-test baseline
* refactor(native-chat): share session option labels with mobile
Mobile's options pill now shortens effort levels (Med, XHigh, Min) and names
choices and options the way desktop does, from one English table that desktop
translates.
* refactor(native-chat): run mobile terminal chat options on the desktop surface
The terminal-chat option surface, its appliers, the option cache and the
dispatch types move to src/shared, and the phone builds its pickers from them.
Grok and every other agent with a catalog now get model and effort pickers on
the phone, picks are saved as the host's launch defaults, the OMP live switch
gate and discovery mapping come from one copy, and authoritative discovery
retires persisted models it no longer lists. The option stamp moves to shared.
* refactor(mobile): merge the session option type imports
* fix(mobile): keep terminal chat option picks on their own chat
The phone keeps one option surface per chat, so a pick that lands after leaving
and returning shows there, and a picker command only switches the chat it came
from. Discovered models merge with the seed catalog as on desktop, so Grok keeps
its effort picker. A Claude model switch the phone cannot confirm is never
saved as a launch default.
* fix(mobile): start terminal chat options over when a tab changes agent
A tab that runs another agent drops its kept option surfaces, report markers
and cached record, so a fresh session shows its own model. A pick that lands
after the phone moved to another chat is not saved as a launch default.
* fix(mobile): fence retired terminal chat option surfaces
A write that lands after its tab started another agent no longer publishes the
old agent's record, and a changed OMP switch capability rebuilds the chat's
surface from the cache instead of reviving an older one.
* refactor(native-chat): simplify the shared session option wiring
Desktop translation pieces use literal English defaults so the localization gate can read them, the selected-choice lookup is shared once, and the phone keeps one option surface per tab with the agent change detected from the cached record.
* refactor(mobile): return no option surface for an agent without a catalog
* test(native-chat): expect the shared Effort label in the startup picker test
* test(native-chat): keep only the label cases main still checks after the rebase
* refactor(native-chat): import the session option snapshot rules once in the PTY option surface
* refactor(native-chat): share structured option pick bookkeeping with mobile
Desktop and the phone now track picks per option through one shared book: a
repeat of an option on its way waits, other options go together, a lost reply
is not a pick done, and a stale reply only commits its own option. The phone
also re-reads options each turn and after the provider starts, and shows picks
at once while the host is still starting the provider.
* refactor(native-chat): share the option pick blocking rule
* fix(native-chat): read in-flight option picks from the shared book for launch send options
* fix(native-chat): keep the structured options hook within its line budget and pass the phone turn in the e2e harness
Hardened-runtime TCC refused EventKit and Contacts requests from terminal-launched
tools without a prompt: Orca and its terminal host lacked the addressbook and
calendars entitlements and the matching purpose strings. Declare them the same
way camera, microphone and location already are; the terminal host inherits both.
Fixes#12933
Co-authored-by: Claude <noreply@anthropic.com>
Japanese IMEs convert one clause at a time, but the terminal's preedit overlay
painted the whole composition uniformly, so the clause the candidate window
targets was invisible. Read the IME's selection from xterm's textarea on the
insertCompositionText input event (Chromium only updates it there) and paint
that range of the preedit in the theme's selection colors. Cell-grid advance
corrections are unchanged and nothing reaches the PTY; a collapsed caret or a
range outside the preedit highlights nothing.
Fixes#27873
Co-authored-by: Neil <neil@stably.ai>