mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
740887fbbb32d9e9e8564300080dc2bd844b59c7
50
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e86cba888b |
build: reduce native dependency installs to the host platform (#20420)
* Reduce native dependency installs to the host platform * Remove install policy documentation * Guard cross-arch packaging and scope release installs to the runner electron-builder only logs a warning for a missing extraResources source, so a host-only install silently shipped a foreign-arch slice without its natives — `pnpm build:mac` on Apple Silicon produced an x64 DMG with no sherpa-onnx-darwin-x64 and no @parcel/watcher-darwin-x64. The previous beforePack hook covered only win32. - Add assertPackagedNativeVariantsInstalled, an arch-aware check over the target's sherpa-onnx, @parcel/watcher, and (on Windows) node-gyp addons. beforePack now runs it for every platform, with remedies split: another architecture comes from install:release, the os:win32 addons need a Windows host. - Drop --os from the release installs. Every packaging job already runs on a runner whose OS matches its target, so only the macOS lanes need extra breadth, and only on CPU for their x64+arm64 config. Windows and Linux packaging return to a plain host-only install. - Add --frozen-lockfile to install:release so a bare run cannot rewrite the lockfile. - Restore the install policy reference doc and the CONTRIBUTING note, plus the rationale comments dropped from the runtime contract test. - Gate the packaging-closure assertions on whether the Windows addons are installed rather than on the host OS, so a cross-arch install exercises them off Windows too. - Make the workflow contract test read `run:` steps as well as retry-action commands, and enforce host-only scoping on the non-macOS packaging lanes. - Remove the unreferenced install measurement script; its numbers live in the policy doc. * Track the install policy doc and index it from AGENTS.md docs/** is ignored behind a per-file allow-list, so the new reference doc was only committed via git add -f and future edits would be skipped. Add it to the allow-list and give it an AGENTS.md entry like every other tracked reference doc, so the host-only install rule is discoverable before someone packages a second architecture. * Route Windows-lane removals through the retrying helper Adding these four specs to the PR Windows lane pulled them into the windows-lane-tree-removal-boundary ratchet, which failed on 20 raw recursive removals. On Windows a bare rmSync races a handle the OS has not released, throwing EPERM after the assertions already passed and reporting a green test as a lane failure. * Adapt the packaging guard to the vendored Windows registry addon main vendored windows-native-registry as the workspace package @orca/windows-registry (#20438). A workspace link resolves on every host, so including it in the installed-Windows-addons checks proved nothing. @vscode/windows-process-tree is the only os: win32 npm addon left, so it alone decides whether the win32 resource plan resolves. |
||
|
|
f37d2fec97 |
fix(linux): land the reviewed Linux packaging stack on main (#18100)
* fix(linux): give the CLI one entrypoint by extracting the AppImage once
* refactor(linux): trim AppImage CLI registration seams
* test(cli): assert registration lock serialization
* fix(linux): fence AppImage terminal shim mounts
* fix(linux): accept extracted AppImage runtimes with APPDIR only
* docs(linux): make headless AppImage extraction runnable
* refactor(linux): import bundled launcher directly
* fix(linux): reclaim superseded AppImage payloads and packaged symlinks
Pruning removed 3215 of 3216 files from a superseded generation and always
stranded resources/app.asar, leaking ~105 MB per version update. Electron's
asar shim reports a *.asar file as a directory, so the recursive remove tried
to rmdir a real file and failed with ENOTEMPTY; the .catch(() => {}) hid it.
Reproduced end to end on Ubuntu 24.04: 519M -> 623M across one update, and
519M again once the payload is actually reclaimed.
removeExtractedAppImagePayload holds process.noAsar for the removal, counted
so overlapping removals cannot hand the shim back early, and the prune site
now warns with the path instead of swallowing the rejection. All three
removal sites use it -- staging cleanup and displaced roots leaked the same
way.
Also reclaim symlinks left by a packaged deb/rpm install, which the
extracted-cache-only rule turned into a hard conflict on a deb -> AppImage
migration, and name the remedy in the conflict error.
* fix(linux): bound the CLI registration lock wait
`retries: 1000` caps the attempt count, not elapsed time, so at up to 1s per
attempt an IPC-driven registration could hang ~16 minutes against a wedged
holder with no feedback.
A legitimate holder is bounded by the extraction timeout, so wait that plus
slack and then fail with a message naming the lock file, rather than hanging.
`maxRetryTime` is forwarded verbatim to the `retry` package by proper-lockfile.
* fix(linux): stop re-extracting the AppImage on inode metadata churn
The extracted-payload cache key hashed ctime alongside dev/ino/size/mtime.
ctime moves on any inode metadata write -- `chmod +x`, which every AppImage
user is told to run, plus `chown`, an ACL or SELinux relabel, and a backup
restore -- none of which alter a byte of the payload.
Measured on Ubuntu 24.04: `chmod +x` leaves dev, ino, size and mtime
identical and moves ctime alone, so the key changed and the next launch paid
a full ~519 MB re-extraction and a multi-second stall to rebuild a payload it
already had, then pruned the old generation.
Key on content identity instead. An in-place content change moves mtime and
almost always size; a replacement moves the inode. The existing
replace-in-place test still passes.
* fix(linux): stop CLI commands from falling through to Chromium startup
* refactor(cli): remove redundant command membership check
* test(cli): cover command-named project selectors
* fix(cli): redirect the open-url command before startup
* test(linux): cover AUR serve wrapper flags
* fix(linux): tighten CLI launch detection
* fix(linux): respect CLI flag value boundaries
* fix(linux): strip injected Chromium switches from CLI args
* fix(linux): report a missing display instead of dying in uv_close
* refactor(linux): read display locks without a preflight race
* fix(linux): preserve unverified external displays
* chore: format reliability gate manifest
* test(packaging): split runtime resource checks
* fix(linux): fail serve when no display is available
* fix(linux): do not treat a lockless X socket as a dead display
An X server writes its lock beside its socket and both survive a crash
(verified against Xvfb under SIGKILL), so a socket with no lock was never
left by a crashed server. It is an endpoint published from elsewhere: a
container bind-mounting only /tmp/.X11-unix, WSLg, or a foreign PID
namespace. Declaring those dead made the desktop gate exit(1) on displays
that work, with no workaround, and the serve gate refuse to start.
Liveness now splits by ownership. A foreign DISPLAY trusts a lockless
socket; Orca's own :99 does not, because removeStaleDisplayArtifacts
unlinks the lock before the socket and so manufactures that state itself --
adopting it would resurrect the orphan-socket bug and stop the cleanup from
self-healing. The stale-lock rejection is unchanged.
Also correct four doc statements this behaviour falsified.
* fix(linux): fail closed when a stale socket blocks the Xvfb rebind
Readiness only checked that /tmp/.X11-unix/X99 exists. A stale socket we
could not unlink still exists after our own Xvfb refused to bind, so Orca set
DISPLAY to a dead server and Chromium died in Ozone init.
Measured on Ubuntu 24.04 against the pre-fix build: with a leftover :99
socket and no lock, serve exits 139 (SIGSEGV), the socket inode is unchanged
before and after, and no lock is recreated -- it neither cleaned up nor
respawned. To a user that is a crash, not a misconfiguration.
This is reachable in the documented topology, where orca-xvfb.service has no
User= and runs as root while serve runs as User=orca: /tmp is sticky, so the
orca uid cannot unlink a root-owned socket, rmSync fails, and Xvfb exits with
the display already active.
Readiness now requires the display to actually be live -- our socket plus a
lock naming a running process -- so the same state reports an unusable
display and exits 1 with the existing diagnosis.
* fix(linux): recognise abstract X sockets and inherited Wayland fds
Two display setups this gate could not prove were refused outright, and on the
desktop path that is app.exit(1) with no workaround.
An X server may bind only the abstract namespace (`@/tmp/.X11-unix/X0`), which
leaves no filesystem socket to stat. Abstract addresses are kernel-owned and
vanish the moment the owner exits, so an entry in /proc/net/unix is proof of a
live server -- no lock file needed and no stale entry possible. Verified on
Ubuntu 24.04, where 139 such addresses were present.
WAYLAND_SOCKET is an already-connected fd handed over by the compositor, so
there is no path to stat and WAYLAND_DISPLAY may be unset entirely. Its
presence is the display.
Both are consulted only after the filesystem-socket check fails, so no
existing verdict changes.
* fix(linux): never treat Orca's own display number as a foreign endpoint
Recognising a lockless X socket as live is correct for an endpoint published
from elsewhere -- a container bind mount, WSLg -- because an X server writes
its lock beside its socket and both survive a crash. It is wrong for
VIRTUAL_DISPLAY_NUMBER, because Orca's own teardown unlinks the lock before
the socket and so manufactures that exact state.
The managed branch was already strict, but a caller that sets DISPLAY=:99
explicitly takes the foreign path and skipped it, accepting a dead display
left by Orca's own interrupted cleanup. Route the managed number through the
strict probe on both paths.
Found by an adversarial audit of the asymmetry introduced earlier in this
branch; the documented systemd topology is unaffected because its Xvfb writes
a real lock.
* test(linux): add a packaged-artifact contract for the CLI launch paths
* test(linux): avoid buffered serve readiness detection
* test(linux): signal AppImage serve owner directly
* test(linux): tolerate readiness timeout boundary
* test(linux): add startup margin to shutdown oracle
* ci(linux): give package contracts timeout headroom
* fix(ci): route all Linux packaging contract changes
* test(linux): poll shutdown readiness without tail leaks
* test(linux): bound shutdown cleanup grace
* test(linux): assert on CLI output, not the harness's own control lines
run-cli-case.sh echoes `RESULT status=N case=<name>`, and the two cases named
*-skills asserted `expectOutput: 'skills'`. That substring was satisfied by
the case name in the harness's own line, so 2 of 8 cases asserted nothing
about the command -- gutting `skills` entirely would still have gone green.
Control lines are now excluded before matching, and both cases assert the
rendered help header, which only real help output produces. Verified on an
Ubuntu 24.04 host: 8/8 still pass against a stack-tip AppImage.
Also register the gate in reliability-gates.jsonc, which #15085 added a CI
Docker gate without. Red/green is recorded from a stock release AppImage
failing 4 of 8, three of them at status 133 (SIGTRAP).
* fix(linux): require static AppImage runtimes (#17319)
* test(linux): reject a wrong-architecture native binary at packaging time
Cross-building the arm64 slice on an x64 host silently packed an x86-64
`pty.node` -- the rebuild logged "Forcing native rebuild for linux-arm64" and
shipped the host's binary anyway. Every gate here inspects symbol versions,
which are perfectly valid on the wrong architecture, so nothing noticed.
Observed on a Raspberry Pi 5: the packaged app loaded, then failed with
"Failed to load native module: pty.node", and the launch contract reported
3 of 8 cases crashed rather than naming the cause. Swapping in the aarch64
`pty.node` took the same build to 8/8.
Compare ELF `e_machine` against the slice being packaged and fail with the
offending path. Checked before the glibc pass, because a wrong-architecture
binary's symbol versions are valid but meaningless and would send the reader
down the wrong path.
Release CI builds arm64 on a native runner, so this guards local and future
cross-builds rather than a shipped artifact.
* test(linux): judge per-arch vendored binaries against their own path
The first CI run of the architecture gate failed the x64 package job on
`@parcel/watcher-linux-arm64-glibc/watcher.node`. That binary is arm64 on
purpose: the package ships every architecture and its loader picks the match,
so its presence in an x64 build is correct.
Judge a binary against the architecture its own path names, falling back to
the slice when the path names none. That keeps the case this gate exists for
-- `bin/linux-arm64-*/node-pty.node` holding an x86-64 binary, which is what
shipped to a Raspberry Pi 5 -- while letting multi-arch dependencies through.
Dry-run over the real dependency tree flags nothing for either target arch.
* fix(linux): move deb/rpm update installation outside Orca (#17318)
* fix(linux): complete deb/rpm package metadata
* fix(linux): preserve CLI link during package upgrades
* docs(linux): document local RPM build prerequisites
* fix(linux): move deb/rpm update installation outside Orca
* fix(updater): preserve Linux recovery across stale events
* fix(updater): fence stale downloaded events by active target
* fix(updater): preserve active Linux package recovery
* test(linux): keep workflow order assertion in scope
* test(updater): assert stale recovery stays silent
* fix(updater): preserve Linux package recovery after checks
* refactor(updater): keep Linux marker message with status
* fix(linux): describe the right manual update path for deb/rpm hosts
A remote host installed from .deb or .rpm now reports
manual-service-update-required, and the guidance told the operator to
"update through the service manager that starts this server" -- which is
correct for unsupported-headless-serve but wrong for a package install,
where nothing about the remedy involves the service manager.
Say both, keyed on how the host was installed.
* docs(linux): document orcad update restart safety
* docs(linux): scope restart census omissions
* docs(linux): use absolute service CLI launcher
* fix(serve): validate in-process serve options before startup (#17683)
* fix(linux): stop offering updates a distro-managed install cannot apply (#17918)
Closes #17702.
The resources/package-type marker is authoritative but never checked against
the host, so any repackager that unpacks Orca's .deb -- AUR, Nix, a container
rebuild -- inherits `deb` verbatim. Install feasibility was then computed
after a ~165 MB download, so those users got check -> download -> a card
promising an install command -> a dead end.
Validate the marker against the host: a deb/rpm marker with no matching
package manager in the trusted directories means a package manager owns this
install. This reuses the exact lists and resolver that
buildLinuxPackageInstallCommand already loops over, so a false positive is
impossible by construction -- any host flagged here would have failed with
no-package-manager after the download anyway. The gate only moves that
verdict earlier. Verified across Debian 12, Ubuntu 24.04, Arch, Fedora 40 and
openSUSE Leap: no false positive on a real deb host, correct on every
repackaging host.
The release is still reported, because the user does want to know 1.4.194
exists and to update through their distro; only the download path is closed.
`externallyManaged` is an additive optional field on the existing `available`
status, so older paired clients decode it unchanged. downloadUpdate() refuses
authoritatively, since main owns this verdict rather than the card, and
unwinds any pinned-build state first -- a Linux pinned jump resolves to
'release', and stranding isPinnedBuildActive would silently kill every
background check for the rest of the process.
Note the fix the issue suggests cannot work: electron-updater builds a
PacmanUpdater whose doDownloadUpdate looks for a .pacman asset Orca does not
publish, then dereferences undefined.
* style(cli): restore prettier wrapping on install error copy
* test(linux): re-pin the child-process ratchets and the batch-shim allowlist after the merge
|
||
|
|
97eb762b27 |
refactor(packaging): prune declaration and source-map artifacts in one walk (#17659)
* refactor(packaging): prune declaration and source-map artifacts in one walk prunePackagedRuntimeTypeDeclarations and prunePackagedRuntimeSourceMaps were byte-identical apart from their regex, and each did its own full recursive walk of packaged Resources/node_modules (~1.7s per walk). Collapse them into prunePackagedRuntimeTypeAndSourceMapArtifacts, which runs a single walk with the OR of both predicates. The two regexes are disjoint (.d.ts.map never ends in .js.map), so one pass deletes exactly the union the two passes deleted. Neither old function had a production caller outside prunePackagedRuntimeNodeModules, so both exports are replaced by the combined one rather than kept as wrappers, which would have reintroduced the duplicate walk. Also moves prunePackagedZodSources ahead of the filename walk: zod/src is removed wholesale, so traversing it first was pure wasted work. The prunes are independent, so the reorder does not change the result. * fix: correct the one-walk rationale and close the .d.mts coverage gap The comment credited predicate disjointness for making the merge safe. That is not the reason and is misleading: it implies a future overlapping predicate would break the collapse. Passes commute because pruneMatchingFiles only deletes files and never removes directories, so the tree it walks is identical each time — verified by running the old two-walk code with the passes reversed and diffing survivors. Also narrow isPrunablePackagedRuntimeArtifact to isPrunableTypeOrSourceMapArtifact (node-pty prebuilds and duplicate sherpa dylibs are prunable runtime artifacts too, but this predicate returns false for them), and add the missing .d.mts fixture so every branch of the (?:c|m)? alternation is exercised against the exact-survivor assertion. |
||
|
|
b892f05c34 | perf(packaging): ship one native runtime per target (#17528) | ||
|
|
fd9125ea8c |
feat(native-chat): Codex structured native chat restructure (#16729)
* feat(native-chat): port structured Codex sessions from restructure-recovery Rebuilds the desktop structured native-chat implementation from brennanb2025/native-chat-restructure-recovery (tip 4e31c08db3) on top of current main as a single commit, scoped to the local Codex path. Ported: - Structured agent-session core: durable record store + single-writer lease, canonical journal, agent-session wire host/attach/eviction/subscribers, `agentSession.*` RPC surface (registered via ALL_RPC_METHODS; host-side mobile allowlist included for wire compat), pty write gate, transcript additions, and the Codex app-server adapter/launch resolution. - Renderer: NativeChatStructuredSession view/composer stack, structured launch path with the single-flight guard, local structured session tabs sync, activation gate + structured inventory (read-only `agentSession.handoffStatus` probe), agent-session tabs in the tab strip, AI-vault structured session activation, and the settings pane with the parent Experimental Chat UI toggle plus the nested "Use updated structured native chat" toggle. New sessions require both flags, agent codex, no prompt, and a local non-WSL, non-Windows-host execution host (structured-native-chat-availability). - Fixes 72c013cea6 (verified Codex launch recovery), 8ddbaf5e3d (defer native terminal view switching affordances), and 4e31c08db3 (release the launch gate after a visibility retry) with their regression tests, including the third-launch-after-retry guard case. - Cross-version agent-session wire test + CI lane, packaging entries (proper-lockfile, agent-tooling asar excludes), and the wire-compat doc section. Deliberately not ported: mobile/ changes, the Claude structured runtime (only the claude-transcript-branch-proof and claude-structured-owner-identity leaf modules remain, backing the kept TUI-recovery arms), the terminal↔chat adoption/handoff flow (`agentSession.adoptTerminal`/`requestHandoff`, the handoff request engine, TUI adoption machinery, orca-runtime adoption methods), renderer switching affordances and their dead leftovers, the hook/subagent-status refactor cluster, and unrelated branch changes. The crash-during-acquisition recovery path (restart handoff adjudication, restore/reverse re-acquire, lease schema handoff keys) is kept because every plain direct launch depends on it; a trimmed handoff coordinator exposes only status/restore/close. Branch edits that targeted files main has since split (ipc/pty.ts, worktrees.ts, rpc/methods/terminal.ts, useIpcEvents, pty-connection, store/slices/terminals.ts, runtime-types, web preload) were re-applied to the split modules, preserving main's newer logic (Windows CIM fallback, browser tab close rework, cold-restore resume flow, dispatcher threading). Known seam: the mobile clipboard image-provenance CONSUMER gate ships (agentSession.send refuses unproven mobile image refs with agent_session_image_untrusted) but the producer hunk in rpc/methods/clipboard.ts stays with the unported mobile cluster, so mobile image sends into structured chat fail closed until that side ports. * fix(native-chat): trust only authenticated local image uploads * fix(build): preserve Windows process-tree patch application * test(windows): include process creation time in addon fixture * fix(build): run windows-process-tree node-gyp from the physical package dir gyp expands the node-addon-api dependency by probing node, whose cwd resolves to the package's physical directory in the store, so the emitted target is a store-relative ../../../../node-addon-api@... hop. gyp then resolves that hop against the rebuild cwd; from the node_modules symlink/junction it escapes the store and configure fails with "node_addon_api.gyp not found" (run 32999886072). Rebuild from realpath(package dir) so both bases agree, matching how the package manager itself runs native install scripts. The regression test replays gyp's expansion+resolution against the planned cwd and fails without the fix. * fix(native-chat): keep chat tabs visible through terminal closes and empty-worktree launches Two proven blockers in the native Codex tab contract: closeTerminalTab pre-empted the canonical unified close. With one terminal left it deactivated the worktree on a terminal/editor/browser-only check, blanking a workspace that still held a renderable agent-session tab; with two or more it pre-picked a successor from terminal entities only, re-stamping the group active before closeUnifiedTab's MRU/neighbor repair could land on the chat tab. Successor choice now defers to the unified contract whenever the terminal has a unified row, and deactivation is gated on the unified renderable count (matching leaveWorktreeIfEmpty), with the legacy pre-pick kept only for terminals without a unified row. A structured session created on an empty worktree was published into the host's headless group while preserveLocalLayout froze the local layout, leaving the tab in store but permanently off screen. A preserveLocalLayout owner now always takes client-owned placement — repairing a rendered leaf whose group record is missing, or materializing a rendered group on a truly empty worktree — and applies the client-derived layout repair while still rejecting host-authored layout. Regression tests drive the real store through closeTerminalTab (git worktree and folder workspace) and the real snapshot applier for the empty-worktree adoption states; all fail without the fixes. * fix(native-chat): close stale turns and retry rejected sends * fix(native-chat): retire hosted rows on structured tab activation * fix(native-chat): preserve rpc defaults across main merge * chore: format remote wire compatibility guide * test(native-chat): cover retry after unconfirmed send * fix(native-chat): reload outbox on session switch * docs(settings): disclose structured chat platform limits * fix(native-chat): await Codex launch-home preparation * fix(codex): align child-process allowlist with async trust bridge * test(identity): update inventory for tab surface refactor * fix(windows): preserve process-tree CRLF patch sources * fix(native-chat): anchor an unmatched chat echo where it was sent (#16117) * fix(native-chat): anchor an unmatched chat echo where it was sent The reported symptom was old user messages replaying below every new turn, so the conversation read as scrambled. The cause was not that the echo failed to match a transcript row. Claude consumes a mid-turn send through a `queued_command` attachment and writes no `type:"user"` record for it, so some echoes can never match, and no amount of matching will change that. The cause was WHERE an unmatched echo rendered: buildMobileNativeChatTransientData appended every pending item after the entire transcript, so it re-read below each turn that landed afterwards. Render each echo directly after the transcript row it was sent against, using the baseline the send already captures. An unmatched echo is then at worst a duplicate in the right position rather than a scrambled one, and it stays visible. Echoes sharing an anchor keep send order; a send with no baseline, or one whose anchor folding dropped, still falls back to the tail. Deliberately NOT fixed by deleting the echo. Inferring from send ordering that an echo can never match, then removing it, loses the user's own text for a message the agent did receive, and it cannot fire in the common case anyway - measured drain groups are 1,017 of size 1 against 55 larger. It also escalates an existing gap: the count pass has no baseline-tail guard, unlike the glue pass, while `messages` is a 40-row window that head-trims, resets on reconnect and grows at the front on loadEarlier, so a false landing there would license deleting a DIFFERENT outstanding message. That count-pass gap is real and left for a separate change; anchoring makes its worst case a duplicate in place rather than a scrambled conversation. * fix(native-chat): preserve folded echo anchors * fix(native-chat): preserve forward-folded echo anchors * fix(native-chat): keep leading folded echoes in place * fix(workspace-cleanup): show git status for every row (#16690) * fix(native-chat): refuse structured chat on every Windows execution path canUseStructuredNativeChat only refused win32 when a project runtime resolved, so folder-workspace keys (and other keys with no project runtime) failed open into structured chat on Windows. Fail closed on win32 unconditionally after the host check, matching the settings copy: local macOS/Linux only; Windows/WSL/SSH stay on terminal chat. * fix(native-chat): restore runtime refusals behind the win32 gate |
||
|
|
4ec6bbf588 |
Kill hung WSL transcript filesystem operations via child process with route quarantine (#15381)
* fix(native-chat): kill hung WSL operations via child process
Stalled UNC file operations hold libuv permits even after the gate
timeout expires, blocking Chat tab recovery. Two stalled operations
fill both permits and freeze all WSL access until restart.
Fork file I/O for UNC paths into a separate child process. On deadline
expiry, kill the process to force the hung syscall to exit. This frees
the permit for the affected tab's next read. Temporarily quarantine the
stalled route to avoid retry storms.
* chore: drop internal review artifact from the repo root
* fix(native-chat): harden the WSL transcript fs sidecar
Review follow-ups on the sidecar isolation change:
- Only the deadline may abort running gate work. The sole waiter's
same-duration timeout fired first, killed healthy children on caller
abandonment, and settled the task before the deadline could quarantine
a stalled route - leaving the back-off dead for every dedupe:false op.
- Resolve the fork entry from out/main/chunks too: the resolver compiles
into a shared chunk, and the scanner service child has no
process.resourcesPath, so packaged WSL vault scans threw entry-not-found
(masked as an empty tree).
- Allowlist the fork env instead of spreading process.env; ambient
NODE_OPTIONS would halt or --require code into every child.
- Wrap transport faults (spawn failure, child death) in
WslTranscriptFsError('unavailable') so discovery reports them as scan
issues instead of misreading them as missing paths or empty trees.
- Gate the vitest in-process fallback on the vitest worker global so a
leaked VITEST=true cannot revert production to in-process UNC syscalls.
- Reap idle sidecar processes after 60s instead of holding them for the
app session.
- Split 'open' into its own protocol union member so the reusable-call
Exclude actually strips it from the pooled-process API.
- Guard kill('SIGKILL') against the teardown race where an exiting child
emits an unlistened 'error', and dispatch reads by handle kind before
path spelling.
* fix(native-chat): probe stalled WSL routes instead of a fixed quarantine
Remaining review follow-ups:
- Escalating route quarantine: first strike lifts after 5s so a distro
that was cold-booting when its op hit the deadline recovers on the
next poll (~35s total instead of ~90s); repeat stalls double the
back-off toward the prior 2x-timeout cap, and any settle the deadline
did not force clears the strikes. Queued same-route tasks fail fast
at quarantine instead of stranding one waiter deadline per file in
sequential scans.
- Single request implementation: the vitest in-process fallback now runs
the child's own dispatcher (WslTranscriptFsProcessOperations + decode),
so unit suites exercise exactly what the forked process executes and
the per-call-site fallback closures are gone. Dirent fixtures gained
the full kind-flag set the serializer reads.
- Dropped the production-dead per-route close queue; UNC FileHandles
(test fallback only) mirror the process-handle close contract.
- Error class, messages, and factories move to wsl-transcript-fs-error
(re-exported from the gate) to keep the gate under the lines budget.
* fix(native-chat): harden WSL transcript fs with route quarantine strike
Extract quarantine logic into a dedicated module with strike decay: stalls older
than 5 minutes restart from base back-off, and concurrent-lane timeouts count as
one incident. Allow joining live in-flight tasks on quarantined routes (they cost
no new I/O). Preserve quarantine across transport faults (child death). Handle
file shrinking during tail reads by detecting short reads and returning empty.
Defer file closes that arrive mid-read instead of refusing, preventing slot
leaks. Separate process slot and boundary-finding concerns into focused modules.
* fix(native-chat): enforce route quarantine windows and isolate lanes per
A late result arriving after the deadline was incorrectly lifting the route
quarantine, allowing subsequent work to start before the back-off period
expired. Now late results are correctly recognized as stale and never cut
the quarantine short.
Process work is now isolated per (route, priority) lane so a scan stall
cannot block exact reads on the same distro. Each lane gets its own client
and process pool; late results and handle faults stay scoped to their lane.
Tests now fake performance.now() alongside timers (the quarantine clock
depends on it) and wait for the full back-off window to expire rather than
advancing by 0. Gate state is reset between test cases since late releases
never lift the quarantine.
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
|
||
|
|
a3edabcd7b |
fix(package): keep cached dev Electron bundles out of app.asar (#15359)
`files` is an all-negation list, so electron-builder's default `**/*` packs anything without an explicit `!` entry. out/electron-dev holds `pnpm dev`'s per-branch Electron.app copies (~270MB each), so packaging on a machine that has run dev bundled them all. CI never creates the directory, so releases were never affected. |
||
|
|
9f3a912c1e |
fix(terminal): type Option-composed ASCII instead of reporting it as a chord (#14743)
* fix(terminal): preserve Option-composed ASCII input * fix(terminal): preserve Option keyboard protocol semantics * fix(terminal): complete Option keyboard event encoding * fix(terminal): harden Option input encoding * fix(terminal): close keyboard protocol fallback gaps * test(terminal): prove Option-composed ASCII reaches the pty end to end The Option-compose fix had unit coverage only. This drives a live Electron pane whose kitty flags are armed by the application's own CSI > 1 u and asserts the bytes at the pty boundary: composed `@` and Shift-layer `\` arrive as text, configured Option-as-Alt still reports the layout-resolved chord, and a non-ASCII glyph still reaches the app as its alt hotkey. Restoring the pre-fix policy fails exactly the two composed-text scenarios. Also records the ASCII rule's rationale where the rule lives, not only in a test comment. * refactor(terminal): drop the unread Option layers from the layout snapshot The native helper computed an Option and Option+Shift character for every key, shipped both over IPC, validated them in the parser and cached them in the renderer — but no production caller ever asked for them. Only the base and Shift layers are read, and Shift is the one the web layout map cannot supply, which is why the helper exists at all. Removing them halves the helper's UCKeyTranslate work per key and drops the option parameter that six signatures were threading through for nobody. |
||
|
|
17c57b7d20 |
Unpack OpenCode SQLite worker entry from asar (#13853)
The scanner service runs under ELECTRON_RUN_AS_NODE and cannot access packed files in app.asar. Without unpacking, the worker entry fails to spawn, causing all OpenCode sessions to disappear in packaged builds. |
||
|
|
3b1017c4fb |
Add nightly cut (#13410)
* Add daily macOS dev build release channel Publish once-daily signed macOS builds from main at a dedicated cadence, separate from hourly (too noisy) and release branches (too infrequent). Builds are notarized and installable via the updater, but unvetted — published to stablyai/orca-daily rather than the main repo to avoid evicting stable/RC entries from the releases feed. * fix lint * fix commit * Add third token mint to daily macOS build workflow The upload step's 2x45m retry budget can outlive the one-hour token, so a third is minted after it for verify and cleanup operations. Release notes are moved to a file to ensure consistency between draft creation and publish. Daily channel description updated with specific UTC release time. |
||
|
|
128e3e335e | fix(packaging): prune non-target native binaries (#12174) | ||
|
|
5887b36eff |
fix(updater): recover Linux .deb/.rpm installs that fail escalation (#12183)
* fix(updater): recover Linux .deb/.rpm installs that fail escalation A `.deb` install fails with `No authentication agent found` when the session has no polkit agent. Orca reported "Quit and reopen Orca, then try again" — wrong advice — and its only action was Retry Download, discarding a verified 160 MB package that was still in the updater cache. Keep the one-click install path, but make a failed root-package install recoverable without downloading again: - Retain the downloaded package and its expected SHA-512 from the `update-downloaded` event, mirroring electron-updater's cache-name rule. - Capture the child stderr that BaseUpdater logs but drops from the `error` event, redact it (ANSI, control bytes, `<home>`, `<package>`, `<user>`, 1 KiB cap), and classify the failure. Classification reads the original text — redaction can rewrite a matched phrase. - Send a structured `linux-package-install` recovery status and render a dedicated card: Copy Install Command / Try Automatic Install Again / Show Package. - Revalidate on every action: cache containment, lstat, streamed SHA-512, timingSafeEqual. Concurrent requests coalesce into one hash pass. - Build the command from fixed tokens plus one POSIX-single-quoted absolute path, resolving sudo and the package manager only from /usr/bin, /bin, /usr/sbin, /sbin. Orca never runs it. - Disable `autoInstallOnAppQuit` for .deb/.rpm so an ordinary quit cannot trigger the same failing escalation after the UI is gone. Extracts the error-card presentation into UpdateErrorCardContent so UpdateCard does not absorb another stateful surface. Lifecycle breadcrumbs carry package type, reason, exit code and version — never a path, command, username or raw child output. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Improve Linux package install recovery diagnostics - Distinguish invalid-package-path errors from missing package manager - Expand ANSI escape sequence stripping to handle OSC hyperlinks and DCS - Prevent generic error logs from overwriting specific diagnostic verdicts - Add error handling for shell.openUrl in update UI - Fix test isolation with proper afterEach hooks --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
484273844a |
feat(updater): add an adhoc release channel for branch builds (#12051)
* feat(updater): add an adhoc release channel for branch builds Hourly covers main. This covers everything that is not main yet: a dispatchable macOS build of an unlanded branch, published to stablyai/orca-adhoc, so the team can run an experimental feature for a few days instead of reasoning about it from a diff. Adhoc sits at the bottom of the version order — 'adhoc' < 'hourly' < 'rc' < stable — so no routine check can walk anyone onto somebody's branch; only an explicit pinned jump reaches one. It gets its own repo rather than sharing orca-hourly's, because a branch build must not appear in the list a developer riding main is looking at. Signed and notarized exactly like hourly, for the same reason: macOS anchors a notarized app's TCC grants on identifier + team, so an unnotarized build reads as a new client and silently loses file access under Documents/Desktop/Downloads. Tags stamp to the second rather than the minute. Hourly runs under a concurrency group and cannot overlap itself; adhoc builds are dispatched on demand, so two people cutting from different branches inside one minute is ordinary — and a minute-resolution tag would collide and fail the second build after its whole pack-and-notarize run. Channel-specific behaviour now derives from one DEDICATED_REPO_CHANNELS list: repo mapping, macOS-only support, and UpdateSource. The RPC schema that validates releaseChannelOverride was a hand-copied enum missing the new channel, which would have rejected the override on its way to the main process; it reads the predicate now. * fix(updater): merge the duplicated shared/types import Co-authored-by: Orca <help@stably.ai> * fix(ci): default the adhoc build ref to the dispatch branch The Actions UI puts its own "Use workflow from" branch picker directly above the ref field, and picking a branch there is what most people read as "build this". Making the field optional means the obvious action is also the correct one; naming a branch explicitly still wins, so main's copy of the workflow runs rather than a stale one on an old branch. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
2b44e9ed9e |
fix(updater): notarize hourly macOS builds so TCC grants survive updates (#12007)
macOS anchors a notarized Developer ID app's TCC grants on identifier + team, which is cdhash-independent and so survives an in-place update. Without a notarization ticket there is no such stable identity, so every hourly reads as a different client: the grant row stays but stops matching, and file access under Documents/Desktop/Downloads fails with EPERM and no re-prompt. `tccutil reset` fixes it until the next build — and orca-hourly has shipped as many as 14 builds in a day. Skipping notarization was chosen because Squirrel.Mac validates the replacement bundle's signature, not its notarization. That is true, but it is the wrong requirement; the in-place swap was never the problem. Budgets grow to absorb the notary round trip (publish 2x45, job 150), and the App token is re-minted after the build so its one-hour life starts at the first call that uses it rather than during `pnpm install`. |
||
|
|
f998f7ec62 |
feat(updater): add hourly dev channel and build switching (#11250)
* feat(updater): add hourly dev channel and build switching Adds an hourly macOS build channel plus a dev-only surface for switching update channels and jumping to any published build, including older ones. Hourly builds publish to a separate stablyai/orca-hourly repo. The routine update path resolves tags from the main repo's releases atom feed, which exposes only its 10 newest entries — 24 hourly tags a day would evict every stable/RC entry there and leave real users with nothing to update to. Hourly artifacts carry the release bundle id and Developer ID signature so Squirrel.Mac can swap them in place; only notarization is skipped, which in-place updates never check. Version tails are stripped to the base (1.4.160-hourly.<stamp>, not 1.4.160-rc.3-hourly.<stamp>) so hourlies sort below both rc.N and stable and are reachable only by an explicit pinned jump, never by an ordinary check. The picker is revealed by Option-clicking the Updates header, matching the Help menu's existing hidden admin affordance. Pinned jumps set allowDowngrade and release the feed on every settle path so a jump can never leave background checks permanently deferred. * chore(hourly): create orca-hourly and add token provisioning script Adds setup-hourly-release-token.sh, which provisions HOURLY_RELEASE_TOKEN without the value ever reaching stdout, argv, or shell history: it is read with `read -rs`, passed to gh through GH_TOKEN in the environment rather than as an argument (argv is world-readable via ps), piped into `gh secret set` on stdin, and scrubbed by an EXIT trap. Verification creates and deletes a draft release in orca-hourly to prove Contents:write for real rather than trusting the permission checkbox. Drafts are absent from the releases atom feed, so the probe cannot disturb users. Refuses to run without a controlling terminal instead of falling through having set nothing, and refuses to run under xtrace, which would echo the token on every expansion. * fix(updater): address review feedback on the hourly channel Renderer: - Guard listBuilds against out-of-order responses. activeChannel flips once getVersion resolves, and rapid channel clicks stack requests, so a slower earlier load could land last and fill the list with builds from a channel the picker was no longer showing. - Selecting the running build's own channel now clears the override instead of pinning it. There was previously no way back to "follow this build's channel", so merely opening the panel left background checks pinned. - Validate releaseChannelOverride on hydration, matching every other enum-like field in that function. Main: - Exclude pinned jumps from recordCompletedUpdateCheck() in update-available. A dev browsing the picker was persisting lastUpdateCheckAt and suppressing the next real background check for a full day. - parseHourlyVersionStamp now anchors on the whole version and round-trips the parsed fields. It accepted garbage prefixes, and Date.UTC rolled impossible dates forward, so ...hourly.202602300000 rendered as March 2. Workflow: - Publish into a draft and flip it live only after the manifest check. The window between creating the release and verifying its assets previously exposed a tag the picker would offer and the download would 404 on; a draft is invisible to listReleaseBuilds, so a job that dies in that window — including a hard kill by the job timeout, which runs no cleanup step — leaves nothing user-visible behind. - Add a failure handler that discards the draft, gated on the publish step not having succeeded so a later prune failure cannot delete a live release. - Align retry budgets with the job timeout (was 60min against a worst case of ~185min, so a mid-retry kill skipped the cleanup that step exists for). - Exclude drafts from the freshness and retention queries. - persist-credentials: false; the job only reads this repo and never pushes. * refactor(hourly): authenticate with a GitHub App instead of a PAT A fine-grained PAT expires, and the hourly build would then fail silently on a schedule nobody watches. A GitHub App's private key has no expiry, so this is set up once. It is also owned by the org rather than by the person who created it, so the credential survives that person leaving. The workflow mints a short-lived installation token via actions/create-github-app-token and passes it as GH_TOKEN. Installation tokens live one hour, which is ample: this job runs no tests, no notarization, and no Windows signing, so it is pack + upload. The retry budgets and job timeout are re-sized to that reality rather than copied from the release pipeline, whose 3x45 publish budget exists for notarization and SignPath. setup-hourly-release-token.sh now provisions HOURLY_RELEASE_APP_ID and HOURLY_RELEASE_APP_PRIVATE_KEY. The key is redirected from a file straight into `gh secret set` on stdin, so its contents never enter a shell variable, argv, or the terminal. * fix(hourly): make the xtrace guard fire and cover cancelled runs The xtrace guard disabled tracing before testing for it, so `[[ -o xtrace ]]` read the state the previous line had just cleared and never fired. `bash -x` ran straight through, tracing exactly the key handling the guard exists to prevent. Test first, then disable. The draft cleanup only ran on failure(), but a run stopped from the Actions UI is cancelled(), not failed — a manual cancel mid-publish stranded the draft. Cover both. |
||
|
|
cc078a5021 |
perf(main): move hang watchdog into a worker thread (#11488)
* perf(main): add watchdog boundary memory benchmark Add a repeatable Electron 43 RSS harness that measures the production-built watchdog entry across the child-process and worker-thread boundaries. Record per-trial samples, the median, revision, runtime, and settling procedure for reproducible PR evidence. * perf(main): move hang watchdog into a worker thread Keep main-thread hang detection independent of the blocked Electron event loop without paying for a second ELECTRON_RUN_AS_NODE process. Preserve the marker and telemetry contract while moving timing configuration and heartbeats onto a bundled worker entry. * test(main): smoke packaged hang watchdog worker * fix(main): make packaged watchdog smoke able to fail The smoke reported failure only through process.exitCode, but its finally block quit Electron gracefully, and Electron takes its status from the browser exit code. Every failure mode — entry missing from app.asar, worker error, marker timeout, non-zero worker exit — exited 0 with the diagnostic discarded on stderr, so the required PR check could never go red. Propagate a real status via app.exit, assert the success line in stdout, and surface stderr. Verified against a packaged tree with the entry removed: exit 0 before, exit 1 after. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
8f7692aa12 |
Fix packaged skills CLI runtime ownership (#11627)
* fix(cli): make packaged skills runtime self-contained * fix(cli): address packaged skills review feedback * ci(cli): smoke packaged skills on Windows --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
650dd48ec9 |
feat(cli): add orca account add / account list for headless hosts (Claude + Codex) (#9177)
* feat(cli): add `orca account add` / `account list` for headless hosts The desktop "Add account" UI is disabled when the renderer drives a remote runtime (isRemoteAccountScope === kind:'environment'), so a headless server reached from a remote desktop/web client has no way to register managed Claude accounts. Add a host-local CLI path that reuses the existing capture logic: - ClaudeAccountService.addAccountFromConfigDir(): register a managed account by capturing credentials from an already-authenticated CLAUDE_CONFIG_DIR instead of spawning the interactive browser login (extracted persist/rollback helpers shared with the existing add flow) - RPC accounts.addClaudeFromConfigDir, bridged via OrcaRuntime; rejected for mobile device tokens (host-local only) - `orca account add` runs `claude login` in the user's own terminal into a temp CLAUDE_CONFIG_DIR, then registers it via the local runtime; `orca account list` lists managed accounts Switching (select) already works from a remote client; only adding was blocked. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(cli): support Codex in `orca account add` / `account list` Mirror the Claude headless-account CLI for Codex: - CodexAccountService.addAccountFromHome(): register a managed Codex account by importing auth.json from an already-authenticated CODEX_HOME, reusing a shared persist helper extracted from doAddAccount (no interactive login spawned here) - RPC accounts.addCodexFromHome + OrcaRuntime.addCodexAccountFromHome bridge, rejected for mobile device tokens (host-local only) - `orca account add --agent claude|codex` (default claude); `orca account list` now renders both Claude and Codex managed-account blocks Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: cover headless account-add capture paths (Claude + Codex) - ClaudeAccountService.addAccountFromConfigDir: registers a managed account by capturing an authenticated CLAUDE_CONFIG_DIR; rejects and rolls back when the dir has no .credentials.json - CodexAccountService.addAccountFromHome: imports auth.json from an authenticated CODEX_HOME into a managed account; rejects when auth.json is missing Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: address CodeRabbit review on headless account-add flows - CLI login spawn uses a shell on Windows so `.cmd` agent shims resolve without ENOENT (args are fixed literals, no injection risk) - Claude capture skips the `.credentials.json` precheck on macOS, where creds live in the Keychain and captureAuthFromConfigDir reads them - Claude add rollback is best-effort: a failed rematerialization no longer skips managed-auth cleanup or masks the original add error - Codex persist restores the prior account/selection if a post-write sync or rate-limit refresh fails, so a failure can't leave a dangling managed account - Codex sync passes the account's selection target (correct runtime for WSL) - Add JSDoc to the new public service methods and CLI functions Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): harden headless account capture * fix(cli): correct account command flag surface and interrupt cleanup - `account` commands no longer accept or advertise the browser `--page` flag; `supportsBrowserPageFlag` allow-listed them by omission, so `orca account list --page x` was silently accepted and `--help` rendered a browser-only option - account specs declare GLOBAL_FLAGS, so `--help`/`--json` render in the Options block like every other command - `--agent` on `account add` documents the account provider instead of the terminal TUI-agent meaning inherited from the shared flag table - a SIGINT/SIGTERM during the interactive login now removes the temp login dir (and restores the macOS Keychain item) before exiting 130; Node terminates without unwinding `finally`, which stranded live OAuth credentials on disk * perf(cli): stop `account list` forcing a provider usage refresh `accounts.list` awaited refreshAccountsForMobile(), which runs fetchAll({ force: true }) — bypassing both the poll throttle and the per-provider Retry-After gate — then O(N) serial per-account round trips. `orca account list` renders only emails and the active ids, so all of that work was discarded. The RPC now takes `refreshUsage` (default true, so mobile and web keep the forced lane) and the CLI opts out. Older hosts declare `params: null` and ignore the field, so a newer CLI degrades to the previous behavior rather than failing. Also documents on `account list` that `--environment` does not retarget it, matching the host-local behavior of shouldIgnoreRemoteSelection. * fix(cli): survive repeated and hangup signals during account add withInterruptCleanup latched cleanup behind a boolean, so a second signal got an already-resolved promise and its process.exit fired while the first cleanup was still inside a Keychain call (3s each) — the temp dir's OAuth credentials and the swapped macOS Keychain item both survived. Memoize the cleanup promise so every signal awaits the same run, and register with `on` instead of `once` so a second Ctrl-C cannot fall through to Node's terminate-immediately default mid-cleanup. Handle SIGHUP too. This flow exists for headless/SSH hosts, where the most likely interrupt is the connection dropping, which hangs up the login's terminal and previously ran no cleanup at all. Warn when the interrupt lands after sign-in completed: the runtime finishes the add independently of this process, so exiting 130 silently would tell the user it was cancelled when the account may exist. Reject a valueless `--agent`; the parser turns it into boolean true, which silently ran a full OAuth login for Claude when the user asked for another provider. Also lock two behaviors the refactor changed but left uncovered: a WSL Codex add must sync the WSL runtime lane rather than the default host lane, and rename the account-spec help test to describe the Options block it actually asserts rather than the usage string it never reads. * fix(build): bundle the main modules the account CLI imports electron-vite cleans out/main and emits only its declared entries, and `build:desktop` runs it after `build:cli`, so the tsc-emitted copies of `claude-accounts/keychain`, `codex-cli/command` and `win32-utils` were deleted before packaging. Both `orca account add` and `orca account list` then died at require time with "Cannot find module '../../main/claude-accounts/keychain'" — reproduced against a real `--serve` host. `agent-hooks/managed-agent-hook-controls` already carried an entry for exactly this reason; these three were missing. Adds a parity test so any future CLI import of a `src/main` module fails in CI rather than at a user's shell after packaging. * test: cover the desktop add-path behavior this PR changes Both changes ride in the persist/rollback helpers the existing GUI add flow shares with the new headless path, and neither had coverage: - Claude: rollbackAddAccount now guards forceMaterializeCurrentSelection- ForRollback, so a rejecting rematerialization no longer replaces the real add error nor skips safeRemoveManagedAuth. Asserts the original error surfaces and the throwaway auth dir is gone. - Codex: the desktop add now passes the account's selection target to syncForCurrentSelection, matching reauthenticate and select. Asserts the host target alongside the existing WSL assertion. Both fail when the corresponding change is reverted. * fix(cli): close the remaining account-add interrupt and preflight gaps The round-1 interrupt fix detached the signal handlers before running the finally-path cleanup, so the very window it was meant to protect — the two serial 3s `security` calls plus rmSync on the success/error path — was still covered only by Node's terminate-immediately default. Both review lanes reproduced it independently. Await cleanup first, detach in a nested finally, and stop a cleanup failure from replacing the error that actually explains why the add failed. Do not burn the interactive login when the runtime is unreachable. The RuntimeClient is lazily constructed and the first call was the registration RPC itself, so "Requires the Orca runtime to be running" was discovered only after the user completed a full OAuth round trip. Preflight with the now-cheap `accounts.list { refreshUsage: false }`. Reject `--environment` / `--pairing-code` on `account add`. shouldIgnoreRemoteSelection pins account commands to the local runtime, so `orca account add --environment homelab` silently registered the account on the laptop instead of the headless host it names. Survive a daemon that cannot spawn `claude`. `allowFailure` is honored in onClose but not onError, and unlike the GUI flow nothing has run `claude` in the daemon before this point — so a launchd/systemd daemon with a minimal PATH hard-failed an add the user had already signed in for, even though identity resolves fine from the config dir's oauthAccount. Also align the `--agent` help description with the global flag column. * fix(cli): reject runtime selectors on `account list` too `orca account list --environment homelab` was accepted and silently listed the LOCAL machine's accounts, because shouldIgnoreRemoteSelection pins account commands to the local runtime. Documenting that in --help does not reach someone who already typed the flag, and answering with the wrong host's accounts is the specific wrong answer they would act on. `account add` already errors; this makes the new command group internally consistent. The other groups in shouldIgnoreRemoteSelection keep their existing silent-ignore behavior — changing those is not this PR's job. * test: harden account-add signal tests and cover cleanup failure - Identify the handler under test by set difference instead of `process.listeners(sig).at(-1)`. Vitest installs its own once-wrapped SIGINT teardown, so the positional lookup could grab the wrong listener; the helper also asserts exactly one new listener was added. - Mock rmSync while keeping the real implementation by default, so the temp-dir assertions elsewhere stay honest. - Cover that a cleanup failure in the `finally` does not replace the error explaining why the add failed. Fails when that guard is removed. Completes the review loop's final round; the loop died on an API error before it could commit this, and its `import()` type annotation would have failed oxlint. * fix(cli): harden interactive account add * test(cli): make account cancellation coverage portable * fix(cli): preserve merged skills runtime modules --------- Co-authored-by: Dominik <marketing@gavaplast.sk> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
8ad9448905 | revert: restore pre-worker process boundaries (#11481) | ||
|
|
3f37e32e72 |
perf(main): move hang watchdog into a worker thread (#11344)
Keep main-thread hang detection independent of the blocked Electron event loop while reducing watchdog memory from 47.1 MiB to 11.5 MiB. Preserve marker, recovery, and telemetry behavior with a bundled worker-thread entry. |
||
|
|
747b241145 |
feat(main): record main-thread hangs so we can measure them (#10256)
A deadlocked main thread never crashes, so it leaves no crash report and no artifact — incidence has been unmeasurable (n=1 confirmed, macOS 26.5.1, FB24004458 / electron#52437). This forks a plain-Node watchdog sibling under ELECTRON_RUN_AS_NODE that survives the deadlock, listens for a 2s heartbeat, and after 45s of silence writes a marker to userData. The next launch consumes it, records a durable crash breadcrumb, and emits a main_thread_hang_detected telemetry event carrying unresponsive_ms and self_recovered. Observes only — it never kills or relaunches the parent. A true positive recovers nothing force-quitting wouldn't, while a false positive would SIGKILL a live main thread mid-write. self_recovered counts exactly the stalls such a killer would have gotten wrong, so recovery can be built on evidence if the field numbers justify it. macOS-only, packaged-only (ORCA_HANG_WATCHDOG_FORCE=1 to test), with sleep-gap suppression and idempotent shutdown on will-quit. |
||
|
|
3c0cd6069f |
fix(release): stop packaging plugin authoring examples into app.asar (#11087)
* fix(release): stop packaging plugin authoring examples into app.asar electron-builder's `files` is an all-negation list, so its default `**/*` packs anything without an explicit `!` entry. examples/ arrived with the plugin system in #8549 and never got one, so 1.4.160-rc.3 shipped examples/plugins/hostile-panel/panel.html — the adversarial fixture the panel containment tests point at, complete with its fetch-exfiltration probe — plus hello-orca, inside every user's app.asar. Verified against the installed 1.4.160-rc.3 artifact, not just the config. The two orchestration design docs landed at the repo root in the same span and shipped the same way; fold them into the existing root-doc negation. Neither has a runtime consumer: bundled plugins ship via extraResources from resources/plugins/launch/, which is already excluded from the asar for exactly this reason. * test(release): assert the examples exclusion through the real file matcher The added case mapped each negation to a bare top-level token, so it passed under '!examples/README.md' — a pattern that still ships the whole tree. Drive app-builder-lib's FileMatcher instead so the assertion matches the test name, and pin the root anchoring so the negation cannot grow into '!**/examples'. |
||
|
|
10ca89ac8b |
feat(updater): switch to validated local mac builds (#10889)
* feat(updater): switch to validated local mac builds * test(updater): cover local build recovery actions * fix(types): keep local build contract in project sources |
||
|
|
97e4776dfe |
feat(plugins): Orca plugin system — kernel, content packs, panels, workers, marketplace v0 (experimental) (#8549)
* feat(plugins): Orca plugin system — kernel, content packs, panels, workers, marketplace v0 (experimental) Adds Orca's experimental plugin system behind a settings flag: a supervised kernel, declarative content packs (VM recipes, commands and keybindings, language packs), sandboxed iframe panels, forked worker hosts, and a Git-backed marketplace v0 with consent, provenance and kill-list enforcement. Theme, icon-theme and terminal-theme contributions are deferred to a follow-up pass. * fix(plugins): make unsupported marketplace listings unreachable by key findPlugin() backs preview/install/previewInstalledUpdate via requireListing(), so filtering only listPlugins() hid the catalog card while leaving the dead install path reachable one click later. * fix(plugins): fan Pi session-only status out to plugin subscribers The providerSessionOnly early-return in applyNormalizedStatus emitted to onAgentStatus (main-window fanout) but skipped enrichedStatusListeners, so plugins subscribed to agent.status.changed silently missed every Pi session_start event. Route both emit sites through one helper so a future early return cannot drop the plugin tap again. Co-authored-by: Orca <help@stably.ai> * plugins: drop dead code and hoist duplicated trust-boundary patterns Cleanup pass over the P1 diff, no behavior change: - Delete `readPluginTreeSnapshot`/`readSnapshotFile` and their types, plus the now-vestigial `directories`/`signal` plumbing in `collectFiles`. - Delete `resolveContainedPluginDirectory` (no callers). - Delete `plugin-content-load-pool.ts`; it reimplemented the existing `mapWithConcurrency`, whose index arg also removes the pairing wrapper in `buildPluginList`. - Hoist `PLUGIN_CONTENT_HASH_PATTERN` and `PLUGIN_COMMIT_PATTERN` into the install-lockfile module; 11 sites hand-rolled these identically. - Point the new reliability gate at the PR instead of gitignored docs paths, matching every other gate's link form. * fix(plugins): retry plugin state renames on Windows AV/EPERM locks Six plugin write paths (lockfile, provenance, current pointer, kill list, marketplace cache, staged install dir) did a plain rename, so an antivirus or indexer holding the target open surfaced as a failed install. The repo already retries this hazard for issue #1507, but only through a sync helper; these paths are all async. Adds one bounded async retry + atomic write used by all six, and trims a consent-provenance header that restated its own JSX. * test(plugins): cover the Windows rename retry path The retry loop shipped untested: both existing cases hit the non-retry path, and the temp-cleanup test passed identically with the `finally` removed. Mock `rename` to queue errno codes so CI can exercise locks it cannot provoke. Co-authored-by: Orca <help@stably.ai> * fix(plugins): pin bundled plugin resources to LF Windows CI checks out with autocrlf, so the byte-hashed launch tree arrived as CRLF and verify-packaged-plugin-resources rejected it — the packaged build could never pass on Windows. Reproduced locally: CRLF yields the exact CI error, LF verifies clean. Files are already LF, so nothing renormalizes. Co-authored-by: Orca <help@stably.ai> * test: guard the bundled-plugin LF pin against a CRLF checkout The byte-hash mismatch only surfaced in Windows packaging CI. Assert the .gitattributes pin and that a CRLF tree is rejected, so a regression fails on any platform instead of waiting for a packaged Windows build. Co-authored-by: Orca <help@stably.ai> * ci: trigger packaged-build check on bundled plugin resource changes The launch tree is byte-hashed during packaging, but no trigger path covered it — so the CRLF fix for that check would not have re-run the check. Add the resources, verifier and .gitattributes paths that can break packaging. Co-authored-by: Orca <help@stably.ai> * perf(plugins): rebuild the panel frame only when its baked theme values change The revision keys the panel iframe, so every bump destroys the sandboxed frame and its in-panel state. It counted root attribute mutations, but --workspace-sidebar-live-width is written every rAF of a sidebar drag, so dragging with a panel open blanked it ~60x/sec. Compare the two values the shell actually bakes in instead. Co-authored-by: Orca <help@stably.ai> * test: stop pinning a plugin name in the CRLF guard The CRLF case rewrites every launch file, so the reported mismatch is whichever plugin sorts first. P2 adds theme plugins that sort ahead of orca-navigation-shortcuts, which broke the assertion there. Co-authored-by: Orca <help@stably.ai> * style: drop stray blank lines left by the rebase resolutions Both sides of the agent-hooks and orca-runtime conflicts contributed a trailing blank, which oxfmt rejects. Whitespace only. Co-authored-by: Orca <help@stably.ai> * test(plugins): stop the startup budget failing on machine load P95 runs 16-34ms idle but exceeds the 50ms bound under full-suite parallelism, so the gate flaked. Widen it to catch an order-of-magnitude regression instead; the no-worker/no-plugin-code assertions are the real guarantee. Verified a 400ms regression still fails. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
48a258d502 |
fix(win): stop shipping duplicate broken orca.cmd shim in app.asar (#9123)
The Windows CLI shim is delivered via extraResources to resources/bin/orca.cmd, beside the native resources/bin/orca.exe, and resolves the launcher adjacent to itself (%SCRIPT_DIR%orca.exe) — which works. But nothing in `files` excluded resources/win32/, so its source copy was also packed into app.asar and then extracted by asarUnpack:['resources/**'] to app.asar.unpacked/resources/win32/bin/orca.cmd. That duplicate has no adjacent orca.exe, so invoking it fails with "Unable to locate the native Orca CLI launcher", breaking orchestration skills that reach for the unpacked shim. Exclude the win32 shim source tree from app.asar so only the working extraResources copy ships. Add a regression guard to the electron-builder config test. Closes #7351 |
||
|
|
1a9e819c40 |
feat(skills): land remaining hybrid stubs (#9846)
* feat(skills): land remaining hybrid stubs * fix(build): exclude skill stub sources from packages |
||
|
|
68fca0b076 |
Add safe skill freshness detection and update rail (#8637)
* Add safe skill freshness detection * Accept observed copy-mode rail outcomes * chore(skills): regenerate snapshot artifacts for the merged guide content The rebase onto main picked up the reviewed guide fixes (#8624), so the current manifest hashes and a new appended snapshot generation must match those bytes; the registry keeps all prior snapshots so existing installs classify as outdated rather than unrecognized. * fix(skills): canonicalize snapshot file order and guard released history Historical snapshots kept git ls-tree byte-order while the working-tree walk and runtime observation use the sorted depth-first order, so any future multi-file skill would misclassify older installs as unrecognized and churn spurious registry revisions; all producers now share one canonical order (no digest changes for today's single-file packages). Also rejects executable files from shipped skills (Windows observation cannot see execute bits, which would misclassify pristine Windows installs) and adds an explicit append-only invariant for released snapshots so a generation-logic change cannot rewrite them silently. * fix(skills): throttle focus rescans and correct self-blocked placement copy Every window focus re-read and re-hashed all installed packages, and the nudge and panel each forced their own trailing rescan for one event; a 15s cooldown plus a shared invalidation latch keep one bounded scan per event while install-change events stay immediate. Bundle artifacts are now loaded once per run instead of re-parsed on every scan. A read-only or otherwise unsupported outdated placement now explains that it blocks itself instead of blaming a phantom sibling placement; the supported topology set moved to shared so eligibility and copy cannot drift. * feat(skills): move freshness surfacing to a lingering toast and update modal The Skills page has been unreachable since its toolbox menu entry was removed (#4535), so surfacing freshness there buried the feature behind its own nudge. The nudge now lingers until acted on (ignoring it records nothing; only the explicit close persists dismissal keys) and opens an update modal hosting the pre-filled editable terminal, an honest current/blocked summary, and the per-placement rows in a collapsed Details section. A compact 'Check for skill updates' row in CLI settings is the manual re-entry point. Skills page restored to main; design-doc surfacing section records the venue decision. * fix(skills): mount update dialog inside the link-routing provider and fold freshness into the setup rails The dialog hosts a live terminal pane that requires the link-routing preference context; mounted outside the provider it crashed the renderer the moment an eligible update existed (caught by live QA — unit tests mock the terminal). It now mounts inside the provider behind its own recoverable boundary. The separate 'Check for skill updates' settings button is gone: the setup rails' own pill now carries freshness (Update available / Up to date, falling back to Installed for blocked or unrecognized copies and for non-local runtimes the local-only scan cannot vouch for), and Re-check refreshes both installation detection and the freshness inventory. Wired for the CLI, Orchestration, Computer Use, and Per-Workspace Environments rails. * fix(skills): use the sleek scrollbar style in the update dialog * chore(skills): regenerate manifest for merged main (v1.4.142-rc.1) Main advanced to 1.4.142-rc.1 with a v1.4.141 release, so the embedded appVersion and release mapping were stale on the PR's merged tree. Only appVersion and the new release entry change; no snapshot digests move (released history preserved). * fix(skills): bound and batch freshness work * fix(skills): harden freshness integrity checks * fix(skills): accept observed copy topology outcomes * chore(skills): regenerate manifest for current main * fix(skills): preserve update terminal lifecycle * chore(skills): regenerate manifest for current main * fix(skills): fail closed on stale freshness scans * chore(skills): regenerate manifest for current main * fix(skills): preserve freshness safety under focus churn * feat(skills): group the update modal by skill with plain-language status The Update skills modal now lists only skills that will update or that can't (with why), grouped by skill with their install locations nested underneath — no more one row per placement. - Statuses collapse to "Update available" / "Can't update" at the skill level. - A location's problem is a chip (Duplicate, Unrecognized, Inaccessible, Read only, In a repo, External/Broken link, Plugin cache) with a hover tooltip that explains what it means for the user and what to do. - Up-to-date, unrecognized-only, and unreadable-only skills are hidden; a current/unrecognized/etc. location only appears when it explains a shown skill. - Copy is de-jargoned (drops "copy"/"placement"/"snapshot"/"official copy") and names the mechanism as the npx skills update command, not "Orca's update". - Rename the section to "Update details"; drop the unreachable newer-known state. Renderer-only: derivation is a pure module (groupSkillFreshness) with unit tests; no IPC or main-process change. Locales updated for all five languages. * chore(skills): regenerate manifest for current main (v1.4.143-rc.0) * feat(skills): don't let a duplicate block the update; clearer skipped copy - Eligibility: a clean standalone duplicate no longer poisons the whole name — the canonical copy still updates and the duplicate is flagged; a duplicate-only skill stays unoffered. - Update modal: "Can't update" -> "Skipped" with a reason-specific sentence (edited/read-only/in-a-repo/plugin/link); chips describe only the location state; footer "Check now" -> "Re-check". - Settings sidebar nav pills go amber "Update available" when a skill is updatable, matching the setup cards. - Localized new strings across en/es/ja/ko/zh. * chore(skills): regenerate manifest for merged main (v1.4.144-rc.1) |
||
|
|
792160e9ec |
fix(packaging): stop tracking pr-evidence screenshots and exclude from app.asar (#8681)
The three pr-evidence/*.png files were accidentally committed with the sidebar fix (#8527). They are e2e evidence outputs regenerated on demand by worktree-lineage-agent-expansion.spec.ts only under ORCA_CAPTURE_EVIDENCE=1, and nothing reads them — contradicting the .gitignore intent that PR evidence screenshots not be committed. - Remove the tracked PNGs (unreferenced generated artifacts). - Ignore pr-evidence/ alongside notes/artifacts/. - Defensively exclude pr-evidence/ from the electron-builder app.asar include surface so a stray local capture never bloats the bundle. - Assert the new exclusion in electron-builder-config.test.mjs. Co-authored-by: Orca <help@stably.ai> |
||
|
|
31f643ca42 |
Add version-matched skill guides to the CLI (#8624)
* Add version-matched bundled skill guides * Clarify skill freshness rollout PRs * Add canonical skills show alias * fix(skills): address guide review feedback * fix(skills): make guide commands cross-platform * fix(skills): apply the ORCA convention to the emulator guides Review follow-up: the emulator guides still instructed literal `orca emulator ...` in sh fences with no Linux disambiguation, so on unmanaged Linux they could launch the GNOME screen reader — the exact failure the executable-selection preamble prevents. Both emulator guides now carry the preamble and ORCA placeholder across fences, tables, and prose, and the cross-platform safety test covers all four converted guides. Also replaces computer-use's "unless a block names a shell" carve-out, which contradicted its own POSIX example, with the unconditional placeholder rule. |
||
|
|
ee82d66a35 |
fix(cli): preserve multiline arguments on Windows (#8374)
* fix(cli): preserve multiline Windows arguments * test(cli): run Windows launcher regression in CI * fix(cli): support Windows Framework C# compiler |
||
|
|
69befad13e |
Harden layout validation, watcher lifecycle, and connection robustness (#7924)
* Harden layout validation, watcher lifecycle, and connection robustness - Throw instead of silently skipping when the packaged daemon-entry is missing, preventing layout regressions from passing build checks. - Terminate idle parcel-watcher processes to reclaim native handles and avoid crash-prone native node module teardowns on shutdown. - Bind the persisted WS fallback port first to prevent orphaning active mobile pairings when the preferred port becomes free again. - Cap concurrent disk reads for restored dirty tab verification at three to prevent startup connection bottlenecks on remote SSH workspaces. * Queue file IDs instead of snapshots in restored conflict scans This avoids using stale file snapshots (e.g., outdated disk signatures) if a tab is saved, closed, or re-baselined while waiting in the queue behind the concurrency limit. The live state is now fetched from the store and validated immediately before initiating the disk read. |
||
|
|
ee823b766c |
Move notification status helper to Contents/MacOS (#7931)
On macOS 26, UNUserNotificationCenter aborts when executables are run from Contents/Resources because bundleProxyForCurrentProcess returns nil. Moving the orca-notification-status helper to Contents/MacOS next to the main Electron executable ensures proper bundle resolution and avoids immediate crashes. |
||
|
|
dfc12f2cf6 | fix(watcher): isolate @parcel/watcher in a forked process so native crashes can't kill the app (#7547) (#7757) | ||
|
|
6a4b89785c |
revert: back out the Windows terminal update-survival chain (#7421→#7499) (#7505)
* Revert "Preload the daemon windowsHide shim via --require; wrap promisify custom (#7499)" This reverts commit |
||
|
|
f4faafa987 |
fix(daemon): relocate daemon host image out of the install-dir kill zone (#7473)
Co-authored-by: Neil <neil@stably.ai> Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com> |
||
|
|
d6fb8e04f9 | Revert "Notarize serve-sim camera dylibs in macOS releases" (#7165) | ||
|
|
ee8bdf1aac |
Notarize serve-sim camera dylibs in macOS releases (#7077)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
df6e5d56a7 |
Test arm64 ConPTY payload packaging (#6970)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
22db5b78d6 |
Fix Windows bundled ConPTY packaging (#6968)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
19eeacfad3 | fix: pin CLI build output to CommonJS (#6656) | ||
|
|
531bcc9a0e | build: publish linux arm64 release artifacts (#4755) | ||
|
|
bb51bc9ae6 | Fix Linux dock window class (#5293) | ||
|
|
58b5472477 |
fix(packaging): include @parcel/watcher native addon in packaged runtime
Fix packaged runtime inclusion for @parcel/watcher and its platform native subpackages. Adds pruning/tests for packaged resources and a real watcher integration regression test. |
||
|
|
38feb52864 | Fix AppImage CLI launcher path | ||
|
|
819998db04 | build: prune packaged runtime node modules (#4020) | ||
|
|
869b4e50db |
build: reduce packaged electron bundle size
Exclude repo-only package inputs and move renderer/build-only dependencies out of production dependencies to reduce packaged app.asar size while keeping source maps allowed for crash diagnostics. |
||
|
|
f2da5fc0a2 |
fix: handle Windows asar paths in runtime dep verifier
Fix release-cut Windows packaging by resolving app.asar entries through listPackage before extracting files in the runtime dependency verifier. |
||
|
|
ce0cd79ec1 | fix: skip optional native rebuild in release packaging | ||
|
|
7337cab2d8 |
feat(linux): add RPM package target and rename CLI to orca-ide
Adds an RPM Linux package target and renames the Linux CLI command to orca-ide to avoid shadowing GNOME Orca, while preserving macOS and Windows CLI command behavior.\n\nFollow-up hardening keeps the Linux launcher executable, removes only old Orca-managed Linux/WSL orca launchers during migration, preserves AppImage/deb artifact names, and updates package/release tests for the RPM asset. |
||
|
|
3861d65a17 |
Fix Linux package icon sizes (#2538)
Co-authored-by: Orca <help@stably.ai> |