mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
* fix(ssh): relay version GC deletes only on an exited verdict and keeps the previous build The relay records .relay-pid in its version dir once it owns its socket. GC calls a relay version dir exited only when that PID is provably dead and every relay-*.sock refuses a connection; a dir without a PID file keeps the test -S rule. The most recently completed other relay build is pinned like orcad's rollback target. Design D5 GC liveness. * feat(ssh): collect the shared runtimes/ Node store and give it its own owner runtimes/ gets its own owner in the install model, so no version-dir GC (new or old clients, whose listings are prefix-scoped) can list or delete it. A store pass removes node-<sha> only when no retained dir references it, it is neither a current pin nor the newest other verified runtime, and a ps or /proc check ran and found no process using it. Legacy relay-*/orcad-* dirs are read for references and reported as diagnostics only (design D10 two-step). Wired behind orcad GC's nodeRuntimePins. * fix(ssh): runtime store process check holds runtimes reached through a symlinked home /proc exe resolves symlinks and argv keeps whatever spelling launched the runtime, so filtering on the exact $root path missed in-use runtimes on hosts like /home -> /var/home. Filter on the store segment instead; the parser already attributes holds root-agnostically. * test(ssh): wait for the holder process to spawn instead of a fixed delay --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
107 lines
4.8 KiB
TypeScript
107 lines
4.8 KiB
TypeScript
/**
|
|
* What a packaged relay directory must contain, declared once. The build, the
|
|
* content hash, and the remote install probe all read this list; they used to
|
|
* keep three of their own, which is how the WSL helper reached the desktop app
|
|
* but never a relay.
|
|
*
|
|
* Order is load-bearing: the hash concatenates these files in sequence.
|
|
*
|
|
* Keep this file erasable-only TypeScript — build-relay.mjs imports it directly
|
|
* under Node's type stripping, which rejects enums, namespaces, and parameter
|
|
* properties.
|
|
*/
|
|
|
|
/** Every platform the relay is bundled for; each gets the full artifact set. */
|
|
export const RELAY_BUILD_PLATFORMS = [
|
|
'linux-x64',
|
|
'linux-arm64',
|
|
'darwin-x64',
|
|
'darwin-arm64',
|
|
'win32-x64',
|
|
'win32-arm64'
|
|
] as const
|
|
|
|
export type RelayBuildPlatform = (typeof RELAY_BUILD_PLATFORMS)[number]
|
|
|
|
export function isWindowsRelayPlatform(platform: string): boolean {
|
|
return platform.startsWith('win32-')
|
|
}
|
|
|
|
export type RelayArtifact = {
|
|
filename: string
|
|
/** Only Windows relays ship it; other hosts must neither receive nor probe it. */
|
|
windowsOnly?: boolean
|
|
/**
|
|
* Present only when the build could produce it, so it is hashed when there and
|
|
* never probed. Required artifacts stay required: a probe that demanded an
|
|
* optional one would loop forever redeploying a relay that is already correct.
|
|
*/
|
|
optional?: boolean
|
|
/**
|
|
* Forked by the relay daemon as a long-lived child of its own. These are relay
|
|
* infrastructure, never user work, and the reap gate subtracts them from a daemon's
|
|
* child census; see src/main/ssh/relay-daemon-service-children.ts.
|
|
*/
|
|
daemonServiceChild?: boolean
|
|
}
|
|
|
|
/** The bare Windows process-table addon; see docs/reference/windows-process-enumeration.md. */
|
|
export const RELAY_WINDOWS_PROCESS_TREE_FILENAME = 'windows-process-tree.node'
|
|
export const RELAY_OPENCODE_SQLITE_READER_FILENAME = 'opencode-sqlite-reader.cjs'
|
|
|
|
export const RELAY_ARTIFACTS: readonly RelayArtifact[] = [
|
|
{ filename: 'relay.js' },
|
|
{ filename: 'relay-watcher.js', daemonServiceChild: true },
|
|
{ filename: 'relay-ai-vault-service.js', daemonServiceChild: true },
|
|
{ filename: RELAY_OPENCODE_SQLITE_READER_FILENAME },
|
|
{ filename: 'managed-hook-runtime.js' },
|
|
// Forked by the AI Vault title reader; without it a relay answers every WSL
|
|
// title request with no title and no error.
|
|
{ filename: 'wsl-transcript-fs-process-entry.js' },
|
|
{ filename: 'node-pty-1.1.0-console-list-agent-patch.cjs', windowsOnly: true },
|
|
// The ConPTY teardown release the desktop's own node-pty patch already carries; pnpm patches do
|
|
// not cross the SSH boundary, so a relay ran the unpatched npm tree and leaked one Windows File
|
|
// handle per terminal for the life of the relay process.
|
|
{ filename: 'node-pty-1.1.0-windows-pty-teardown-patch.cjs', windowsOnly: true },
|
|
// Only Linux relays run it, but it ships everywhere: the manifest's only
|
|
// platform axis is Windows, and a second one would buy nothing but a fork in
|
|
// the hash. Its presence is what moves a host to a fresh relay directory, and
|
|
// therefore to a re-install that can apply it.
|
|
{ filename: 'node-pty-1.1.0-master-cloexec-patch.cjs' },
|
|
// Optional because only a Windows build machine can compile it. Without it the
|
|
// relay reads the process table through a PowerShell scan instead -- slower,
|
|
// but correct, so a relay built anywhere else is still shippable.
|
|
{ filename: RELAY_WINDOWS_PROCESS_TREE_FILENAME, windowsOnly: true, optional: true }
|
|
]
|
|
|
|
/**
|
|
* The daemon's own service children, by entry filename. Anything else under a relay pid is
|
|
* either user work or unidentified, and both keep the relay unreapable.
|
|
*/
|
|
export const RELAY_DAEMON_SERVICE_ENTRY_FILENAMES: readonly string[] = RELAY_ARTIFACTS.filter(
|
|
(artifact) => artifact.daemonServiceChild
|
|
).map((artifact) => artifact.filename)
|
|
|
|
/** Written after the artifacts, so it is never an input to its own hash. */
|
|
export const RELAY_VERSION_FILENAME = '.version'
|
|
|
|
/** Written last by the installer; its absence means a torn install. */
|
|
export const RELAY_INSTALL_COMPLETE_FILENAME = '.install-complete'
|
|
|
|
/** PID of the last relay daemon that bound a socket from this version dir; GC liveness evidence. */
|
|
export const RELAY_PID_FILENAME = '.relay-pid'
|
|
|
|
/** Artifacts every relay must have; the remote install probe requires each one. */
|
|
export function relayArtifactFilenames(isWindows: boolean): string[] {
|
|
return RELAY_ARTIFACTS.filter(
|
|
(artifact) => !artifact.optional && (!artifact.windowsOnly || isWindows)
|
|
).map((artifact) => artifact.filename)
|
|
}
|
|
|
|
/** Artifacts a build may or may not emit. Hashed when present, never probed. */
|
|
export function relayOptionalArtifactFilenames(isWindows: boolean): string[] {
|
|
return RELAY_ARTIFACTS.filter(
|
|
(artifact) => artifact.optional && (!artifact.windowsOnly || isWindows)
|
|
).map((artifact) => artifact.filename)
|
|
}
|