| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import { truncateUtf8 } from '@maka/core/diagnostic-log'; |
| import { release } from 'node:os'; |
| import { |
| assertRuntimeHostManagedDeploymentAuthorityDurablyAbsent, |
| decodeRuntimeHostManagedDeploymentConfig, |
| encodeRuntimeHostServiceManagementFrame, |
| RUNTIME_HOST_OPERATOR_ACCESS_MANAGEMENT_CAPABILITY, |
| RUNTIME_HOST_OPERATOR_CAPABILITY_REQUEST_ENV, |
| RUNTIME_HOST_OPERATOR_PEER_MANAGEMENT_CAPABILITY, |
| RUNTIME_HOST_OPERATOR_PEER_RELAY_DISCOVERY_CAPABILITY, |
| RUNTIME_HOST_OPERATOR_PEER_WEBRTC_STUN_CAPABILITY, |
| RUNTIME_HOST_OPERATOR_PROJECT_DIRECTORY_CONFIGURATION_REQUEST_ENV, |
| RUNTIME_HOST_OPERATOR_PROCESS_LIFETIME_LOCK_CAPABILITY, |
| RUNTIME_HOST_SERVICE_ERROR_CODE_MAX_BYTES, |
| RUNTIME_HOST_SERVICE_ERROR_MESSAGE_MAX_BYTES, |
| resolveRuntimeHostNpmDeploymentLayout, |
| type RuntimeHostManagedDeploymentConfig, |
| type RuntimeHostOperatorCapability, |
| type RuntimeHostServiceManagementFrame, |
| type RuntimeHostServiceSummary, |
| } from '@maka/runtime-host/operator'; |
| import { resolveExistingStorageRoot, tryAcquireStateRootOwner } from '@maka/storage/root-authority'; |
| import { |
| cleanupRuntimeHostManagedDeployment, |
| effectiveRuntimeHostProjectDirectoryRoots, |
| manageRuntimeHostService, |
| removeRuntimeHostServiceFile, |
| resolveRuntimeHostManagedServiceConfigPath, |
| resolveRuntimeHostManagedServiceId, |
| runtimeHostManagedServiceConfigFingerprint, |
| RuntimeHostServiceManagerError, |
| withRuntimeHostManagedServiceDeploymentLock, |
| withRuntimeHostManagedServiceLifecycleLock, |
| type RuntimeHostManagedServiceInput, |
| type RuntimeHostManagedServiceResult, |
| type RuntimeHostManagedServiceTarget, |
| type RuntimeHostServiceBackend, |
| } from './runtime-host-service-manager.js'; |
| import { createLaunchAgentRuntimeHostService } from './runtime-host-launch-agent-service.js'; |
| import { createLaunchAgentRuntimeHostLifecycleProvider } from './runtime-host-launch-agent-service.js'; |
| import { |
| createSystemdUserRuntimeHostLifecycleProvider, |
| createSystemdUserRuntimeHostService, |
| } from './runtime-host-systemd-service.js'; |
| import { createOpenRcRuntimeHostLifecycleProvider } from './runtime-host-openrc-service.js'; |
| import { createWindowsRuntimeHostLifecycleProvider } from './runtime-host-windows-service.js'; |
| import type { |
| RuntimeHostLifecycleProvider, |
| RuntimeHostLifecycleProviderOffer, |
| } from './runtime-host-lifecycle-provider.js'; |
| import { manageRuntimeHostManagedLifecycle } from './runtime-host-managed-lifecycle-manager.js'; |
| import { |
| acknowledgeRuntimeHostManagedDeploymentCleanup, |
| assertRuntimeHostManagedOperatorDeployment, |
| clearRuntimeHostManagedDeploymentCleanupReceipt, |
| readRuntimeHostManagedDeploymentCleanupReceipt, |
| removeRuntimeHostManagedDeployment, |
| resolveRuntimeHostManagedControlRoot, |
| resolveRuntimeHostManagedDeploymentForCli, |
| } from './runtime-host-managed-deployment.js'; |
| |
| export interface RuntimeHostServiceManagementCliOptions |
| extends Omit<RuntimeHostManagedServiceInput, 'action'> { |
| readonly action: RuntimeHostManagedServiceInput['action']; |
| readonly json: boolean; |
| readonly framed?: boolean; |
| readonly managedRootId?: string; |
| readonly operatorDeploymentId?: string; |
| } |
| |
| export interface RuntimeHostServiceManagementCliDeps { |
| readonly manage: typeof manageRuntimeHostService; |
| readonly manageLifecycle: typeof manageRuntimeHostManagedLifecycle; |
| readonly withDeploymentLock: typeof withRuntimeHostManagedServiceDeploymentLock; |
| readonly withLifecycleLock: typeof withRuntimeHostManagedServiceLifecycleLock; |
| readonly createBackend: (serviceId: string, clientDataRoot: string) => RuntimeHostServiceBackend; |
| readonly writeOutput: (value: string) => unknown; |
| readonly writeError: (value: string) => unknown; |
| } |
| |
| export async function runManagedRuntimeHostServiceCli( |
| options: RuntimeHostServiceManagementCliOptions, |
| overrides: Partial<RuntimeHostServiceManagementCliDeps> = {}, |
| ): Promise<number> { |
| const deps: RuntimeHostServiceManagementCliDeps = { |
| manage: manageRuntimeHostService, |
| manageLifecycle: manageRuntimeHostManagedLifecycle, |
| withDeploymentLock: withRuntimeHostManagedServiceDeploymentLock, |
| withLifecycleLock: withRuntimeHostManagedServiceLifecycleLock, |
| createBackend: createPlatformRuntimeHostServiceBackend, |
| writeOutput: (value) => process.stdout.write(value), |
| writeError: (value) => process.stderr.write(value), |
| ...overrides, |
| }; |
| try { |
| if ( |
| options.managedRootId && |
| options.operatorDeploymentId !== undefined && |
| options.expectedTarget?.deploymentId !== undefined && |
| options.expectedTarget.deploymentId !== options.operatorDeploymentId |
| ) { |
| throw new RuntimeHostServiceManagerError( |
| 'target_mismatch', |
| 'The managed Runtime Host deployment generation changed before the operation', |
| ); |
| } |
| if ( |
| options.managedRootId && |
| options.action === 'uninstall' && |
| !options.retainManagedDeployment && |
| !options.operatorDeploymentId |
| ) { |
| throw new RuntimeHostServiceManagerError( |
| 'target_mismatch', |
| 'Canonical deployment cleanup must run through its generation-bound operator', |
| ); |
| } |
| const { json: _json, framed: _framed, ...input } = options; |
| const serviceId = resolveRuntimeHostManagedServiceId(options.clientDataRoot); |
| const manage = () => |
| options.managedRootId |
| ? deps.manageLifecycle(options.managedRootId, input, { |
| resolveProvider: resolveRuntimeHostLifecycleProvider, |
| operatorClaim: { |
| deploymentId: options.operatorDeploymentId, |
| cliPath: options.cliPath, |
| }, |
| }) |
| : deps.manage(input, deps.createBackend(serviceId, options.clientDataRoot)); |
| const controlRoot = options.managedRootId |
| ? resolveRuntimeHostManagedControlRoot(options.managedRootId) |
| : options.clientDataRoot; |
| const mutate = () => |
| deps.withDeploymentLock(controlRoot, () => |
| deps.withLifecycleLock(controlRoot, async () => { |
| if (options.managedRootId) { |
| await assertRuntimeHostManagedOperatorDeployment( |
| options.managedRootId, |
| options.operatorDeploymentId, |
| options.cliPath, |
| { allowAbsent: options.action === 'uninstall' }, |
| ); |
| } |
| return manage(); |
| }), |
| ); |
| const result = |
| options.action === 'status' || options.action === 'logs' |
| ? options.managedRootId |
| ? await mutate() |
| : await manage() |
| : await mutate(); |
| const blocked = |
| (result.action === 'retire' && result.retirement.kind === 'active_tasks') || |
| (result.action === 'uninstall' && result.retirement.kind === 'active_tasks') || |
| (result.action === 'configure' && result.configuration.kind === 'active_tasks'); |
| if ( |
| options.managedRootId && |
| result.action === 'uninstall' && |
| result.retirement.kind === 'stopped' && |
| !options.retainManagedDeployment && |
| result.retainedStateRoot |
| ) { |
| await deps.withDeploymentLock(controlRoot, () => |
| deps.withLifecycleLock(controlRoot, () => |
| cleanupCanonicalRuntimeHostManagedDeployment({ |
| cliPath: options.cliPath, |
| clientDataRoot: options.clientDataRoot, |
| managedRootId: options.managedRootId!, |
| operatorDeploymentId: options.operatorDeploymentId, |
| expectedTarget: options.expectedTarget!, |
| finalize: false, |
| }), |
| ), |
| ); |
| await deps.withDeploymentLock(controlRoot, () => |
| deps.withLifecycleLock(controlRoot, () => |
| cleanupCanonicalRuntimeHostManagedDeployment({ |
| cliPath: options.cliPath, |
| clientDataRoot: options.clientDataRoot, |
| managedRootId: options.managedRootId!, |
| operatorDeploymentId: options.operatorDeploymentId, |
| expectedTarget: options.expectedTarget!, |
| finalize: true, |
| }), |
| ), |
| ); |
| } |
| if (options.framed) { |
| deps.writeOutput(encodeRuntimeHostServiceManagementFrame(successFrame(result))); |
| } else if (options.json) { |
| deps.writeOutput(`${JSON.stringify({ ...result, ok: !blocked })}\n`); |
| } else if (blocked) { |
| deps.writeError(formatHumanResult(result)); |
| } else { |
| deps.writeOutput(formatHumanResult(result)); |
| } |
| return blocked ? 1 : 0; |
| } catch (error) { |
| const code = |
| error instanceof RuntimeHostServiceManagerError ? error.code : 'internal_service_error'; |
| const message = error instanceof Error ? error.message : String(error); |
| if (options.framed) { |
| deps.writeOutput( |
| encodeRuntimeHostServiceManagementFrame({ |
| schemaVersion: 1, |
| kind: 'error', |
| action: options.action, |
| error: { |
| code: |
| truncateUtf8(code, RUNTIME_HOST_SERVICE_ERROR_CODE_MAX_BYTES) || |
| 'internal_service_error', |
| message: |
| truncateUtf8(message, RUNTIME_HOST_SERVICE_ERROR_MESSAGE_MAX_BYTES) || |
| 'Runtime Host service operation failed', |
| }, |
| }), |
| ); |
| } else if (options.json) { |
| deps.writeOutput( |
| `${JSON.stringify({ schemaVersion: 1, ok: false, action: options.action, error: { code, message } })}\n`, |
| ); |
| } else { |
| deps.writeError(`${message}\n`); |
| } |
| return 1; |
| } |
| } |
| |
| export async function runManagedRuntimeHostDeploymentCleanupCli(options: { |
| readonly clientDataRoot: string; |
| readonly cliPath: string; |
| readonly managedRootId?: string; |
| readonly operatorDeploymentId?: string; |
| readonly finalize?: boolean; |
| readonly expectedTarget: RuntimeHostManagedServiceTarget; |
| }): Promise<number> { |
| try { |
| if (options.managedRootId) { |
| const controlRoot = resolveRuntimeHostManagedControlRoot(options.managedRootId); |
| await withRuntimeHostManagedServiceDeploymentLock(controlRoot, () => |
| withRuntimeHostManagedServiceLifecycleLock(controlRoot, () => |
| cleanupCanonicalRuntimeHostManagedDeployment({ |
| ...options, |
| managedRootId: options.managedRootId!, |
| finalize: options.finalize ?? false, |
| }), |
| ), |
| ); |
| return 0; |
| } |
| const serviceId = resolveRuntimeHostManagedServiceId(options.clientDataRoot); |
| await cleanupRuntimeHostManagedDeployment( |
| options, |
| createPlatformRuntimeHostServiceBackend(serviceId, options.clientDataRoot), |
| ); |
| return 0; |
| } catch (error) { |
| process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); |
| return 1; |
| } |
| } |
| |
| async function cleanupCanonicalRuntimeHostManagedDeployment(options: { |
| readonly clientDataRoot: string; |
| readonly cliPath: string; |
| readonly managedRootId: string; |
| readonly operatorDeploymentId?: string; |
| readonly expectedTarget: RuntimeHostManagedServiceTarget; |
| readonly finalize: boolean; |
| }): Promise<void> { |
| const { managedRootId: rootId, expectedTarget } = options; |
| if (expectedTarget.serviceId !== rootId || expectedTarget.rootId !== rootId) { |
| throw new RuntimeHostServiceManagerError( |
| 'target_mismatch', |
| 'The managed Runtime Host does not match the expected deployment identity', |
| ); |
| } |
| if ( |
| expectedTarget.deploymentId !== undefined && |
| expectedTarget.deploymentId !== options.operatorDeploymentId |
| ) { |
| throw new RuntimeHostServiceManagerError( |
| 'target_mismatch', |
| 'The managed Runtime Host deployment generation changed before cleanup', |
| ); |
| } |
| const capability = await resolveExistingStorageRoot({ |
| path: expectedTarget.rootPath, |
| kind: 'interactive', |
| expectedRootId: rootId, |
| }); |
| const owner = await tryAcquireStateRootOwner(capability); |
| if (!owner) { |
| throw new RuntimeHostServiceManagerError( |
| 'uninstall_incomplete', |
| 'Runtime Host still owns the State Root; refusing to remove its package', |
| ); |
| } |
| try { |
| await assertRuntimeHostManagedDeploymentAuthorityDurablyAbsent(owner); |
| const deploymentRoot = resolveRuntimeHostManagedDeploymentForCli(rootId, options.cliPath); |
| if (!deploymentRoot) { |
| throw new RuntimeHostServiceManagerError( |
| 'invalid_launch', |
| 'The Runtime Host operator does not belong to the expected managed deployment', |
| ); |
| } |
| if (!options.operatorDeploymentId) { |
| throw new RuntimeHostServiceManagerError( |
| 'target_mismatch', |
| 'Canonical deployment cleanup requires a generation-bound operator', |
| ); |
| } |
| if (!options.finalize) { |
| await acknowledgeRuntimeHostManagedDeploymentCleanup({ |
| serviceId: rootId, |
| deploymentId: options.operatorDeploymentId, |
| deploymentRoot, |
| stateRootPath: expectedTarget.rootPath, |
| }); |
| return; |
| } |
| const receipt = await readRuntimeHostManagedDeploymentCleanupReceipt(rootId); |
| if ( |
| !receipt || |
| receipt.deploymentId !== options.operatorDeploymentId || |
| receipt.deploymentRoot !== deploymentRoot |
| ) { |
| throw new RuntimeHostServiceManagerError( |
| 'uninstall_incomplete', |
| 'The managed Runtime Host deployment has not acknowledged cleanup', |
| ); |
| } |
| // Canonical peer keys are deployment-scoped, including rotated UUID paths. |
| await removeRuntimeHostManagedDeployment(deploymentRoot, rootId); |
| await clearRuntimeHostManagedDeploymentCleanupReceipt(rootId); |
| } finally { |
| await owner.close(); |
| } |
| } |
| |
| function formatHumanResult(result: RuntimeHostManagedServiceResult): string { |
| const service = result.service; |
| if (result.action === 'uninstall') { |
| if (result.retirement.kind === 'active_tasks') { |
| return 'Runtime Host service still owns active work. Retry with explicit interruption authority.\n'; |
| } |
| return result.retainedStateRoot |
| ? `Runtime Host service is uninstalled. Data was retained at ${result.retainedStateRoot}\n` |
| : 'Runtime Host service is uninstalled.\n'; |
| } |
| if (result.action === 'install') { |
| return service.active |
| ? `Runtime Host service is installed and running at ${websocketUrl(service)}\n` |
| : 'Runtime Host service is installed but is not running. Check its status and journal.\n'; |
| } |
| if (result.action === 'configure') { |
| return result.configuration.kind === 'active_tasks' |
| ? 'Runtime Host service still owns active work. Retry with explicit interruption authority.\n' |
| : result.configuration.kind === 'unchanged' |
| ? 'Runtime Host Project roots are already configured.\n' |
| : 'Runtime Host Project roots were configured.\n'; |
| } |
| if (result.action === 'status') { |
| if (!service.installed) return 'Runtime Host service is not installed.\n'; |
| return `Runtime Host service is ${service.state} at ${websocketUrl(service)}\n`; |
| } |
| if (result.action === 'retire') { |
| return result.retirement.kind === 'active_tasks' |
| ? 'Runtime Host service still owns active work. Retry with explicit interruption authority.\n' |
| : 'Runtime Host service is retired and its State Root writer is released.\n'; |
| } |
| if (result.action === 'logs') return result.logs || 'No Runtime Host service logs were found.\n'; |
| return `Runtime Host service is ${service.state}.\n`; |
| } |
| |
| function successFrame(result: RuntimeHostManagedServiceResult): RuntimeHostServiceManagementFrame { |
| const service = runtimeHostServiceSummary(result); |
| const common = { |
| schemaVersion: 1, |
| kind: 'result', |
| service, |
| ...requestedOperatorCapabilities(), |
| ...(result.retainedStateRoot ? { retainedStateRoot: result.retainedStateRoot } : {}), |
| ...(result.logs !== undefined ? { logs: result.logs } : {}), |
| } as const; |
| if (result.action === 'retire' || result.action === 'uninstall') { |
| return { |
| ...common, |
| action: result.action, |
| retirement: { ...result.retirement }, |
| }; |
| } |
| if (result.action === 'configure') { |
| return { |
| ...common, |
| action: result.action, |
| configuration: { ...result.configuration }, |
| }; |
| } |
| return { ...common, action: result.action }; |
| } |
| |
| function requestedOperatorCapabilities(): { |
| readonly operatorCapabilities?: RuntimeHostOperatorCapability[]; |
| } { |
| const capabilities: RuntimeHostOperatorCapability[] = []; |
| const requested = process.env[RUNTIME_HOST_OPERATOR_CAPABILITY_REQUEST_ENV]; |
| if ( |
| requested === RUNTIME_HOST_OPERATOR_ACCESS_MANAGEMENT_CAPABILITY || |
| requested === RUNTIME_HOST_OPERATOR_PEER_MANAGEMENT_CAPABILITY || |
| requested === RUNTIME_HOST_OPERATOR_PEER_RELAY_DISCOVERY_CAPABILITY || |
| requested === RUNTIME_HOST_OPERATOR_PEER_WEBRTC_STUN_CAPABILITY || |
| requested === RUNTIME_HOST_OPERATOR_PROCESS_LIFETIME_LOCK_CAPABILITY |
| ) { |
| capabilities.push(requested); |
| } |
| return capabilities.length > 0 ? { operatorCapabilities: capabilities } : {}; |
| } |
| |
| export function runtimeHostServiceSummary( |
| result: RuntimeHostManagedServiceResult, |
| ): RuntimeHostServiceSummary { |
| const config = result.service.config; |
| return { |
| platform: process.platform, |
| arch: process.arch, |
| osRelease: release(), |
| state: result.service.state, |
| pid: result.service.pid, |
| lastExitCode: result.service.lastExitCode, |
| installedVersion: result.service.installedVersion, |
| ...(result.service.lifecycle ? { lifecycle: { ...result.service.lifecycle } } : {}), |
| ...(result.service.reconciliation |
| ? { reconciliation: { ...result.service.reconciliation } } |
| : {}), |
| ...(config ? { stateRoot: config.rootPath } : {}), |
| ...(config && |
| process.env[RUNTIME_HOST_OPERATOR_PROJECT_DIRECTORY_CONFIGURATION_REQUEST_ENV] === '1' |
| ? { |
| configurationFingerprint: runtimeHostManagedServiceConfigFingerprint(config), |
| } |
| : {}), |
| projectDirectoryRoots: config ? [...effectiveRuntimeHostProjectDirectoryRoots(config)] : [], |
| }; |
| } |
| |
| export function createPlatformRuntimeHostServiceBackend( |
| serviceId: string, |
| clientDataRoot: string, |
| platform: NodeJS.Platform = process.platform, |
| ): RuntimeHostServiceBackend { |
| const serviceConfigPath = resolveRuntimeHostManagedServiceConfigPath(clientDataRoot); |
| if (platform === 'linux') { |
| return createSystemdUserRuntimeHostService(serviceId, { |
| serviceConfigPath, |
| }); |
| } |
| if (platform === 'darwin') { |
| return createLaunchAgentRuntimeHostService(serviceId, { |
| serviceConfigPath, |
| }); |
| } |
| throw new RuntimeHostServiceManagerError( |
| 'unsupported_platform', |
| 'Managed Runtime Host services currently require Linux or macOS', |
| ); |
| } |
| |
| export async function discoverRuntimeHostLifecycleProvider( |
| rootId: string, |
| options: { |
| readonly platform?: NodeJS.Platform; |
| readonly environment?: NodeJS.ProcessEnv; |
| } = {}, |
| ): Promise<RuntimeHostLifecycleProviderOffer> { |
| const platform = options.platform ?? process.platform; |
| const environment = options.environment ?? process.env; |
| if (platform === 'darwin') { |
| const provider = createLaunchAgentRuntimeHostLifecycleProvider(rootId); |
| await provider.supervisor.preflight(); |
| return { provider, availability: 'session' }; |
| } |
| if (platform === 'win32') { |
| const cliPath = process.argv[1]; |
| if (!cliPath) { |
| throw new RuntimeHostServiceManagerError( |
| 'invalid_launch', |
| 'The Windows lifecycle probe requires the current CLI path', |
| ); |
| } |
| const provider = createWindowsRuntimeHostLifecycleProvider(rootId, { cliPath }); |
| await provider.supervisor.preflight(); |
| return { provider, availability: 'session' }; |
| } |
| if (platform !== 'linux') { |
| throw new RuntimeHostServiceManagerError( |
| 'unsupported_platform', |
| 'Supervised Runtime Host deployments currently require Linux, macOS, or Windows', |
| ); |
| } |
| |
| const systemd = createSystemdUserRuntimeHostLifecycleProvider(rootId, { |
| env: environment, |
| }); |
| let systemdError: unknown; |
| try { |
| await systemd.supervisor.preflight(); |
| return { |
| provider: systemd, |
| availability: isWslEnvironment(environment) ? 'environment' : 'machine', |
| }; |
| } catch (error) { |
| systemdError = error; |
| } |
| |
| const openRcProvider = process.getuid?.() === 0 ? 'openrc_system' : 'openrc_user'; |
| const openRc = createOpenRcRuntimeHostLifecycleProvider(rootId, openRcProvider, { |
| env: environment, |
| }); |
| try { |
| await openRc.supervisor.preflight(); |
| return { |
| provider: openRc, |
| availability: isWslEnvironment(environment) |
| ? 'environment' |
| : openRcProvider === 'openrc_system' |
| ? 'machine' |
| : 'session', |
| }; |
| } catch (openRcError) { |
| if ( |
| systemdError instanceof RuntimeHostServiceManagerError && |
| systemdError.code === 'linger_disabled' |
| ) { |
| throw systemdError; |
| } |
| throw new RuntimeHostServiceManagerError( |
| 'service_manager_unavailable', |
| 'No supervised Linux lifecycle provider is available; configure systemd user lingering or OpenRC activation, or use an on-demand Runtime Host', |
| { cause: new AggregateError([systemdError, openRcError]) }, |
| ); |
| } |
| } |
| |
| /** Resolves only the provider identity already persisted by the deployment authority. */ |
| export function resolveRuntimeHostLifecycleProvider( |
| config: RuntimeHostManagedDeploymentConfig, |
| ): RuntimeHostLifecycleProvider { |
| const canonical = decodeRuntimeHostManagedDeploymentConfig(config); |
| if (canonical.lifecycle.mode !== 'supervised') { |
| throw new RuntimeHostServiceManagerError( |
| 'invalid_config', |
| 'An on-demand Runtime Host has no lifecycle provider', |
| ); |
| } |
| const rootId = canonical.root.id; |
| const provider = canonical.lifecycle.provider; |
| if (provider === 'systemd_user') return createSystemdUserRuntimeHostLifecycleProvider(rootId, {}); |
| if (provider === 'launch_agent') return createLaunchAgentRuntimeHostLifecycleProvider(rootId); |
| if (provider === 'windows_task') { |
| return createWindowsRuntimeHostLifecycleProvider(rootId, { |
| cliPath: resolveRuntimeHostNpmDeploymentLayout( |
| canonical.deploymentRoot, |
| canonical.launch.package.integrity, |
| ).cliPath, |
| }); |
| } |
| return createOpenRcRuntimeHostLifecycleProvider(rootId, provider); |
| } |
| |
| function isWslEnvironment(environment: NodeJS.ProcessEnv): boolean { |
| return Boolean(environment.WSL_DISTRO_NAME?.trim() || environment.WSL_INTEROP?.trim()); |
| } |
| |
| function websocketUrl(service: RuntimeHostManagedServiceResult['service']): string { |
| const websocket = service.config?.websocket; |
| return websocket |
| ? `ws://${websocket.host}:${websocket.port}${websocket.path}` |
| : 'an unknown endpoint'; |
| } |