{"_id":"@adhd/sox-host-runtime","_rev":"5-27a1b41f1bb67ee76c9704b080fb36bd","name":"@adhd/sox-host-runtime","dist-tags":{"latest":"0.5.0"},"versions":{"0.2.0":{"name":"@adhd/sox-host-runtime","version":"0.2.0","license":"MIT","_id":"@adhd/sox-host-runtime@0.2.0","maintainers":[{"name":"pseudosky","email":"skywinston.sk@gmail.com"}],"dist":{"shasum":"0e4a8c1962fe31c85bbe7abaefb4c1e4bb04660b","tarball":"https://registry.npmjs.org/@adhd/sox-host-runtime/-/sox-host-runtime-0.2.0.tgz","fileCount":86,"integrity":"sha512-k7SKp9Hr0qkf9KCJF7nTJhRvPJkI5HOM/A9//+K5akGMGv/8TWpYZ/rulJYcnJCZ8XoyY8pQBRW1NGxfT4ip+w==","signatures":[{"sig":"MEYCIQCtdaMvCE5ui4WDI+zJ/o8tiudkNeKCECCC8tk93fyesAIhALNa2FPuSJIeOTCE2PZwUrksog6GaSXbltAefjwbcHyl","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":434046},"main":"./dist/index.js","_from":"file:adhd-sox-host-runtime-0.2.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"}},"_npmUser":{"name":"pseudosky","email":"skywinston.sk@gmail.com"},"_resolved":"/private/var/folders/yg/cfczgtx54bzfh74lx2_mv0z80000gp/T/fc85360beacd2b44ad8565204b54ad03/adhd-sox-host-runtime-0.2.0.tgz","_integrity":"sha512-k7SKp9Hr0qkf9KCJF7nTJhRvPJkI5HOM/A9//+K5akGMGv/8TWpYZ/rulJYcnJCZ8XoyY8pQBRW1NGxfT4ip+w==","_npmVersion":"11.6.2","description":"Sox host runtime — supervisor, event bus, hook loader, MCP registrar","directories":{},"_nodeVersion":"24.11.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/sox-host-runtime_0.2.0_1782451182450_0.08668584632585197","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@adhd/sox-host-runtime","version":"0.3.0","license":"MIT","_id":"@adhd/sox-host-runtime@0.3.0","maintainers":[{"name":"pseudosky","email":"skywinston.sk@gmail.com"}],"dist":{"shasum":"69bcf3298e37c795e7f3425f08acf8ed834b1ccf","tarball":"https://registry.npmjs.org/@adhd/sox-host-runtime/-/sox-host-runtime-0.3.0.tgz","fileCount":102,"integrity":"sha512-4TUusmjE43EBF+rExA1vNpL9LXShIH/8yWtDfcP6l2B+jJyGJ4x3pWjWxO23+XtYnYES+i+bI9sEssFzf7C8Pw==","signatures":[{"sig":"MEUCICnR4vx/DwfinQbUylYxg4IvHTOiCDQFcNtu9S6XLYD1AiEA3Jh8FPEa0biHgI4NQPGTdQB+lpsAtc1iSwwF2/JqO6Y=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":685419},"main":"./dist/index.js","_from":"file:adhd-sox-host-runtime-0.3.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"}},"_npmUser":{"name":"pseudosky","email":"skywinston.sk@gmail.com"},"_resolved":"/private/var/folders/yg/cfczgtx54bzfh74lx2_mv0z80000gp/T/8e91f10ebd4f0f781cc0ae9cf695a32f/adhd-sox-host-runtime-0.3.0.tgz","_integrity":"sha512-4TUusmjE43EBF+rExA1vNpL9LXShIH/8yWtDfcP6l2B+jJyGJ4x3pWjWxO23+XtYnYES+i+bI9sEssFzf7C8Pw==","_npmVersion":"11.6.2","description":"Sox host runtime — supervisor, event bus, hook loader, MCP registrar","directories":{},"_nodeVersion":"24.11.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/sox-host-runtime_0.3.0_1786144738090_0.4487743137080078","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@adhd/sox-host-runtime","version":"0.4.0","license":"MIT","_id":"@adhd/sox-host-runtime@0.4.0","maintainers":[{"name":"pseudosky","email":"skywinston.sk@gmail.com"}],"dist":{"shasum":"7bd695e754d72627dda526278d4c4c65b5a3ec5b","tarball":"https://registry.npmjs.org/@adhd/sox-host-runtime/-/sox-host-runtime-0.4.0.tgz","fileCount":114,"integrity":"sha512-wyUwaPQvFy3YxHlEKbTUVtorKP+Rg0skm8siVILD33JQRfrCvajdNLQCg6mktLHnPtgccDBqEZLS9TpEKt1nBQ==","signatures":[{"sig":"MEQCIFukQafdURV52cg43ioWae7OjOgKRuBCRhlSq3Fh23bVAiA0Nhce3kACWZ7nYV2pu4UylbXaXp6EDUqOPY3hXxzbXg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":750146},"main":"./dist/index.js","_from":"file:adhd-sox-host-runtime-0.4.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"}},"_npmUser":{"name":"pseudosky","email":"skywinston.sk@gmail.com"},"_resolved":"/private/var/folders/yg/cfczgtx54bzfh74lx2_mv0z80000gp/T/e89ace304dbb17b73ea9598b2daf483b/adhd-sox-host-runtime-0.4.0.tgz","_integrity":"sha512-wyUwaPQvFy3YxHlEKbTUVtorKP+Rg0skm8siVILD33JQRfrCvajdNLQCg6mktLHnPtgccDBqEZLS9TpEKt1nBQ==","_npmVersion":"11.6.2","description":"Sox host runtime — supervisor, event bus, hook loader, MCP registrar","directories":{},"_nodeVersion":"24.11.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/sox-host-runtime_0.4.0_1787113047864_0.7567337867560922","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"name":"@adhd/sox-host-runtime","version":"0.4.1","license":"MIT","_id":"@adhd/sox-host-runtime@0.4.1","maintainers":[{"name":"pseudosky","email":"skywinston.sk@gmail.com"}],"dist":{"shasum":"c5946b1db445fbc47dbf7e26ff966fb3459255fb","tarball":"https://registry.npmjs.org/@adhd/sox-host-runtime/-/sox-host-runtime-0.4.1.tgz","fileCount":114,"integrity":"sha512-K7MmW5i18K8JmLmjXTuKNJsG54oLOy+LKG4YnKPFljOEV/bInv7a8NLGHGdyhiYcv4Q7BsKFu4t8hghr708r7w==","signatures":[{"sig":"MEUCIASoGh+NKiKs3WycH1VHqlSko47tMuGJZ3v8ImjNhVXWAiEAjLDIxAP18YBpx2f72EoPUx9gHalXw7HIXsyhB3/6qoA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":759618},"main":"./dist/index.js","_from":"file:adhd-sox-host-runtime-0.4.1.tgz","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"}},"_npmUser":{"name":"pseudosky","email":"skywinston.sk@gmail.com"},"_resolved":"/private/var/folders/yg/cfczgtx54bzfh74lx2_mv0z80000gp/T/51e77cdb9c10181d08b731708dda1712/adhd-sox-host-runtime-0.4.1.tgz","_integrity":"sha512-K7MmW5i18K8JmLmjXTuKNJsG54oLOy+LKG4YnKPFljOEV/bInv7a8NLGHGdyhiYcv4Q7BsKFu4t8hghr708r7w==","_npmVersion":"11.6.2","description":"Sox host runtime — supervisor, event bus, hook loader, MCP registrar","directories":{},"_nodeVersion":"24.11.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/sox-host-runtime_0.4.1_1787259631813_0.6364474092194885","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@adhd/sox-host-runtime","version":"0.5.0","description":"Sox host runtime — supervisor, event bus, hook loader, MCP registrar","license":"MIT","publishConfig":{"access":"public"},"engines":{"node":">=20"},"main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"}},"keywords":["mcp","host","runtime","supervisor","typescript"],"repository":{"type":"git","url":"git+https://github.com/PseudoSky/adhd.git"},"homepage":"https://github.com/PseudoSky/adhd","dependencies":{"@adhd/sox-listen-guard":"^0.1.1"},"_id":"@adhd/sox-host-runtime@0.5.0","bugs":{"url":"https://github.com/PseudoSky/adhd/issues"},"_integrity":"sha512-uUUGqLBPCIlS0Tk/vLi5Ed7gLhDPrDPPs+IP2eC8oO89ynQoDx3elCxKgnBwYAhBiH9KIrFnRfgz6y82CMwpGg==","_resolved":"/private/var/folders/yg/cfczgtx54bzfh74lx2_mv0z80000gp/T/2d99c42282a84d26464d4cf05d9d5f5b/adhd-sox-host-runtime-0.5.0.tgz","_from":"file:adhd-sox-host-runtime-0.5.0.tgz","_nodeVersion":"24.11.1","_npmVersion":"11.6.2","dist":{"integrity":"sha512-uUUGqLBPCIlS0Tk/vLi5Ed7gLhDPrDPPs+IP2eC8oO89ynQoDx3elCxKgnBwYAhBiH9KIrFnRfgz6y82CMwpGg==","shasum":"94fba780f29b70445173562e6db2ad18690eebba","tarball":"https://registry.npmjs.org/@adhd/sox-host-runtime/-/sox-host-runtime-0.5.0.tgz","fileCount":116,"unpackedSize":823586,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDFdFRVuDYG7gSKm35iF/DEaHBl4z0P2NXSq7+TmlrV4QIhAIiZbrGKUezLk/wEaIe5Z0pmhwoepYHgIF12gQ5wC1jB"}]},"_npmUser":{"name":"pseudosky","email":"skywinston.sk@gmail.com"},"directories":{},"maintainers":[{"name":"pseudosky","email":"skywinston.sk@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sox-host-runtime_0.5.0_1788566485342_0.6354845198990535"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-26T05:19:42.274Z","modified":"2026-09-05T00:01:25.632Z","0.2.0":"2026-06-26T05:19:42.604Z","0.3.0":"2026-08-07T23:18:58.285Z","0.4.0":"2026-08-19T04:17:27.999Z","0.4.1":"2026-08-20T21:00:31.971Z","0.5.0":"2026-09-05T00:01:25.472Z"},"license":"MIT","description":"Sox host runtime — supervisor, event bus, hook loader, MCP registrar","maintainers":[{"name":"pseudosky","email":"skywinston.sk@gmail.com"}],"readme":"# @adhd/sox-host-runtime\n\nThe process and extension host runtime underneath the `soxe` CLI ecosystem: it loads an `extensions.lock` file, spawns and supervises each extension's process, dispatches lifecycle events (`PreToolUse`, `PostToolUse`, `SessionEnd`, …) through an isolated hook bus, registers MCP servers and their tools, and owns every path soxe writes to disk (`~/.adhd/sox-ecosystem/…`, ADR-0004). It also carries the operational hardening that keeps a long-running host honest: crash-loop capping, verified process kill + orphan reaping, cross-scope singleton healing, and generators for real OS service units (launchd `.plist` / systemd `.service`).\n\nThis package has no store/adapter dependency and is not part of the `@adhd/sox-store-adapter` family — it manages OS processes and the filesystem, not a database.\n\n```bash\npnpm add @adhd/sox-host-runtime\n```\n\n## Quick start\n\nThe hook loader + event bus is the core dispatch primitive — every lifecycle notification in a soxe host flows through it. A throwing hook never aborts the chain (`fireIsolated`), so one broken extension can't take down the others:\n\n```typescript\nimport { HookLoader, HostEventBus } from '@adhd/sox-host-runtime';\n\nconst hookLoader = new HookLoader();\nhookLoader.register(\n  { id: 'audit-logger', event: 'SessionEnd', order: 1 },\n  async (ctx) => {\n    console.log(`[audit] session ended at ${ctx.timestamp}`);\n  },\n);\n\nconst bus = new HostEventBus(hookLoader);\nbus.on('SessionEnd', async () => {\n  console.log('cleanup reaction ran');\n});\n\nconst results = await bus.emit('SessionEnd', { timestamp: new Date().toISOString() });\n// emit() runs the HookLoader's registered hooks first, then the bus's own `on()`\n// reactions — one result per participant, `undefined` on success or `{ id, error }`\n// on a caught throw.\nconsole.log(results); // [ undefined, undefined ]  (one for 'audit-logger', one for the reaction)\n```\n\n### Starting the extension runtime from a lockfile\n\n`startRuntime` reads `extensions.lock`, activates every declared extension (spawning `type:service` entries under a supervised child process), and writes a `runtime.json` record you can query later. `stopRuntime` tears everything down cleanly (SIGTERM, then SIGKILL after the extension's declared grace period):\n\n```typescript\nimport { startRuntime, stopRuntime, getRuntimeRecord } from '@adhd/sox-host-runtime';\n\nconst record = await startRuntime({\n  scope: 'project',\n  lockfilePath: './extensions.lock',\n  configPath: './sox.config.json',\n  runtimeFilePath: './.adhd/sox-ecosystem/run/runtime.json',\n  root: process.cwd(),\n});\n\nconsole.log(record.entries.map((e) => `${e.id} → pid ${e.pid}`));\n\n// later, from the same process or a fresh read of the file on disk:\nconst current = getRuntimeRecord('./.adhd/sox-ecosystem/run/runtime.json');\n\nawait stopRuntime({ scope: 'project', runtimeFilePath: './.adhd/sox-ecosystem/run/runtime.json' });\n```\n\n## API reference\n\n### Hook loader & event bus\n\n```typescript\nclass HookLoader {\n  register(manifest: HookManifest, handler: HookHandler): void;\n  hooksFor(event: string): ReadonlyArray<RegisteredHook>;\n  fire(event: string, ctx: Omit<HookContext, 'event'>): Promise<void>;\n  // A throwing hook does NOT abort the chain — caught and recorded, execution continues.\n  fireIsolated(event: string, ctx: Omit<HookContext, 'event'>): Promise<Array<{ id: string; error: unknown } | undefined>>;\n  orderedIdsFor(event: string): string[];\n  clear(): void;\n}\nfunction compareHooks(a: RegisteredHook, b: RegisteredHook): number;\n\nconst LIFECYCLE_EVENTS: readonly ['PreToolUse', 'PostToolUse', 'SessionEnd', 'ScopePromotionProposed', 'Stop'];\nfunction isLifecycleEvent(s: string): s is LifecycleEvent;\n\nclass HostEventBus {\n  constructor(hookLoader: HookLoader);\n  on(event: LifecycleEvent, handler: LifecycleHandler, id?: string): string;\n  off(event: LifecycleEvent, id: string): boolean;\n  emit(event: LifecycleEvent, partial: Omit<HookContext, 'event'>): Promise<BusEmitResult>;\n  reactionsFor(event: LifecycleEvent): string[];\n  clearReactions(): void;\n}\nfunction createEventBus(hookLoader: HookLoader): HostEventBus;\n```\n\n### Runtime lifecycle (`extensions.lock` → running processes)\n\n```typescript\nfunction loadFromLockfile(opts?: LoaderOptions): Promise<LoaderResult>;\n// LoaderResult: { activated: ActivatedHandle[]; skipped: {key, reason}[]; errors: {key, error}[]; hookLoader; commandRegistry }\n\nfunction startRuntime(opts: StartRuntimeOptions): Promise<RuntimeRecord>;\nfunction stopRuntime(opts: StopRuntimeOptions): Promise<void>;\nfunction stopExtension(runtimeFilePath: string, id: string): Promise<boolean>;\nfunction reconcileRuntime(runtimeFilePath: string, configPath: string): Promise<string[]>;\nfunction getRuntimeRecord(runtimeFilePath: string): RuntimeRecord | null;\nfunction reapOrphansForExtension(id: string, opts?: { lockfilePath?: string; runtimeFilePath?: string; graceMs?: number; log?: (msg: string) => void }): Promise<ReapExtensionResult>;\nfunction runtimeFilePathFromLockfile(lockfilePath: string): string;\nfunction getRuntimeFilePath(scopeLockfilePath: string): string;\nfunction getScopePaths(scope: string, root: string): { config: string; lockfile: string };\nfunction resolveExtensionDir(source: string, root: string): string | null;\n```\n\n> `getRegistrar(runtimeFilePath)` is **deprecated**: it reads an in-process `Map`, so it only returns non-null in the exact process that called `startRuntime` — a separate CLI invocation always gets `null`. Use `record.execSocketPath` (present only in supervisor/lockfile-based start mode) and talk to the running supervisor over its Unix-domain exec socket instead.\n\n### Process supervision\n\n```typescript\nclass ProcessSupervisor {\n  constructor(opts: SupervisorOptions); // { key, entrypointPath, args?, env?, lifecycle, permissions?, crashLoop?, ... }\n  policy(): Policy;\n  start(): Promise<void>;\n  stop(): Promise<void>;   // SIGTERM the whole process group, then SIGKILL after stop_timeout_ms\n  kill(): Promise<void>;\n  restart(): Promise<void>;\n  isHealthy(): boolean;\n  isCrashLooped(): boolean; // sticky — true once the crash-loop cap has fired; needs an explicit start()/restart()\n  pid(): number | null;\n}\nfunction expandTilde(p: string): string;\n```\n\nPermission enforcement (`opts.permissions`) is **opt-in**: a `SupervisorOptions` with no `permissions` block spawns byte-identically to a plain `child_process.spawn` (env + cwd untouched). Declaring a block switches that domain to deny-by-default:\n\n```typescript\nimport { compilePolicy, compilePolicyFromEnv } from '@adhd/sox-host-runtime';\n\nconst policy = compilePolicy({ fs: { read: ['/tmp/**'] }, network: { outbound: ['api.example.com'] } });\npolicy.allowsFsRead('/tmp/data.json'); // true\npolicy.allowsFsRead('/etc/passwd');    // false — fs domain was declared, so it's deny-by-default\npolicy.allowsNetwork('other.example.com'); // false\n\n// A spawned child reconstructs the same decisions from policy.toEnv() via:\nconst restored = compilePolicyFromEnv(process.env);\n```\n\n### Crash-loop capping\n\n```typescript\nconst CRASH_LOOP_MAX_FAILURES = 5;\nconst CRASH_LOOP_WINDOW_MS = 60000;\n\nclass CrashLoopGuard {\n  constructor(opts: CrashLoopGuardOptions);\n  failuresInWindow(): number;\n  // ...records an unexpected exit; once 5 exits land inside a rolling 60s window\n  // the guard trips and ProcessSupervisor stops auto-restarting (isCrashLooped() → true).\n}\nfunction crashLoopMarkerDir(): string;\nfunction crashLoopMarkerPath(markerDir: string, key: string): string;\nfunction readCrashLoopMarker(markerPath: string): CrashLoopMarker | null;\nfunction listCrashLoopMarkers(markerDir: string): CrashLoopMarker[];\nfunction clearCrashLoopMarker(markerPath: string): void;\n```\n\n### Shutdown grace-margin discipline\n\n```typescript\nconst SOX_SHUTDOWN_SAFETY_MARGIN_MS = 1000;\nfunction resolveShutdownSafetyNetMs(stopTimeoutMs: number, marginMs?: number): number;\nfunction resolveStopTimeoutMsFromEnv(env?: NodeJS.ProcessEnv, fallbackMs?: number): number;\n```\n\nDerives a service's own internal shutdown safety-net timeout from its resolved `stop_timeout_ms` grace, with a guaranteed 1000ms of headroom before the reaper's SIGKILL — the canonical fix for two services that had each hand-picked a safety-net literal that happened to race the reaper's escalation instead of beating it.\n\n### MCP server registration\n\n```typescript\nclass McpRegistrar {\n  register(serverKey: string, proc: ChildProcess, timeoutMs?: number): Promise<McpRegistration>;\n  registrations(): McpRegistration[];\n  tools(serverKey: string): McpToolDescriptor[];\n  call(serverKey: string, toolName: string, args: Record<string, unknown>, timeoutMs?: number): Promise<McpCallResult>;\n  deregister(serverKey: string): void;\n  allToolNames(): Array<{ serverKey: string; toolName: string }>;\n}\nclass McpClient {\n  constructor(proc: ChildProcess);\n  call(method: string, params?: unknown, timeoutMs?: number): Promise<unknown>;\n  close(): void;\n}\n```\n\n### Data paths (ADR-0004 — the one resolver)\n\nEvery soxe data path — the install ledger, supervisor registry, per-scope config/lockfile/ledger, run directory, sockets, logs — is computed here. It reads `SOX_ECOSYSTEM_HOME` (default `~/.adhd/sox-ecosystem/`) at *call* time, so a test can override it after import:\n\n```typescript\nimport { dataRoot, scopeConfigPaths, runDir, socketDir } from '@adhd/sox-host-runtime';\n\ndataRoot('project', '/path/to/repo'); // '/path/to/repo/.adhd/sox-ecosystem'\nscopeConfigPaths('user');             // { config: '...', lockfile: '...' }\nrunDir();                             // '$userDataRoot/run'\nsocketDir();                          // '$userDataRoot/run/supervisors'\n```\n\n```typescript\ntype DataScope = 'org' | 'user' | 'project' | 'local';\nconst DATA_SUBDIR: string;\nfunction userDataRoot(): string;\nfunction dataRoot(scope: DataScope, root?: string): string;\nfunction scopeConfigPaths(scope: DataScope, root?: string): { config: string; lockfile: string };\nfunction ledgerPathFor(scope: DataScope, root?: string): string;\nfunction ownershipPathFor(scope: DataScope, root?: string): string;\nfunction storeRootFor(scope: DataScope, root?: string): string;\nfunction installRegistryPath(): string;\nfunction supervisorsPath(): string;\nfunction logDirFor(supervisorId: string): string;\n```\n\n### Env scrubbing for spawned children\n\n```typescript\nfunction scrubEnv(env: Record<string, string | undefined>): ScrubbedEnv;\nfunction scrubEnvReported(env: Record<string, string | undefined>): { env: Record<string, string>; dropped: string[] };\nfunction isDeniedEnvKey(key: string): boolean;\nfunction formatDeniedEnvWarning(dropped: string[]): string;\nconst ENV_BASE_ALLOW: readonly string[];\nconst ENV_ALLOW_PREFIXES: readonly string[];\nconst ENV_DENY_PREFIXES: readonly string[];\n```\n\nForwarded to a spawned child: base process keys (`PATH`, `HOME`, locale), everything prefixed `NODE_*`, and everything prefixed `SOX_*` **except** `SOX_PERM_*` (the compiled sandbox policy) and `SOX_CONFIG_*` (the resolved config cascade) — those two namespaces are host-authoritative and are never inherited from an ambient shell.\n\n### Verified kill + orphan reaping\n\n```typescript\nfunction pidAlive(pid: number): boolean;\nfunction killAndVerify(pid: number, opts?: KillOptions): Promise<KillOutcome>; // 'already-dead' | 'term' | 'kill' | 'undead'\nfunction identityToken(source: string): string;\nfunction findOrphansByIdentity(token: string, opts?: { ... }): OrphanMatch[];\nfunction findOrphansByServiceId(serviceId: string, token: string, opts?: { ... }): OrphanMatch[];\nfunction reapByIdentity(token: string, opts?: KillOptions & { ... }): Promise<ReapResult>;\nfunction reapBySource(source: string, opts?: KillOptions & { ... }): Promise<ReapResult>;\nfunction snapshotProcesses(): PsProcess[];\nfunction gatherProcessSnapshot(supervisorRegistryEntries: Array<{ ... }>): ProcessSnapshotRow[];\n```\n\nMatching is by a precise entrypoint identity token resolved from the lockfile/runtime record — never a bare `node` argv match — so `soxe stop`/reap can find and kill a daemon by *what it is*, even after the supervisor is gone and the process is detached (`PPID 1`).\n\n### OS service units (launchd / systemd)\n\n```typescript\nfunction detectOsSupervisor(platform?: NodeJS.Platform): 'launchd' | 'systemd';\nfunction getOsUnitPlatform(kind?: OsSupervisor): OsUnitPlatform;\nfunction deriveOsUnitSpec(opts: { ... }): OsUnitSpec;\nfunction enableOsUnit(spec: OsUnitSpec, platform: OsUnitPlatform, opts?: EnableOptions): EnableResult;\nfunction disableOsUnit(label: string, platform: OsUnitPlatform, opts?: DisableOptions): DisableResult;\nfunction restartOsUnit(newSpec: OsUnitSpec, platform: OsUnitPlatform, lastKnownGoodPath: string | undefined, opts?: RestartOptions): Promise<RestartResult>;\nfunction restartAndVerify(opts: RestartAndVerifyOptions): Promise<RestartAndVerifyResult>;\nfunction updateOsUnit(spec: OsUnitSpec, platform: OsUnitPlatform, opts: UpdateOsUnitOptions): Promise<UpdateOsUnitResult>;\nfunction reloadAndVerifyOsUnit(spec: OsUnitSpec, platform: OsUnitPlatform, opts?: ReloadAndVerifyOsUnitOptions): ReloadAndVerifyOsUnitResult;\nfunction verifyUnitOnDisk(unitPath: string): UnitDiskVerification;\nfunction classifyOsUnitOrphan(fileName: string, disk: UnitDiskVerification): 'heal' | 'alarm' | 'unattributable';\n```\n\n`LaunchdPlatform` and `SystemdPlatform` implement the common `OsUnitPlatform` interface, so a caller drives either supervisor through the same `enable`/`disable`/`restart` calls above.\n\n### Cross-scope singleton healing & log management\n\n```typescript\nfunction resolveStoreResource(manifestPath: string, configEnv: Record<string, string>): StoreResource;\nfunction singletonKey(id: string, resource: StoreResource): string | null;\nfunction healSingletonDuplicates(opts: { ... }): Promise<HealResult>;\nfunction findCrossScopeSharers(targetScope: string, targetResource: StoreResource, others: ScopeResource[]): string[];\n\nclass LogManager {\n  constructor(opts: LogManagerOptions);\n  // pipes a supervised child's stdout/stderr to a rotating, date-stamped log file\n}\nfunction findAllLogStreamsForExt(extId: string, supervisorId: string, scope: string): LogStreamDescriptor[];\nfunction rotateOsUnitLogs(opts: RotateOsUnitLogsOptions): void;\n```\n\n### Global supervisor registry, start lock, and stale-state GC\n\n```typescript\n// registry.ts — the durable record of every supervisor process this machine has started\nfunction getSupervisorsFilePath(): string;\nfunction readSupervisorsFile(filePath?: string): SupervisorsFile;\nfunction writeSupervisorsFile(file: SupervisorsFile, filePath?: string): void;\nfunction registerSupervisor(entry: SupervisorRegistryEntry): void;\nfunction deregisterSupervisor(supervisorId: string): void;\nfunction listRegisteredSupervisors(): SupervisorRegistryEntry[];\n\n// lock.ts — prevents two `soxe start` invocations for the same scope from racing\nfunction computeSupervisorId(scope: string, root: string): string;\n// Throws if a live holder still holds the lock past opts.timeoutMs.\nfunction acquireStartLock(supervisorId: string, opts?: { timeoutMs?: number }): { release: () => void };\n\n// gc.ts — probes for and clears stale registry entries whose process is gone\nfunction probeSocket(socketPath: string, timeoutMs: number): Promise<boolean>;\nfunction probeEntryLiveness(entry: SupervisorRegistryEntry, opts?: { socketTimeoutMs?: number }): Promise<'alive' | 'dead'>;\n// Reads the registry with lazy GC applied — dead entries are removed as a side-effect\n// and excluded from the returned list.\nfunction readGlobalRegistry(opts?: { socketTimeoutMs?: number }): Promise<SupervisorRegistryEntry[]>;\n```\n\n### Audit log (soft policy enforcement for in-process extensions)\n\n`agent`/`skill`/`hook`/`command` extensions run in-process rather than as a spawned child, so there's no OS process boundary to enforce a `Policy` at — `makeInprocHandle` gives them the same `Policy`-shaped `allows*()` checks with every decision recorded to an in-memory audit trail instead:\n\n```typescript\nfunction auditAccess(extensionId: string, type: ExtensionType, domain: AccessDomain, target: string, decision: AuditDecision): void;\nfunction getAuditLog(): readonly AuditEntry[];\nfunction clearAuditLog(): void;\nfunction makeInprocHandle(extensionId: string, type: ExtensionType, policy: Policy): InprocPolicyHandle;\n\ntype ExtensionType = 'agent' | 'skill' | 'hook' | 'command';\ntype AccessDomain = 'fs' | 'socket' | 'network';\ntype AuditDecision = 'allow' | 'deny';\n```\n\n### Adapters (per extension type)\n\n```typescript\nfunction activateMcp(opts: McpAdapterOptions): Promise<McpAdapterHandle>;\nfunction activateHook(opts: HookAdapterOptions): Promise<HookAdapterHandle>;\nfunction activateAgent(opts: AgentAdapterOptions): Promise<AgentAdapterHandle>;\nfunction activateSkill(opts: SkillAdapterOptions): Promise<SkillAdapterHandle>;\nfunction activateCommand(opts: CommandAdapterOptions): Promise<CommandAdapterHandle>;\nclass CommandRegistry { /* backs activateCommand */ }\n```\n\nThese are what `loadFromLockfile` calls internally per lockfile entry `type` (`mcp-server`, `hook`, `agent`, `skill`, `command`) — import them directly only if you're driving a single extension type outside the full loader.\n\n## Invariants / gotchas\n\n- **`getRegistrar()` only works in-process.** It reads an in-memory map populated by `startRuntime()` in the *same* process. A second CLI invocation always sees `null` — use the exec socket (`record.execSocketPath`) instead.\n- **The non-permissions-enforced spawn path is byte-identical to a plain `child_process.spawn`.** Declaring `permissions` on `SupervisorOptions` is what switches a domain to deny-by-default; omitting it is fully backward compatible.\n- **Crash-loop is sticky.** Once `isCrashLooped()` is true (5 unexpected exits in a 60s window, by default), the supervisor will not auto-restart again until an explicit `start()`/`restart()`.\n- **`SOX_ECOSYSTEM_HOME` never reroutes host discovery paths** — it only moves soxe's own bookkeeping (install ledger, supervisors, runtime records, logs). Host *discovery* paths are controlled exclusively by `SOX_SANDBOX_ROOT` (in `@adhd/sox-host-registry`), not by this package.\n- **`SOX_PERM_*` and `SOX_CONFIG_*` are never forwarded to a spawned child from the ambient shell**, even though every other `SOX_*` variable is — those two namespaces are host-authoritative (compiled sandbox policy, resolved config cascade) and letting a caller inject them would be a privilege escalation.\n","readmeFilename":"README.md","homepage":"https://github.com/PseudoSky/adhd","keywords":["mcp","host","runtime","supervisor","typescript"],"repository":{"type":"git","url":"git+https://github.com/PseudoSky/adhd.git"},"bugs":{"url":"https://github.com/PseudoSky/adhd/issues"}}