Files
orca/config/daemon-host-node-runtime.cjs
T
Neil 58877e2d92 fix(daemon): relocate daemon host image out of the install-dir kill zone
The terminal daemon was fork()ed with no explicit execPath, so its process
image was the install-dir Orca.exe run via ELECTRON_RUN_AS_NODE. On Windows,
the NSIS installer's CHECK_APP_RUNNING sweep force-kills processes whose image
lives under the install directory, so an app update killed the live daemon and
every terminal it hosted (including the user's running Claude process).

Relocate the daemon host image out of the install dir: stage a standalone,
version-correct node.exe under %APPDATA%\orca\daemon-host\<appVersion>\ and fork
the daemon from it. The daemon's image now lives in userData, outside the
installer's kill zone, so it survives updates.

Fail-open: if no relocated node.exe is present, fall back to the current
Orca.exe + ELECTRON_RUN_AS_NODE path (zero regression). Adoption and stale-kill
are unaffected — process identification matches on daemon-entry/socket/token
args and the unchanged entryPath, never the host image name.

Build: afterPack stages the build host's own official (OpenJS-signed) Node 24.x
node.exe into resources/daemon-host/node.exe. engines.node="24" pins the build
host to the ABI Electron 42 embeds (Node 24.15, NAPI 10) so the relocated host
loads node-pty's NAPI conpty.node addon. Shipping the OpenJS-signed binary means
it is validly signed regardless of whether SignPath re-signs nested PEs.

Extract the version-keyed copy + daemon-pinned GC shared by the node-pty native
relocation (#7421) into install-dir-runtime-relocation.ts and reuse it for the
host relocation. CI verify step + contract/config tests guard the staging.
2026-07-05 18:43:18 -07:00

42 lines
2.0 KiB
JavaScript

const { copyFileSync, existsSync, mkdirSync } = require('node:fs')
const { join } = require('node:path')
// Where the standalone daemon-host node.exe is staged under packaged resources.
// Mirrored at runtime by src/main/daemon/daemon-host-relocation.ts, which copies
// it into userData/daemon-host/<version>/ and forks the terminal daemon from it.
const DAEMON_HOST_DIR = 'daemon-host'
const DAEMON_HOST_NODE_EXE = 'node.exe'
/**
* Stages a standalone Windows node.exe into resources/daemon-host so the
* detached terminal daemon can be forked from a userData copy that lives
* outside the install directory the NSIS updater force-closes mid-update.
*
* Source is the build host's own node.exe (process.execPath). It is version-
* correct by construction: engines.node pins the build to Node 24.x, whose NAPI
* 10 runtime loads node-pty's Electron-42-built conpty.node. Being an official
* Node binary, it also carries the OpenJS Foundation Authenticode signature, so
* it ships validly signed regardless of whether SignPath re-signs nested PEs.
*
* Windows x64 only — the sole Windows build target. Other targets ship no
* node.exe and the runtime falls open to forking the install-dir Electron host.
*/
function ensurePackagedDaemonHostNode(resourcesDir, electronPlatformName) {
if (electronPlatformName !== 'win32') {
return
}
const nodeExePath = process.execPath
// The build must run under standalone Node (not Electron) so execPath is a
// real node.exe with a NAPI runtime; anything else would strand the daemon.
if (!/node\.exe$/i.test(nodeExePath) || !existsSync(nodeExePath)) {
throw new Error(
`[daemon-host] cannot stage node.exe: build host execPath is not a node.exe (${nodeExePath})`
)
}
const destDir = join(resourcesDir, DAEMON_HOST_DIR)
mkdirSync(destDir, { recursive: true })
copyFileSync(nodeExePath, join(destDir, DAEMON_HOST_NODE_EXE))
}
module.exports = { ensurePackagedDaemonHostNode, DAEMON_HOST_DIR, DAEMON_HOST_NODE_EXE }