mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 08:03:14 +00:00
93 lines
2.7 KiB
TypeScript
93 lines
2.7 KiB
TypeScript
import { createHash } from 'crypto'
|
|
import { join } from 'path'
|
|
import { PROTOCOL_VERSION } from './types'
|
|
|
|
export type DaemonConnectionInfo = {
|
|
socketPath: string
|
|
tokenPath: string
|
|
}
|
|
|
|
export type DaemonPidFile = {
|
|
pid: number
|
|
startedAtMs: number | null
|
|
entryPath?: string
|
|
}
|
|
|
|
export type DaemonProcessHandle = {
|
|
shutdown(): Promise<void>
|
|
}
|
|
|
|
export type DaemonLauncher = (socketPath: string, tokenPath: string) => Promise<DaemonProcessHandle>
|
|
|
|
export type DaemonSpawnerOptions = {
|
|
runtimeDir: string
|
|
launcher: DaemonLauncher
|
|
}
|
|
|
|
export class DaemonSpawner {
|
|
private runtimeDir: string
|
|
private launcher: DaemonLauncher
|
|
private handle: DaemonProcessHandle | null = null
|
|
private socketPath: string
|
|
private tokenPath: string
|
|
|
|
constructor(opts: DaemonSpawnerOptions) {
|
|
this.runtimeDir = opts.runtimeDir
|
|
this.launcher = opts.launcher
|
|
this.socketPath = getDaemonSocketPath(this.runtimeDir)
|
|
this.tokenPath = getDaemonTokenPath(this.runtimeDir)
|
|
}
|
|
|
|
async ensureRunning(): Promise<DaemonConnectionInfo> {
|
|
if (this.handle) {
|
|
return { socketPath: this.socketPath, tokenPath: this.tokenPath }
|
|
}
|
|
|
|
this.handle = await this.launcher(this.socketPath, this.tokenPath)
|
|
|
|
return { socketPath: this.socketPath, tokenPath: this.tokenPath }
|
|
}
|
|
|
|
// Why: after the daemon process dies unexpectedly, the cached handle is
|
|
// stale. Clearing it lets the next ensureRunning() fork a fresh daemon
|
|
// instead of returning the dead socket path.
|
|
resetHandle(): void {
|
|
this.handle = null
|
|
}
|
|
|
|
async shutdown(): Promise<void> {
|
|
if (!this.handle) {
|
|
return
|
|
}
|
|
const handle = this.handle
|
|
this.handle = null
|
|
await handle.shutdown()
|
|
}
|
|
}
|
|
|
|
export function getDaemonSocketPath(
|
|
runtimeDir: string,
|
|
protocolVersion = PROTOCOL_VERSION
|
|
): string {
|
|
// Why: Windows IPC servers use named pipes rather than filesystem socket
|
|
// files. Include the protocol version in the endpoint name so a daemon from
|
|
// an older build is never reused after a breaking protocol change.
|
|
if (process.platform === 'win32') {
|
|
const suffix = createHash('sha256').update(runtimeDir).digest('hex').slice(0, 12)
|
|
return `\\\\?\\pipe\\orca-terminal-host-v${protocolVersion}-${suffix}`
|
|
}
|
|
return join(runtimeDir, `daemon-v${protocolVersion}.sock`)
|
|
}
|
|
|
|
export function getDaemonTokenPath(runtimeDir: string, protocolVersion = PROTOCOL_VERSION): string {
|
|
return join(runtimeDir, `daemon-v${protocolVersion}.token`)
|
|
}
|
|
|
|
export function getDaemonPidPath(runtimeDir: string, protocolVersion = PROTOCOL_VERSION): string {
|
|
return join(runtimeDir, `daemon-v${protocolVersion}.pid`)
|
|
}
|
|
|
|
export function serializeDaemonPidFile(pidFile: DaemonPidFile): string {
|
|
return JSON.stringify(pidFile)
|
|
}
|