feat(server): headless, Electron-free @stablyai/orca-server#6844
feat(server): headless, Electron-free @stablyai/orca-server#6844Jinwoo-H wants to merge 12 commits into
Conversation
Adds an npm-installable headless Orca runtime server that boots under plain Node — no Electron, no X11 — so the runtime can run in lightweight containers and microVMs. Pairs with desktop/mobile/CLI clients over the existing E2EE RPC protocol and exposes a pairing URL on stdout. How it works: - Host abstractions (AppEnvironment, SecretStore, managed fetch) replace direct electron `app`/`safeStorage`/`net` usage in the runtime core. Desktop installs Electron-backed impls (behavior unchanged); the node server installs Node-backed impls. SecretStore uses AES-256-GCM at rest (not plaintext) on keychain-less hosts. - A new `orca-server` bin (src/server) boots the runtime + RPC + headless PTY host. The bundle is built with esbuild (electron aliased to a throwing/ delegating shim, native addons external). - node-pty is the only ABI-sensitive native dep. The publish flow ships a prebuilt matrix (linux x64/arm64 glibc+musl, darwin x64/arm64) so installs are toolchain-free; node-pty's source build is the fallback. - Browser panes / tray / notifications / auto-updater degrade gracefully via the existing capability flags; clients fall back to a local browser tab. Coexistence: warns if sharing a desktop install's userData (set ORCA_USER_DATA_PATH to isolate). CI: .github/workflows/publish-orca-server.yml builds the 6-cell prebuilt matrix, verifies the tarball ships every slot (pty.node + spawn-helper), and publishes @stablyai/orca-server. Requires an NPM_TOKEN repo secret (publish step no-ops without it). Triggered by an `orca-server-v*` tag or workflow_dispatch. Verified: real CLI client ↔ node server (status, repo add, terminal create/read/send full-duplex over E2EE); toolchain-free install + PTY spawn on glibc + musl (x64 and arm64) in CI; tarball install boots + spawns a PTY.
…fix conflicts) Co-authored-by: Orca <help@stably.ai>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds shared host abstractions for app environment, secret storage, and managed fetch, then migrates main-process code and tests to those abstractions. It also introduces a headless Node server entrypoint and CLI bridge, plus build, Docker, prebuild, smoke-test, and GitHub Actions publishing tooling for 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…mjs (lint) Co-authored-by: Orca <help@stably.ai>
There was a problem hiding this comment.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/providers/local-pty-shell-ready.ts (1)
51-64: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winBuild the shell-wrapper paths with
path.join()
getShellReadyWrapperRoot()andgetRequiredShellReadyWrapperPaths()still concatenate runtime filesystem paths with/, which can produce mixed separators on Windows.Source: Coding guidelines
🧹 Nitpick comments (1)
src/main/terminal-history.test.ts (1)
76-78: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winForward the requested path name through the mock.
getPathcurrently drops thenameargument, so this test can pass even ifterminal-historyasks for the wrong app path. Mirror theAppEnvironment.getPath(name)contract.Proposed test stub adjustment
setAppEnvironment({ - getPath: () => getPathMock(), + getPath: (name) => getPathMock(name), getAppPath: () => '/fake/app',
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 6213cf3a-e32f-498b-ab5e-40ab7d808ed0
📒 Files selected for processing (52)
.github/workflows/publish-orca-server.ymlconfig/docker/Dockerfile.serverconfig/docker/orca-server-with-xvfb.shconfig/scripts/build-server-prebuilds.mjsconfig/scripts/build-server.mjsconfig/scripts/headless-server-smoke-client.mjsconfig/scripts/install-node-pty-prebuilt.mjspackage.jsonsrc/main/automations/service.test.tssrc/main/browser/browser-session-registry.persistence.test.tssrc/main/browser/browser-session-registry.tssrc/main/daemon/daemon-init.test.tssrc/main/daemon/daemon-init.tssrc/main/index.tssrc/main/integration-credential-file.tssrc/main/ipc/pty.test.tssrc/main/ipc/pty.tssrc/main/jira/client.test.tssrc/main/jira/client.tssrc/main/linear/client.test.tssrc/main/linear/client.tssrc/main/memory/collector.test.tssrc/main/memory/collector.tssrc/main/network/electron-managed-fetch.tssrc/main/persistence.test.tssrc/main/persistence.tssrc/main/providers/local-pty-shell-ready.test.tssrc/main/providers/local-pty-shell-ready.tssrc/main/runtime/electron-app-environment.tssrc/main/runtime/electron-secret-store.tssrc/main/runtime/file-watcher-host.tssrc/main/runtime/mobile-pairing-userdata-path.test.tssrc/main/runtime/orca-runtime.tssrc/main/server/electron-shim.tssrc/main/server/node-app-environment.test.tssrc/main/server/node-app-environment.tssrc/main/server/node-secret-store.tssrc/main/server/node-server-args.test.tssrc/main/server/node-server-args.tssrc/main/server/node-server-main.tssrc/main/server/shared-user-data-guard.test.tssrc/main/server/shared-user-data-guard.tssrc/main/speech/openai-api-key-store.test.tssrc/main/speech/openai-api-key-store.tssrc/main/telemetry/client.tssrc/main/terminal-history.test.tssrc/main/terminal-history.tssrc/main/terminal-scrollback-snapshots.tssrc/server/index.tssrc/shared/app-environment.tssrc/shared/managed-fetch.tssrc/shared/secret-store.ts
Co-authored-by: Orca <help@stably.ai>
|
Ready to review this PR? Stage has broken it down into 10 individual chapters for you: Chapters generated by Stage for commit 9bce58b on Jul 2, 2026 3:56am UTC. |
Co-authored-by: Orca <help@stably.ai>
There was a problem hiding this comment.
Actionable comments posted: 3
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 1a1deb3b-80a8-4e2c-b281-0536d2694e16
📒 Files selected for processing (10)
.github/workflows/publish-orca-server.ymlconfig/docker/Dockerfile.serverconfig/docker/orca-server-with-xvfb.shconfig/scripts/build-server.mjsconfig/scripts/headless-server-smoke-client.mjssrc/cli/index.tssrc/cli/runtime/launch.test.tssrc/cli/runtime/launch.tssrc/main/server/node-server-main.tssrc/server/cli.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- config/docker/Dockerfile.server
- config/docker/orca-server-with-xvfb.sh
- config/scripts/headless-server-smoke-client.mjs
- src/main/server/node-server-main.ts
- config/scripts/build-server.mjs
- .github/workflows/publish-orca-server.yml
Co-authored-by: Orca <help@stably.ai>
There was a problem hiding this comment.
♻️ Duplicate comments (2)
.github/workflows/publish-orca-server.yml (2)
96-101: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
macos-13runner is retired — pipeline will fail on darwin-x64 slot.GitHub retired the
macos-13hosted runner image by December 4, 2025; jobs using this label now terminate immediately. This was flagged in a prior review (recommendingmacos-15-intel) but the runner label here is unchanged.🐛 Proposed fix
- - { runner: macos-13, slot: darwin-x64, kind: darwin } + - { runner: macos-15-intel, slot: darwin-x64, kind: darwin }Is the macos-13 GitHub Actions runner label still usable in 2026?
103-121: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winHost-toolchain prebuild slots aren't pinned to Node 24.
resolve/publishjobs explicitly pinNODE_VERSION, and musl slots build insidenode:24-alpine, but the glibc/darwin host-toolchain step here has noactions/setup-nodestep, sonode-gypbuilds against whichever Node ABI the runner image ships by default — risking an ABI mismatch with the Node 24 target used elsewhere.🔧 Proposed fix
- uses: actions/checkout@v6 with: ref: refs/tags/${{ needs.resolve.outputs.tag }} + + - uses: actions/setup-node@v6 + if: matrix.kind != 'musl' + with: + node-version: ${{ env.NODE_VERSION }} # glibc + darwin: build natively on the host runner. - name: Build prebuilt (host toolchain)
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 571e09f9-e2f3-40c7-9e5f-1bfdfebbaac8
📒 Files selected for processing (5)
.github/workflows/publish-orca-server.yml.github/workflows/release-cut.ymlconfig/scripts/package-electron-runtime-contract.test.mjsconfig/scripts/publish-complete-draft-releases.mjsconfig/scripts/publish-complete-draft-releases.test.mjs
Co-authored-by: Orca <help@stably.ai>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
.github/workflows/publish-orca-server.yml (1)
164-169: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winAvoid persisting write-scoped Git credentials before install/build.
contents: writeis only needed for the finalgh release edit, butactions/checkoutpersists its token by default before dependency install and build steps. Disable persisted credentials here, or split the release edit into a separate write-scoped job.Proposed fix
- uses: actions/checkout@v6 with: ref: refs/tags/${{ needs.resolve.outputs.tag }} + persist-credentials: false.github/workflows/release-cut.yml (1)
1255-1265: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winGate the Homebrew bump on the publish job succeeding
This
if:replaces the defaultsuccess()guard forneeds, sohomebrew-bump-published-rc-draftcan still run afterpublish-orca-server-published-rc-draftsfails and point Homebrew at an unpublished RC.🔒 Suggested fix
- if: ${{ needs.cut.outputs.latest_publishable_rc_tag != '' }} + if: ${{ needs.cut.outputs.latest_publishable_rc_tag != '' && needs.publish-orca-server-published-rc-drafts.result == 'success' }}
♻️ Duplicate comments (2)
.github/workflows/publish-orca-server.yml (2)
118-130: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winPin host prebuilds to the workflow Node version.
The non-musl prebuild path still invokes the runner’s default
node; nativenode-ptyartifacts can be built against a different ABI thanNODE_VERSION: 24.Proposed fix
- uses: actions/checkout@v6 with: ref: refs/tags/${{ needs.resolve.outputs.tag }} + + - uses: actions/setup-node@v6 + if: matrix.kind != 'musl' + with: + node-version: ${{ env.NODE_VERSION }} # glibc + darwin: build natively on the host runner.
106-111: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick winUse a supported Intel macOS runner label.
Line 111 still uses
macos-13, which actionlint reports as an unknown hosted runner label. Use a currently supported Intel macOS label such asmacos-15-intel.Source: Linters/SAST tools
🧹 Nitpick comments (3)
src/cli/runtime/launch.ts (1)
5-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winShare the serve options type with the server bridge.
src/server/cli.tsredeclares the sameServeOrcaAppOptionsshape, so future flags can drift between the injected hook contract and the server argument mapper. Prefer importing this exported type there instead of maintaining two copies.config/scripts/publish-complete-draft-releases.mjs (1)
118-169: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win
published/published_*naming is now misleading whenpublish: false.
published.push(tag)and the derivedpublished_count/published_tags/published_tags_json/latest_published_tagoutputs are computed the same way regardless of whether the PATCH (draft: false) actually ran. When called withpublish: false(as the release-cut workflow's "Find complete release-cut RC drafts" step always does), these outputs claim tags are "published" while the GitHub release is still a draft. The newpublishable_*fields duplicate the exact same values and are the ones the workflow now actually consumes — the oldpublished_*fields are effectively vestigial and could mislead a future consumer into thinking a release was actually made public.♻️ Suggested clarification
- published.push(tag) + // "published" here means "publish-eligible / prepared"; only reflects an + // actual GitHub PATCH when `publish` is true. + published.push(tag)Consider renaming the internal variable/outputs (e.g.
preparedOrPublished) or dropping the now-duplicatepublished_*output keys once no consumer depends on them, to avoid the two names silently diverging in meaning..github/workflows/release-cut.yml (1)
1223-1239: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winScope
secrets: inheritfor the new reusable-workflow calls.Both new jobs pass all repository/organization secrets to the called workflows rather than only what's required (e.g.
NPM_TOKEN, release token). This broadens blast radius if the reusable workflow is ever compromised or misconfigured, per the static analysis hint.Also applies to: 1255-1265
Source: Linters/SAST tools
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 4353e9b3-f41c-4dc5-8012-8d2ebaf6555d
📒 Files selected for processing (14)
.github/workflows/publish-orca-server.yml.github/workflows/release-cut.ymlconfig/scripts/package-electron-runtime-contract.test.mjsconfig/scripts/publish-complete-draft-releases.mjsconfig/scripts/publish-complete-draft-releases.test.mjssrc/cli/handlers/core.tssrc/cli/help.tssrc/cli/index.test.tssrc/cli/runtime/launch.test.tssrc/cli/runtime/launch.tssrc/cli/specs/serve.tssrc/main/server/node-server-main.test.tssrc/main/server/node-server-main.tssrc/server/cli.ts
✅ Files skipped from review due to trivial changes (1)
- src/cli/help.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- src/server/cli.ts
- config/scripts/package-electron-runtime-contract.test.mjs
- src/cli/runtime/launch.test.ts
- src/main/server/node-server-main.ts
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Orca <help@stably.ai>
Summary
Adds
@stablyai/orca-server: an npm-installable, headless Orca runtime server that runs under plain Node without Electron or X11. After install, the supported cross-platform command is:The npm package intentionally exposes only the
orca-idebin. It does not publishorca, to avoid shadowing GNOME Orca on Linux or colliding with desktop-app CLI shims on macOS/Windows.orca-server.jsremains the packagemain/direct bundle entry, not a public global command.Screenshots
No visual change.
Testing
pnpm exec oxlint --format github(0 errors; existing unrelated warnings remain)pnpm typecheckpnpm vitest run config/scripts/package-electron-runtime-contract.test.mjs src/main/server/node-server-args.test.ts src/main/server/node-app-environment.test.ts src/main/speech/openai-api-key-store.test.ts src/main/providers/local-pty-shell-ready.test.ts src/cli/index.test.ts src/cli/runtime/launch.test.ts src/main/server/node-server-main.test.ts src/main/server/shared-user-data-guard.test.ts src/shared/secure-file.test.tspnpm vitest run src/main/persistence.test.ts -t "Kagi session"pnpm run build:servernode config/scripts/headless-server-smoke-client.mjs "$PWD/out/server/orca-ide.js"AI Review Report
Reviewed the PR with CodeRabbit plus an additional AI pass focused on the npm CLI surface and release flow. Main findings addressed:
orca-ideonly; do not publishorcaororca-serveras global bins.@stablyai/orca-serverreleases with desktop stable and RC releases: stable publishes to npmlatest, RC publishes to npmalpha.nodeuser and keep healthchecks aligned with the published bin.Cross-platform review explicitly covered macOS, Linux, and Windows command naming, npm bin collision behavior, Node ABI/prebuild coverage, path handling, Docker/Linux behavior, and Electron-free runtime shims.
Security Audit
The server continues to rely on pairing plus E2EE RPC for API access; the Docker/default bind behavior should not be treated as an unauthenticated open control plane. Secret storage uses AES-256-GCM on Node hosts and now refuses to overwrite an existing invalid key; first-run key creation uses exclusive secure-file publication so competing server processes re-read the winner instead of replacing it.
Publishing uses the repo-level
NPM_TOKENGitHub secret, which is configured instablyai/orca; the workflow passes only that secret into the reusable npm publish job. No npm credential or token value is stored in the repo.Notes
package.json, so normal stable/RC release cuts keep npm aligned with Orca app versions.orca-ide serve; this matches the existing Linux desktop CLI naming and avoids the GNOME Orca command collision.