mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 00:03:15 +00:00
`isReapableRelayHusk` required `childCount === 0`, where `childCount` came from `pgrep -P <relay> | grep -c .`. But the relay forks service children of its own, and `relay-ai-vault-service.js` never exits once spawned. Any relay that had served a single AI Vault request therefore reported a non-zero child count forever, so the sweep answered `retained-live-work` for a superseded, disconnected relay holding no user work at all — and its version directory stayed pinned against GC by its own live socket. The probe now censuses each direct child instead of counting them, and the reap gate reads the count of children it could *not* positively identify as relay infrastructure. The asymmetry is the safety argument (docs/reference/ssh-execution-boundary.md): subtracting a child we can name is positive knowledge, assuming about one we cannot is not. An unrecognised argv, an argv `ps` would not print, and a host without `pgrep` all keep the relay unreapable. `reapEmptyRelayHuskCommand` re-runs the same census on the host immediately before signalling. Fixes #13614
98 lines
4.2 KiB
TypeScript
98 lines
4.2 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_ARTIFACTS: readonly RelayArtifact[] = [
|
|
{ filename: 'relay.js' },
|
|
{ filename: 'relay-watcher.js', daemonServiceChild: true },
|
|
{ filename: 'relay-ai-vault-service.js', daemonServiceChild: true },
|
|
{ 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 },
|
|
// 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'
|
|
|
|
/** 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)
|
|
}
|