The three popup compatibility probes ran in the parallel unit shards and
timed out at their 14s in-process deadline when other files starved the
runner. Move them into the package job's serial Electron lifecycle step,
and stop the six probes already in that step from also running in the
shards.
Claude-Session: p2-headless
* fix(relay): end the restart-safe quiet at the drain's pace window, not a window after it
A paced drain tells its last host when the window ends, so the restart-safe
wait has to see an empty runtime reaching past that point. It counted a full
window of quiet samples from the first empty one instead, and the runtime only
empties as the window ends, so every same-cap cell waited the window twice:
about 25 minutes per Asia cell at 1200 s on 2026-10-13, and 5 minutes per
300 s cell.
The window is now measured from the verifier's own start, which runs after the
cell accepted the drain and so ends no earlier than the cell's own window. A
pass still needs two consecutive restart-safe samples, the last taken at or
after that end, and any non-zero runtime counter or open migration still
restarts the count. Progress lines report paceWindowRemainingMs in place of the
old sample target.
Claude-Session: f2fe8cb5-4467-4a25-8aab-98598b70791f
* fix(relay): drop the same-cap drain timeout's double-wait allowance and stale quiet wording
The drain step's timeout added the window's excess over 300000 only because
restart-safe used to wait a second window after the cell emptied, so it is
now the 15-minute migration lease plus the window for every pace (35 minutes
at 1200000, unchanged at 300000 and below). Docs, workflow comments and the
shadow gate's settledAfterSeconds note now describe the window-end rule. The
always-quiet test starts its clock late, so a window measured from zero
fails it, and the overlapping late-control test is folded away.
Claude-Session: f2fe8cb5-4467-4a25-8aab-98598b70791f
* docs(relay): state the restart-safe window-end rule exactly
Claude-Session: f2fe8cb5-4467-4a25-8aab-98598b70791f
* docs(relay): the slowest restart-safe wait is now 35 minutes
Claude-Session: f2fe8cb5-4467-4a25-8aab-98598b70791f
Any press on a <webview> guest moves Chromium focus into it, so an agent click
took the user's terminal focus (#20595). Main now asks the hosting pane to guard
focus for each agent press (Orca's click/mouse down and the CDP proxy) and the
pane puts it back inside the webview's focus event, and again when the press
ends. Offscreen pages keep their native focus.
* Require exact-commit PR checks before agent merges
* Give full PR checks a unique required status context
* Validate required context ownership on every pull request
Older requested refs predate the security addon builder, so the desktop
template CI job failed trying to run a script that isn't there. Gate the
build, keep, and collect steps on that script existing in the checkout.
Add a test that pins the gating for both cases.
Also drop an unused getAppEnvironment import in orcad-entry.ts.
* [CI] Reduce redundant test work and import existing helpers directly
* [CI] Preserve provider guards and pin the mobile browser test core
* [CI] Fix CI test setup and verify retained contracts
* [CI] Compare native media chunks without boxed byte arrays
* [CI] Detect shifted native media reads with position-sensitive bytes
* feat(windows): add administrator PowerShell terminal setting
* fix(settings): localize administrator terminal search keywords
* fix(windows): restrict elevated terminal host to Orca's runtime and requesting account
* fix(windows): harden administrator terminal routing, inventory and restore
* fix(build): build the Windows agent hook when electron-builder packages Windows
* fix(build): build the packaged Windows agent hook for the target architecture
* fix(windows): accept the development terminal runtime and test real elevation in CI
* feat(windows): show administrator status and restart instructions
* fix(settings): localize administrator mode search keywords
* test(settings): isolate the desktop permission section in shell picker tests
* Add Windows administrator restart and simplify Terminal settings
* Keep administrator restart silent and settings actions within narrow panes
* Keep Windows shell choices and permission guides usable at minimum width
* Prevent later restarts from replaying administrator terminal reset
* Fix CI route resolution and transient Windows fixture cleanup
* ci(relay): use the runner's preinstalled gcloud instead of downloading the SDK
dl.google.com downloads of google-cloud-sdk time out on the runners, failing every relay
operation at setup-gcloud. skip_install keeps the WIF cred-file login.
* ci: drop the temporary gcloud skip_install proof workflow
* ci(wsl): real WSL2 lane for orcad deploy, bridge, liveness and holder (WSL-via-orcad C10)
* ci(wsl): upload the template's dot-directories; save the rootfs cache on failure
* ci(wsl): update the runner's WSL to 3.0.1+ before asserting WSLInterop survives
* test(wsl): let a released holder settle before asserting it exited
* ci(wsl): install a pinned, cached WSL 3.0.1 MSI instead of wsl --update
* test(wsl): keep the lane's survival seconds typed in its assertion message
On a validated AppImage the desktop's daemon launch first runs <generation>/resources/node-runtime/bin/node
on the generation's daemon-entry.js when the extracted copy for the file at $APPIMAGE is complete, carries
the bundled Node and the entry, and is the running release. It then falls back to the AppImage's own mount
and the current mount, logging every fallback. The copy rung records $APPIMAGE/<entry> like the own-mount
rung, so either daemon is adopted. Generation leases recognize the bundled Node. macOS, Windows, deb and
rpm never reach it.
Co-authored-by: m4air <m4air@Mac.localdomain>
* ci: run mobile runtime test groups on separate runners
* ci: use a lightweight runner for the mobile result gate
* ci: distribute Bun tests across both mobile runners
* ci: reuse mobile compiler state without adding runner queues
* test: verify eviction of source variants in one cache bucket
* test: baseline existing serializer limits in the capacity capture
* fix(windows): restrict secure files in-process instead of spawning icacls
Every secure write on Windows spawned icacls.exe five times on the calling
thread (the Electron main thread for most stores): verify, /reset,
/inheritance:r /grant:r, verify, verify. Process creation under endpoint
security costs ~0.6 s each, so each write froze the UI for seconds (#26027).
The /reset + /inheritance:r pair is also what behavioural EDR scores as
ransomware tradecraft (#25583).
Add native/windows-security (@orca/windows-security): a small N-API addon
over advapi32 that reads and writes DACLs in-process. It exposes the token
user SID, a DACL read as SDDL, a DACL write (sync and on the thread pool),
a create-with-DACL file write, and an additive grant. Policy and
verification stay in TypeScript.
- writeSecureFile creates each temp file already carrying the protected
user/SYSTEM/Administrators DACL (CreateFileW + SECURITY_ATTRIBUTES), reads
it back once, and renames it. No ACL is modified on the write path and no
process starts. Before: 5-7 spawns, 94-138 ms per write. After: 0 spawns,
~0.5 ms.
- A secure directory is verified once per process. It is rewritten only
when verification fails, on the thread pool, because the write walks
every descendant (~2 s on a 29k-entry userData).
- The verifier follows OpenSSH's rule: only the user, SYSTEM and
Administrators may be granted anything, and the path must be protected
(or, for a file, inherit from a secure parent). DACLs written by the
icacls-era hardening pass unchanged, so there is no migration.
- If the addon cannot load, the write still happens and reports
unrestricted, logged once. There is no icacls or PowerShell fallback.
- grantDirAcl, the startup userData grant, and the install-dir probe and
package repair use the same addon, so whoami.exe and icacls.exe are no
longer spawned anywhere at runtime.
- orcad's Windows slots ship the addon beside their bundles (built per
arch, PE-machine checked). Its data-root privacy check fails closed, so
without the addon a Windows orcad would not start.
Fixes#26027Fixes#25583
* refactor(secure-file): harden existing Windows files through the sync lane
The read path called restrictWindowsPathSync directly and recorded success
itself; the existing sync lane already does both. One call site fewer.
* refactor(windows-security): one load, one rule, no fallthrough (review #28135)
- The descriptor parser is anchored, so a conditional ACE (its body holds
parentheses) or anything else it cannot read fails closed instead of
verifying the ACEs before it.
- findSecureDaclViolation is one flat rule: protected, allow rules only for
the user, SYSTEM and Administrators (OpenSSH's ACE rule), and full
control for the user. The inherits-from-a-secure-parent exception is gone:
it accepted exactly what loosening the parent reaches.
- The addon and the user SID load once, behind one wrapper that adds the
extended-length prefix to every path and runs async DACL writes to one
path in order, so a startup grant cannot land after a re-lock and restore
the broader DACL.
- With the addon loaded, a failed born-restricted create throws with the
matching Node error code instead of falling back to a plain write.
- A grant leaves a NULL DACL untouched instead of turning 'everyone' into
'only this SID'. WriteFile runs once and fails on a short write.
- The artifact create-intent store writes through writeSecureFile and drops
its own temp writer, directory LRU and synchronous directory re-lock.
- Test seams the global fake makes redundant are gone.
* fix(packaging): fail a Windows build whose workspace addon binary is missing or foreign-arch
The @orca/windows-* workspace links exist on every host, so their presence
proved nothing: a missing or wrong-architecture .node only surfaced at
runtime, as unrestricted secure files or an orcad that refuses to start.
beforePack now reads each addon's PE machine against the target arch.
* refactor(windows-security): prune what the native ACL left dead (review #28135)
- Sync-spawn ratchet: hardenSecurePath and the sync-flag exemption it alone
used are gone, and the advice and baseline header no longer say secure
writers run icacls.
- The install-dir probe reads ACEs through parseSddlDacl, so an ACE it
cannot parse fails the whole reading closed instead of being skipped; AC
is a resolved alias there now.
- The packaging check reads the addon binary through node_modules, the copy
that ships.
- No winCode-to-errno table: nothing reads .code off a failed secure write.
- The load memo is not mutated; secure-path keeps its own log-once flag.
- grantInstallDirPackageAcl is a plain function, and secure-file restricts
a Windows directory and a POSIX path directly instead of through one
two-platform helper.
* fix(windows-security): resolve LA in real-ACL tests; tear a file whose close fails
- The GitHub Windows runner is the built-in Administrator (RID 500), whose
SID reads back as LA. Production already resolves it through
localDomainSidOf; the win32 test helpers parsed without it. One shared
readDaclOf resolves aliases for every real-ACL suite, and the verifier's
unit test covers an LA-aliased directory and LA as a stranger.
- createFileWithDacl treats a failed CloseHandle like a failed write (lost
data on a network volume) and deletes the torn file.
- Drop a duplicate test reset and a typo.
* fix(windows-security): load only from inside the app; read DACLs as structure (review #28135)
- Loader: orcad's staged binary first, then the workspace package only if
its real path is inside the app (process.resourcesPath, or this
checkout). A bare specifier otherwise walks every parent up to
C:\node_modules, which any local user may create, and orcad holds
credentials.
- The addon reads a DACL as { protected, aces: [{ type, flags, mask, sid }] }
via GetAce, with a SID only for plain allow/deny ACEs and null for a NULL
DACL. findSecureDaclViolation and the install-dir probe check that
structure, so conditional, callback and object ACEs fail closed. The SDDL
parser, its alias table, LA/localDomainSid resolution and getDaclSddl are
gone; SDDL is only the write format.
- Install-dir tree pass: files flagless as before, but a directory only when
its own DACL is protected (inheritably). Re-granting inheriting
directories re-propagated each subtree (3.2 s for 1,093 directories). The
root is no longer granted, or counted, twice. The two passes are plain
functions behind a { root, tree } seam.
- One namespacing (path.win32.toNamespacedPath) at the addon boundary, also
the per-path ordering key; the comment states the real sync-grant
invariant.
- restrictWindowsDirectory(dir): Promise<boolean> replaces the callback
lane; the reporter is guarded once, so the 'settle' stage is gone.
- Sync-spawn ratchet: the secure writers no longer spawn or block, so they
leave the gate (109 -> 61 uses).
- Errors name the Win32 call and code only; a failed
GetSecurityDescriptorControl fails the write instead of defaulting to
unprotected.
* test(secure-file): assert the durable POSIX write's outcome, not the old chmod order
writeSecureFile now chmods the temp file to 0o600 before fsyncing it, so
the fsync also covers the mode change and the file is owner-writable when
opened. Assert what matters under umask 0o200: the write succeeds, the
temp file is fsynced read-only, and the published file is exactly 0o600.
* fix(windows-security): namespace paths with the host's toNamespacedPath
path.win32.toNamespacedPath rewrote POSIX temp paths (/tmp/x -> \tmp\x) in the
Linux runs of the Windows-logic unit tests, so the fake addon wrote to the wrong
place. The addon only runs on Windows, where the host function is the win32 one;
elsewhere it is the identity. The long-path assertion runs on Windows only.
* test(windows-security): assert the install-dir long-path prefix on Windows only
The prefix comes from the host's toNamespacedPath, which is the identity off
Windows; the Linux run of this suite expected the win32 escape.
* fix(windows-user-data-acl): grant the root even when its children cannot be listed (review #28135)
A readdir failure on userData aborted the whole grant, so the root grant
that could repair a DACL refusing the listing was never attempted. The
listing failure is now recorded, the root is still granted, and the
marker stays withheld so the next launch retries.
* test(windows-security): wait for relock propagation, accept Entra ID SIDs (review #28135)
- The large-relock test waited only for the parent to turn protected,
which happens before propagation reaches the children, then read a child.
It now waits on the parent and both ends of the child range.
- The token-user test rejected S-1-12-1 (Entra ID) accounts, failing the
suite on Entra-joined dev machines.
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
* build(linux): carry the pinned Node inside the AppImage so orca serve's slot works offline
Linux packaging places the pinned glibc Node at resources/node-runtime/bin/node before the
glibc-floor gate, proves it serves one PTY with the packaged daemon entry, and deb/rpm exclude
it through fpm. orca serve's orcad slot copies that Node (hash-checked) instead of downloading.
* test(serve): type the bundled Node fixture
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Overlap mobile typechecks with tests and avoid repeated filesystem probes and unused device substitutes in recording replays. Keep state isolated between loaders, give exhaustive snapshot cases an explicit execution deadline, and retire obsolete locale keys that blocked repository validation.
* Remove repeated daemon smoke builds and idle compiler slots
* Test compiler scheduling through its process interface
* Keep preview component below the existing line limit with a type-only split
* perf(claude): run the Windows Claude hook as a native orca-hook.exe
Each Claude hook event on Windows started cmd.exe (plus the user's cmd
AutoRun) and then curl.exe on top of the agent's own shell. A small signed
Rust binary now does the same synchronous POST in one process, under Git
Bash, PowerShell and direct CreateProcess alike.
- native/windows-agent-hook: std-only crate, same contract as the .cmd hook
(prints {}, env guards before stdin, endpoint re-read, 0.5s/1.5s caps,
waits for the listener reply, always exit 0). Its installed file name
selects the agent profile, so the registration stays a bare path.
- Packaged as resources/bin/orca-hook.exe (SignPath signs it with the other
inner PEs); afterPack fails on a missing or wrong-machine binary.
- Claude install copies it verbatim to ~/.orca/agent-hooks/claude-hook.exe
(temp + rename, running copy moved aside) and registers it; it falls back
to the .cmd hook when the bundle is missing, unsigned or cannot be copied.
Only Claude opts in, via windowsNativeHookExecutable.
- The managed-command matcher sweeps .exe entries alongside .cmd/.ps1/.sh.
- The CLI launcher and hook builds share windows-rust-executable-build.mjs,
which now also checks the PE machine.
Refs #20913
* fix(build): publish native Windows binaries atomically so parallel builds cannot collide
The Windows CI lane runs every orca-hook.exe suite in one vitest
invocation, each building through build-windows-agent-hook.mjs. The build
copied straight onto native/windows-agent-hook/.build/orca-hook.exe, and
CopyFile opens its destination unshared, so a second build (or a scanner
still holding the fresh image) failed with EBUSY.
- windows-rust-executable-build.mjs skips the publish when the output
already has the built bytes, otherwise stages a per-process temp file and
renames it over the output, retrying briefly while another handle holds it.
- The test fixture hands each suite a private copy of the build output, so
no suite reads or starts the shared file while another build replaces it.
Refs #20913
* refactor(claude): simplify the native Windows hook to one Claude binary
Review follow-up on the native hook (#20913): the same behaviour with less
machinery.
- The binary is built, shipped and installed as claude-hook.exe, so the
installed name matches its PE OriginalFilename. It is Claude-only, so the
file-name profile table and the per-agent opt-in flag are gone; the
registration is gated by the existing Claude-only usesWindowsEntry check.
- Install and refresh reuse writeManagedScript / refreshManagedScriptIfPresent
(now accepting bytes): temp file, rename, skip when identical. A copy whose
hook is still running cannot be replaced; install then registers the .cmd
hook and refresh keeps the old build until a later start, instead of the
rename-aside and *.retired sweep.
- The install-time Authenticode-presence check is dropped. Release CI now
requires resources\bin\claude-hook.exe to be signed (release-cut and the
signing rehearsal), next to resources\bin\orca.exe.
- getStatus counts either Windows form through findManagedEventHookForms in
hook-settings.ts, replacing managed-event-hook-status.ts.
- Rust: plain Claude constants, one endpoint-file lookup (last assignment
wins, as under `call`), one request write and a deadline-bounded drain.
Guard order and form encoding are unchanged.
- Build: PR CI builds the hook once before the Windows tests, so the suites
only read it; the launcher publish goes back to copyFileSync and the retry
loop and its test are gone. The PE-machine check lives only in afterPack.
Cargo.lock joins both crates' build fingerprints, and the launcher's alias
exports are removed.
Refs #20913
* test(ci): run the managed hook stdin-lifecycle suite in the Windows lane
Its claude-hook.exe case (and the other Windows-only cases) are gated per
test, so the lane-registration guard did not require the file and it ran
only on Linux, where those cases skip. Register it in both Windows lists.
Refs #20913
* feat(agent-hooks): ship the native hook as orca-agent-hook.exe <agent>
The native Windows hook (#20913) becomes one general binary that stays
Claude-only in behaviour.
- Named orca-agent-hook.exe everywhere: build output, PE OriginalFilename
and InternalName, resources\bin, the afterPack check, the installed copy
in ~/.orca/agent-hooks, and the release signed-target lists.
- It takes the agent as its first argument and Claude registers
`<path> claude`. The CLAUDE_JOB_DIR exit and the Grok/Devin drain belong
to that agent; an unknown or missing agent answers {} without reading
stdin. The managed-command matcher owns `orca-agent-hook.exe claude` and
still owns the old claude-hook.exe entry, so installs migrate; install
deletes a leftover claude-hook.exe when nothing runs it.
- A copy still running from the previous build no longer forces the .cmd
fallback. Identical bytes are not rewritten; otherwise the temp file is
renamed over the target, and when Windows refuses because the image is
mapped, the running copy is renamed to orca-agent-hook.exe.old first.
Install deletes a stale .old; only when that copy is also still running
does the install fall back to .cmd. writeManagedScript and
refreshManagedScriptIfPresent take the aside path, sharing one helper.
Refs #20913
* fix(agent-hooks): roll back a failed aside rename and keep an installed hook exe
Design-review follow-up on the native hook (#20913).
- If the running copy was moved to orca-agent-hook.exe.old but the new
file then could not take its name, the copy is renamed back before the
error is rethrown, so settings.json never points at a missing exe. The
rename helper now owns the `.old` name, next to the comment that
explains it; a mocked-fs unit test covers both the sync and async paths.
- A failed write, or a build with no bundled exe, keeps registering a copy
that is already installed (every build knows `claude`), so a dev checkout
and an installed build no longer flip settings.json between forms.
- The claude-hook.exe migration is gone: no release ever shipped that
name, so the legacy sweep, the `.exe` matcher needle and their test are
removed.
Refs #20913
* fix(agent-hooks): derive the native hook's agent with win32 path rules
getWindowsNativeHookAgent took the host's basename of the managed script
path, so on a POSIX host (Linux CI) a Windows path was not split and the
registered command became `orca-agent-hook.exe C:\...\claude`. It now takes
the path and applies path.win32.basename itself. The install destination
and the `.old` aside stay on host paths: they name files the host opens.
Refs #20913
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
* fix(claude): register a statusLine Windows PowerShell can run
Claude runs the statusLine under Git Bash when present and otherwise under
PowerShell, which cannot parse the POSIX runtime-home command Orca registered.
On Windows without Git Bash the statusline never posted rate_limits, so the
live Claude usage feed went dark. Register the same shell-neutral .cmd
invocation the lifecycle hooks use; the old form is still recognised as
Orca-owned and replaced, and a user's own statusLine is never touched.
* fix(claude): keep the statusLine free of a per-tick PowerShell launch
A profile path the bare form cannot carry (a space, non-ASCII, quotes) fell
back to the encoded PowerShell launcher, which would start a hidden PowerShell
on every statusline tick and print "{}" if the script went missing. Register
the bare .cmd path only when it is cmd-safe and keep the runtime-home form
otherwise, so Git Bash users on such paths still run the .cmd directly.
Fold the registration tests into claude-status-line-install.test.ts and add a
real Git Bash run for a profile path with a space.
* test(claude): pair the stubbed win32 platform with win32 basename
The Windows statusLine case fed a Windows path to getManagedCommand on a
Linux runner, whose posix basename does not split backslashes. Production
only takes that branch on real Windows, where node:path is path.win32.
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
* fix(daemon): verify daemon PID identity from the native Windows process table
The Windows daemon PID check forked powershell.exe Get-CimInstance through
node:child_process with no windowsHide, flashing a console and costing
~850ms per check. Read the command line and creation time from the shared
windows-process-table reader instead, and route the POSIX ps queries
through @orca/process-host.
Refs #27390
* refactor(daemon): drop the ps migration and trim the PID identity tests
Keep #27703 to the PowerShell removal: restore the macOS/Linux ps paths,
their tests and the process-host baseline entry. Collapse the mocked
Windows identity cases into one table, keep only the real-addon native
case, and move the creation-time fallback beside queryWindowsProcess.
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
Preserve all 52 Markdown cases while consolidating one file and importing existing direct owners. Advance editor debounce windows through public selective timers, exclude unrelated lint tests from Performance PR triggers, and remove a pane assertion case entailed by retained coverage.
* ci: ratchet synchronous child-process launches in main and shared code
The Electron main process freezes every window while a synchronous child
process runs, and on Windows each launch is slow because antivirus scans
it. Nothing stopped new synchronous launches from landing in main-process
code, and the spawn observer behind ORCA_MAIN_THREAD_DIAGNOSTICS only saw
async spawns, so the worst blocks were invisible to it.
Ratchet: check:main-sync-spawn-ratchet parses production src/main and
src/shared files (test-only paths excluded) and fails when a file not in
config/main-sync-spawn-baseline.txt reaches execFileSync, spawnSync or
execSync from child_process, runProcessSync from @orca/process-host, or
one of the synchronous secure-file writers (they run icacls on Windows).
Named, aliased, namespace, default, require() and import() forms count.
A baseline entry whose file no longer launches synchronously fails as
stale, so the list can only shrink. It runs in lint and in the PR
static-analysis job next to the process-host import check.
Observer: runProcessSync now reports to the spawn observer, timing the
whole call (that is how long the thread was stuck) and reporting from a
finally so failing, throwing and timed-out children are counted too.
Runtime behavior is unchanged except that the diagnostics observer now
sees synchronous launches; with no observer registered the sync path
pays one extra branch.
* ci: count sync launches per file and gate the repo's sync wrappers
The sync-spawn ratchet only tracked which files launched synchronously, so a
file already on the list (wsl.ts, win32-utils.ts, git-exec-file.ts) could add
more launches unnoticed, and new callers of the repo's own synchronous
wrappers passed CI.
The baseline now stores a per-file count of uses, in the same format as the
focus-read ratchet: a rise or a new file fails, a lower or zero count fails
until `pnpm check:main-sync-spawn-ratchet --prune` lowers it, and --prune
never raises an entry. Every value reference to a gated function counts
(call, hand-off, alias and its uses, re-export); imports alone count nothing.
Gated wrappers: gitExecFileSync (and the git/runner re-export),
restrictWindowsPathSync, hardenSecurePath when called with sync: true,
grantDirAcl, getWslHome, listWslDistros, wslUncDirectoryExists,
isWslAvailable (and the wsl.ts re-export), isPwshAvailable. Their 18
current caller files join the baseline.
Failure messages now give advice per family: the async process-host API for
child-process launches, the async twin for wrappers, and for the secure
writers a note that no async secure writer exists yet, so the sync one stays
and must never become a plain write (that drops the Windows ACL hardening).
Also refresh two stale blockMs descriptions and drop a no-op line from the
spawn-observer "stays silent" test.
* ci: count hardenSecurePath unless visibly async, and tighten ratchet tests
hardenSecurePath was counted only when `{ sync: true }` was written inline,
so moving the options into a variable or a spread dropped the count and the
check then asked for a prune. It now counts every use except a call with no
options or a spread-free object literal whose `sync` is absent or false.
Also: resolve backslash paths in the importing file as well as the
specifier; still visit heritage and instantiation expressions
(`extends mixin(fn)`, `fn<T>`); state the reference-only counting limit;
and point the secure-writer advice at raising this file's baseline count.
Tests cover the hardenSecurePath shapes, the alias chains that need the
fixpoint loop, and a prune that must not raise a grown row.
* test(ci): pin the main sync-spawn ratchet background step
* fix: count secure hardening calls with spread arguments
Publish Linux Wayland text copies with an independent clipboard owner, then clear Chromium's stale source only after publication succeeds. Use data control where available and GTK's clipboard bridge on GNOME. Keep macOS, Windows and X11 on their existing Electron writers.
Temporary workaround for https://github.com/electron/electron/issues/54645, with removal checks documented in the native README.
Verified full build, macOS arm64 packaging, Linux x64/arm64 library floors, clipboard/platform tests, real Wayland copy/paste and CI packaging.
Fixes#26917Fixes#23229
* wip: legacy relay bridge client
* Revert "fix(ssh): end an older Orca's relay terminals from the connect refusal (STA-10166) (#27748)"
This reverts commit d4a55f583e.
* feat(ssh): bridge terminals an older relay still runs into their old tabs
* test(ssh): cover the legacy relay bridge client, target bridge, transport and gate
* test(ssh): type the bridge test fakes without assertions
* test(e2e): a 1.4.224 relay's live terminal survives the upgrade on a Docker SSH host
* ci(e2e): route legacy relay bridge changes to its Docker spec
* fix(ssh): bridge delta moves too, and never read a non-exhaustive relay listing as exit
* fix(ssh): keep retrying a bridge for panes that mount before connect; end panes proven gone
* fix(ssh): bridge 1.4.224's detached leases through export preflight; publish every leased pane before the first look
* test(e2e): build the released relay out of process so Playwright never loads ESM build tooling
* fix(renderer): drop the recovery hook main removed from PtyTransport
* test(e2e): show the converted worktree before reading the bridged tab
* test(e2e): reconnect and wait for the converted host before opening the bridged tab
* fix(renderer): match bridged leases to tabs mirrored from the managed server
* test(e2e): find the mirrored relay-era tab under any worktree key
* test(e2e): open the converted worktree before looking for its mirrored tab
* test(e2e): a retired lease is terminated or pruned
* fix(ssh): back off attaches a listed terminal refuses; one pane per bridged shell
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* Skip unnecessary compatibility-boundary parsing and benchmark it in CI
* Use pinned preflight Bun for compatibility checks and localization extraction
* Measure two concurrent Bun projects in full preflight and update its scope test
* fix(bench): make the idle-CPU benchmark run and report CPU on Windows
Three independent Windows defects in the idle-CPU benchmark (its report is
what Windows performance work would cite):
- `buildAppIfNeeded` spawned `npx`, which is `npx.cmd` on Windows, so
execFileSync failed with ENOENT before any sampling. Build through
`config/scripts/run-electron-vite-build.mjs` instead — the repo's own
Windows-safe wrapper, which also runs the boot-graph verification.
- `readWindowsProcesses` returned `cpuTimeSeconds: null`, so the sampler's
per-process CPU delta (idle-cpu-process-sampling.mjs:198) fell back to
the hardcoded `percentCpu: 0` and every CPU KPI in the report was zero.
Read `UserModeTime`/`KernelModeTime` from `Win32_Process` (100 ns units)
and keep `null` when a host does not return them.
Measured on Windows, same options before and after the CPU fix:
| class | mean CPU before | mean CPU after |
| --- | ---: | ---: |
| renderer | 0 | 2.20 |
| gpu | 0 | 2.08 |
| utility | 0 | 0.11 |
| agent-or-node | 0 | 0.06 |
| total | 0 | 4.51 (p95 8.40) |
RSS is unchanged (934 MiB -> 937 MiB), so only the CPU reporting changed.
Refs #25235
* fix(bench): reject a missing Win32_Process time instead of reading it as zero
Review follow-up (CodeRabbit). `Number(null)` is `0`, which is also a real
counter value, so an absent `UserModeTime`/`KernelModeTime` passed the
finite check and the sampler would report a kernel-only delta instead of
taking the fallback. Require an actual number from the CIM payload.
Verified on Windows that the real payload still reports CPU once the check
is tight: total mean 4.40%, p95 14.48%, renderer 3.51%.
* test(bench): cover the Windows CIM CPU time mapping
The mapping was only reachable through Get-CimInstance, so no test ran it.
Lift it into cimCpuSeconds and assert the 100ns-to-seconds sum plus the
missing / string / non-finite cases that must stay null.
* fix(bench): read the Windows process table as UTF-8 so JSON.parse survives
PowerShell writes to a redirected stdout in the OEM code page (437 here),
which best-fits characters such as U+2022 and U+2192 in any running
process's command line to raw 0x07 / 0x1A bytes. ConvertTo-Json had left
them unescaped because they are not control characters in UTF-16, so the
sampler's JSON.parse failed with "Bad control character in string literal"
whenever such a process happened to be alive -- hence intermittent.
Set [Console]::OutputEncoding to UTF-8, as the app's own CIM scan does,
and cover it with a Windows-only test that fails without the fix.
Fixes#25236
* fix(bench): pass the local runtime to fetchRepos in the idle-CPU fixture
fetchRepos now requires a runtimeEnvironmentId, so the fixture's bare call
threw "Cannot read properties of undefined (reading 'runtimeEnvironmentId')"
before sampling on every platform. null selects this app's own catalog, as
the e2e helpers already do.
* ci(bench): run the Windows process-table test in the Windows lane
The UTF-8 regression test self-skips off win32, so it must be registered in
both Windows lists or it runs on no machine.
---------
Co-authored-by: Paolo <paovalletta@hotmail.it>
Co-authored-by: Orca Worker <orca-worker@localhost>
* feat(relay): declare Asia spare cell c35 as migration-only
Adds a seventh asia-east2 cell at the C34 shape (cap 3000, 6000 request
units, pool 16, e2-standard-4) in asia-east2-a, pinned to image F. It gets
its own topology and registration wave but no promotion wave, so the
admission script and workflow refuse to promote it; it stays a
migration-only spare and out of the fleet pool list. Mirrors #25336.
* test(relay): accept the c35 spare under the same-cap capacity allowlist
* fix(skills): judge official skill updates by ownership, not snapshot matching
Orca decided whether an installed official skill was pristine by matching its
bytes against a shipped list of past snapshots, falling back to the skills CLI's
lock hash. Copies installed between releases, CRLF copies, and installs whose lock
recorded a sha256 hash all read "doesn't match the official version" and could not
be updated (#26841), and older content could be labelled newer and never offered.
Now an official skill is Orca's when the skills.sh global lock says it came from
stablyai/orca, and it is current only when its listed files equal the bundled
manifest (line endings and sidecars ignored); anything else is outdated. Install
and update are one pinned `skills add .../tree/v<appVersion>` command, used by the
in-app runner, `orca skills update`, and the Settings rails. The update dialog
preselects Orca-owned rows and lists same-named skills from elsewhere unchecked.
Removes the snapshot registry, release mapping, git-tree identity, lock trust and
convergence logic, and the release-cut provenance step.
* ci(skills): run the pinned-add round trip on a skills CLI that can fetch a commit
* fix(skills): let orca skills update target the shared folder on a bare host
Update became the same pinned add as install, so it inherited install's
refusal when no agent is detected. That broke the bare-host update the old
command guaranteed, and the built-CLI check (update --dry-run, no --agent) on
CI runners. The in-app runner already fell back to universal; both now share
one detectSkillsCliUpdateAgentKeys rule.
Also brings the CLI skills docs off `npx skills update`.
* refactor(skills): derive the update selection from the grouped rows
The selection hook took the live and the retained inventory, re-derived Orca
ownership from installations beside the grouping's own orcaOwned, and kept a
separate display count. Groups already carry both eligibility and ownership,
so the hook reads them once; the button's existing live-scan gate is the only
authorization check. The unselected line is inlined into the headline.
* refactor(skills): drop helpers the ownership design made redundant
- isOwnerManagedSkillScope only negated into the participation check once
plugin caches stopped participating; fold it in.
- Eligibility re-checked resolvedPath/physicalIdentity, which observation
already turns into 'inaccessible'.
- The manifest verifier probed access() before a readFile that throws anyway.
- orchestration-install-command.ts only forwarded one constant from the lib
module its three importers already import.
* i18n(skills): stop naming npx skills update in four skipped-copy strings
The es/ja/ko/zh cantUpdateReason and tipDuplicate strings still named
`npx skills update`, which Orca no longer runs for its skills. Say 'the update
command' instead.
* refactor(skills): inline the single-use pinned source URL helper
orcaSkillsSourceUrl was exported but only called by the builder beside it.
* fix(skills): let an update choice lapse when the install source changes
The selection froze every checked name, Orca's preselection included, the
moment the user ticked anything. A re-check that then attributed a name to
another source kept it checked, replacing it without consent. Store only the
user's explicit choices, keyed by name and lock source, and default to
ownership otherwise.
* fix(skills): label the empty update action 'Update skills', not 'Update 0 skills'
The button stays whenever a row is updatable, so unticking every row (or a
dialog with only non-Orca copies outdated) doesn't reflow the footer. At zero
selected it stays disabled and reads 'Update skills' instead of a zero count.
The label lives beside the dialog's other count copy.
* fix(skills): let keyboard focus open the Not from Orca tooltip
The source of a non-Orca copy is only in the tooltip, and its trigger was a
plain span. Make it focusable like Orca's other tooltip-on-text triggers
(tabIndex 0, cursor-help, the row primitive's focus ring), as in the agreed
mock.
* test(skills): hold the round-trip add equal to the shared install builder
The CI round trip hardcoded its skills add argv, so it could drift from what
Orca runs. A .mjs script can't import the TS builder (extensionless imports),
so the argv moves into an exported roundTripAddArgs and a unit test asserts it
is the builder's unattended add with the tag swapped for the PR commit. The
script runs under the same main guard as the manifest generator.
* i18n(skills): translate the skill row's location count and duplicate reason
es, ko and zh still carried English for SkillUpdateRow.oneLocation and
manyLocations, and es and ko for SkillFreshnessRow.skippedReasonDuplicate.
* fix(skills): pin official skill installs to the build commit and skills CLI
`skills add .../tree/v<version>` only works for stable and RC builds: hourly,
daily and adhoc builds publish elsewhere, so stablyai/orca has no tag for their
version and both install and update failed there. Every build is made from a
commit that exists in stablyai/orca, so the build now stamps `git rev-parse HEAD`
and the one shared builder pins to `tree/<commit>`, which equals the bundled
skills by construction. Main, renderer, web and vitest get it as
ORCA_BUILD_COMMIT (replacing ORCA_APP_VERSION); the tsc-built CLI reads it from
the compiled out/package.json boundary. A missing or malformed commit fails the
build or the command with a clear error instead of building a bad URL.
The builder also pins the skills CLI (`npx skills@1.7.2`): 1.5.x cannot fetch a
commit, and a bare `npx skills` can reuse a stale cache or resolve to another
binary (#18687). The CI round-trip script reuses the same constant.
* ci(skills): run the round trip on the shared pinned skills CLI version
* docs(skills): document the commit-pinned skills add
* refactor(skills): keep the pinned skills CLI version in the install builder
skills-cli-version.ts existed only so the plain-Node round-trip script could
strip-load a .ts file, which needed --disable-warning in CI. The version now
lives beside the builder that uses it; the script keeps a literal, and the
existing round-trip test already asserts its argv, skills@<version> included,
equals the builder's.
* refactor(skills): validate the build commit once, where it is resolved
resolveBuildSourceCommit already refuses anything but a full hash, and both the
define and the CLI's stamped package.json come from it. The builder's second
check could only fire as a module-load throw in the renderer's command
constants. Also drops the resolver's unused cwd parameter.
* test: stamp a fixed build commit instead of running git in the vitest config
Tests only interpolate ORCA_BUILD_COMMIT, so they need not read HEAD (or fail
outside a git checkout) on every run.
* refactor(settings): match only skills add in the Windows npx preflight
Every official skill command is now the builder's skills@<version> add; no
rail emits skills update any more.
* fix(skills): fail an update that leaves a broken skill link behind
Eligibility already skips names with a broken link before the run, so one
afterwards is the run's own damage; report it instead of claiming success.
* ci(release): refuse to tag when the skill manifest is stale
The removed --release step implicitly checked the committed manifest against
skills/. Keep that guarantee with a verify-only run; it writes nothing.
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
* test(e2e): record mixed-version, two-client and phone layout baselines on main (P3)
* test(e2e): P3 review fixes: reconnect and I/O proof, restart order bounds, Linux-only baselines
* test(e2e): re-record Linux phone and serve-restart baselines
* test(e2e): P3: echo drawn in the desktop pane; bound the group selection after restart
* test(e2e): re-record serve-restart group selection
* test(e2e): name P3 baseline helpers for what they record
* fix: preserve module URLs in the headless server bundle
* test: exercise structured chat on the packaged Node server
* test(orcad): run the packaged chat check from the desktop's server template
The chat check loaded the server bundle directly, so a desktop build missing its
server template still passed. Materialize the slot from the built template the way
the desktop does, and replace the fixed streaming pause with a test-owned
acknowledgement so the partial-reply assertion can't race.
* Connect the packaged chat test through a paired connection
Main now sends remote runtime requests over a paired connection rather than a
pairing offer, so the test converts the server's offer once when it is ready.
* ci: the layout oracle gates merges
An oracle-only job joins verify's needs. It runs only the two layout oracle
specs through the existing e2e workflow, routed by layout writer paths; a PR
outside the route skips it and verify treats the skip as a pass. The route is
widened to the terminal store, terminal pane, graph sync, frozen mirror and
compatibility folders while the layout-holder stack is open. The oracle specs
leave the advisory e2e job so a PR does not run them twice.
* ci: the layout oracle tests the merge commit and routes its harness and stack writers
Review fixes: run on the merge commit so a stale-base PR cannot pass on an old
main; route the oracle's own e2e harness and runtime client (a change there broke
main's oracle without running it), and the window-side layout writers the stack
edits; move the route into its own module.
* ci: route the remaining layout writers the stack edits to the layout oracle
* refactor(layout): keep compatibility code in named folders behind one entry
Moves the old-client translators into src/shared/workspace-layout/old-client/
and the remote layout snapshot recorder and persistence into
src/main/remote-layout-snapshot/, each behind an index.ts. A new
check-compat-boundary-imports script, run by pnpm lint and the PR workflow,
fails when code outside a compatibility folder imports past its index or
when a file other than the folder's named importer imports it. Moves only;
no behaviour change.
* test: drop whole-repo scan from boundary check unit tests; CI step runs it
* fix(lint): list only existing compatibility folders; resolve renderer aliases in renderer files
* chore(ratchet): layout-writer ratchets for main and renderer
Two per-file ratchets that may only go down: main-process callers of the
Store's layout setters plus direct edits of saved session partitions, and
renderer writes of the window's layout and per-view selection fields
(including computed-key and spread writes, counted where the key or object
is built). The frozen-mirror writers are exempt. Wired next to the
focus-read ratchet in lint and the PR workflow.
* chore(ratchet): count only membership writes of editor and browser entries
A .map over the existing openFiles or browser tab list keeps its entries
and order, so it is a content update (draft, git status, URL) and is no
longer counted by the renderer layout-writer ratchet.
* chore(ratchet): ignore variable keys and pass-through shorthands
A variable key such as exitCodes[ptyId] names no field, so only string
keys count. A shorthand key counts only in an object handed to set,
setState or a Store writer; elsewhere it passes a value on.
* chore(ratchet): prune renderer baseline after main moved