import { MatrixActor } from '@open-matrix/core/core/MatrixActor.js'; import { MatrixRuntime } from '@open-matrix/core/runtime/MatrixRuntime.js'; import { LoadedPackageActor, RuntimeControlActor, RUNTIME_CONTROL_SERVICE, loadedPackageMount, loadedPackageServiceName, packageKeyFromPackageRef, runtimeControlMount, type ILoadedPackageService, type IRuntimeControlService, type LoadedPackageManifest, type LoadedPackageRecord, type RuntimeControlState, } from '@open-matrix/system-runtimes'; type MatrixIntrospect = ReturnType; type MatrixIntrospectChild = NonNullable[number]; export type RunnerRuntimeLifecycleState = | 'starting' | 'ready' | 'live' | 'stopping' | 'stopped' | 'failed'; export interface IRunnerRuntimeControlService { readonly runtimeId: string; readonly startedAt: string; getState(): RunnerRuntimeLifecycleState; getRuntime(): MatrixRuntime; requestShutdown(reason: string): void; } const RUNNER_RUNTIME_CONTROL_SERVICE = 'runner-runtime-control'; export interface IMountedLoadedPackage { readonly packageRef: string; readonly packageKey: string; readonly mount: string; } export interface IMountRunnerRuntimeControlOptions { readonly runtime: MatrixRuntime; readonly runtimeId: string; readonly runtimeMount: string; readonly controlMount: string; readonly startedAt: string; getState(): RunnerRuntimeLifecycleState; requestShutdown(reason: string): void; /** * Optional loaded-package descriptor. When provided, a per-loaded-package * actor is mounted at `system.runtimes..packages.` * alongside the runtime control actor. Slice 1a wires this for runtimes * that pass a single manifest at startup; multi-package runtimes are * future work. */ readonly loadedPackage?: { readonly packageRef: string; readonly version?: string; readonly manifest?: LoadedPackageManifest; }; } export interface IMountRunnerRuntimeControlResult { readonly controlMount: string; readonly runtimeMount: string; readonly loadedPackages: readonly IMountedLoadedPackage[]; } export class RunnerRuntimeControlActor extends MatrixActor { static readonly hasDynamicChildren = true; static description = 'Runtime control actor for Host-managed runner processes.'; static override accepts: Record = { 'runtime.status': { description: 'Report runtime lifecycle and inventory status', options: 'object?' }, 'runtime.ready': { description: 'Report whether the runtime is ready for traffic', options: 'object?' }, 'runtime.health': { description: 'Report runtime health', options: 'object?' }, 'runtime.shutdown': { description: 'Request graceful runtime shutdown', reason: 'string?' }, }; protected override onIntrospect(payload?: { depth?: 'basic' | 'full' }): MatrixIntrospect { const base = super.onIntrospect(payload); const service = this._service(); const currentMount = this.mount; const children = service.getRuntime().getAllComponents() .map((component): MatrixIntrospectChild | null => { const mount = component.mount.trim(); if (!mount || mount === currentMount) { return null; } return { id: mount, slot: 'runtime', mount, componentClass: component.constructor.name || 'MatrixActor', accepts: [], emits: [], tracks: [], }; }) .filter((child): child is MatrixIntrospectChild => child !== null) .sort((left, right) => left.mount.localeCompare(right.mount)); return { ...base, children: [...(base.children ?? []), ...children], }; } onRuntimeStatus(): Record { const service = this._service(); const runtime = service.getRuntime(); return { ok: true, runtimeId: service.runtimeId, pid: process.pid, ...(process.env.MATRIX_HOST_HOME ? { hostHome: process.env.MATRIX_HOST_HOME } : {}), state: service.getState(), startedAt: service.startedAt, uptimeMs: Date.now() - Date.parse(service.startedAt), actorCount: runtime.getAllComponents().length, mounts: runtime.getAllComponents().map((component) => component.mount), }; } onRuntimeReady(): Record { const service = this._service(); const state = service.getState(); return { ok: true, runtimeId: service.runtimeId, ready: state === 'ready' || state === 'live', state, pid: process.pid, ...(process.env.MATRIX_HOST_HOME ? { hostHome: process.env.MATRIX_HOST_HOME } : {}), }; } onRuntimeHealth(): Record { const service = this._service(); const state = service.getState(); return { ok: true, healthy: state === 'ready' || state === 'live', runtimeId: service.runtimeId, state, pid: process.pid, ...(process.env.MATRIX_HOST_HOME ? { hostHome: process.env.MATRIX_HOST_HOME } : {}), }; } onRuntimeShutdown(payload?: { reason?: string }): Record { const service = this._service(); const reason = typeof payload?.reason === 'string' && payload.reason.trim().length > 0 ? payload.reason.trim() : 'runtime.shutdown'; setTimeout(() => service.requestShutdown(reason), 0); return { ok: true, accepted: true, runtimeId: service.runtimeId, state: 'stopping', reason, }; } private _service(): IRunnerRuntimeControlService { const service = this.context?.getService(RUNNER_RUNTIME_CONTROL_SERVICE); if (!service) { throw new Error('Runner runtime control service is not registered'); } return service; } } export async function mountRunnerRuntimeControl( options: IMountRunnerRuntimeControlOptions, ): Promise { const service: IRunnerRuntimeControlService = { runtimeId: options.runtimeId, startedAt: options.startedAt, getState: options.getState, getRuntime: () => options.runtime, requestShutdown: options.requestShutdown, }; options.runtime.registerService(RUNNER_RUNTIME_CONTROL_SERVICE, service); const canonicalRuntimeMount = runtimeControlMount(options.runtimeId); // Build the loaded-package record set (Slice 1a: zero or one package). const loadedPackages: LoadedPackageRecord[] = []; if (options.loadedPackage && options.loadedPackage.packageRef.trim().length > 0) { const packageRef = options.loadedPackage.packageRef.trim(); const packageKey = packageKeyFromPackageRef(packageRef); const record: LoadedPackageRecord = { packageRef, packageKey, mount: loadedPackageMount(options.runtimeId, packageRef), state: 'loaded', loadedAt: Date.parse(options.startedAt) || Date.now(), ...(options.loadedPackage.version ? { version: options.loadedPackage.version } : {}), ...(options.loadedPackage.manifest ? { manifest: options.loadedPackage.manifest } : {}), }; loadedPackages.push(record); } // Register the runtime-control service consumed by RuntimeControlActor. const runtimeControlService: IRuntimeControlService = { getRuntimeState: (): RuntimeControlState => ({ runtimeId: options.runtimeId, ...(process.env.MATRIX_RUNTIME_DISPLAY_NAME ? { displayName: process.env.MATRIX_RUNTIME_DISPLAY_NAME } : {}), ...(loadedPackages[0]?.packageRef ? { packageRef: loadedPackages[0].packageRef } : {}), controlMount: canonicalRuntimeMount, state: lifecycleStateToControlState(options.getState()), health: { status: 'ok', updatedAt: Date.now(), }, registeredAt: Date.parse(options.startedAt) || Date.now(), lastHeartbeat: Date.now(), }), getLoadedPackages: () => loadedPackages, requestShutdown: options.requestShutdown, requestReload: async () => ({ accepted: false, reason: 'runtime.reload is not implemented in Slice 1a' }), }; options.runtime.registerService(RUNTIME_CONTROL_SERVICE, runtimeControlService); // Mount the canonical Runtime control actor at system.runtimes.. // Slice 1b (control-actor-nav): the legacy `.control` sibling mount has been // removed. The canonical runtime control mount is `system.runtimes.`. if (!options.runtime.hasComponent(canonicalRuntimeMount)) { await options.runtime.createSupervised(RuntimeControlActor, canonicalRuntimeMount); } // Mount per-loaded-package actors and register their per-package services. const mountedLoadedPackages: IMountedLoadedPackage[] = []; for (const record of loadedPackages) { const loadedPackageService: ILoadedPackageService = { getRuntimeId: () => options.runtimeId, getLoadedPackage: () => record, requestReload: async () => ({ accepted: false, reason: 'package.reload is not implemented in Slice 1a' }), }; options.runtime.registerService(loadedPackageServiceName(record.packageKey), loadedPackageService); if (!options.runtime.hasComponent(record.mount)) { await options.runtime.createSupervised(LoadedPackageActor, record.mount); } mountedLoadedPackages.push({ packageRef: record.packageRef, packageKey: record.packageKey, mount: record.mount, }); } return { controlMount: canonicalRuntimeMount, runtimeMount: canonicalRuntimeMount, loadedPackages: mountedLoadedPackages, }; } function lifecycleStateToControlState(state: RunnerRuntimeLifecycleState): RuntimeControlState['state'] { switch (state) { case 'starting': return 'starting'; case 'ready': case 'live': return 'running'; case 'stopping': return 'stopping'; case 'stopped': case 'failed': return 'stopped'; } } export function defaultRunnerRuntimeMount(runtimeId: string): string { return `system.runtimes.${runtimeId}`; } export function defaultRunnerControlMount(runtimeId: string): string { // Slice 1b (control-actor-nav): the canonical runtime control mount is // `system.runtimes.`. The legacy `.control` sibling is gone. return defaultRunnerRuntimeMount(runtimeId); }