@camstack/kernel 0.1.7 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addon-installer-2BW2KLQD.mjs +7 -0
- package/dist/chunk-67QMERMP.mjs +411 -0
- package/dist/chunk-67QMERMP.mjs.map +1 -0
- package/dist/chunk-S5YWNNPK.mjs +135 -0
- package/dist/{chunk-RHK5CCAL.mjs.map → chunk-S5YWNNPK.mjs.map} +1 -1
- package/dist/index.d.mts +645 -0
- package/dist/index.d.ts +645 -0
- package/dist/index.js +1754 -2037
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1261 -1462
- package/dist/index.mjs.map +1 -1
- package/dist/worker/addon-worker-entry.d.mts +2 -0
- package/dist/worker/addon-worker-entry.d.ts +2 -0
- package/dist/worker/addon-worker-entry.js +300 -430
- package/dist/worker/addon-worker-entry.js.map +1 -1
- package/dist/worker/addon-worker-entry.mjs +4 -8
- package/dist/worker/addon-worker-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/addon-installer-WQBOEZQT.mjs +0 -6
- package/dist/chunk-GJ3DKNOD.mjs +0 -494
- package/dist/chunk-GJ3DKNOD.mjs.map +0 -1
- package/dist/chunk-LZOMFHX3.mjs +0 -38
- package/dist/chunk-LZOMFHX3.mjs.map +0 -1
- package/dist/chunk-RHK5CCAL.mjs +0 -154
- /package/dist/{addon-installer-WQBOEZQT.mjs.map → addon-installer-2BW2KLQD.mjs.map} +0 -0
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/addon-loader.ts","../src/addon-engine-manager.ts","../src/workspace-detect.ts","../src/capability-registry.ts","../src/infra-capabilities.ts","../src/config-schema.ts","../src/config-manager.ts","../src/worker/addon-worker-host.ts","../src/index.ts"],"sourcesContent":["import type { AddonDeclaration, AddonPackageManifest, ICamstackAddon } from '@camstack/types'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\n\nexport interface RegisteredAddon {\n readonly declaration: AddonDeclaration\n readonly packageName: string\n readonly packageVersion: string\n /** Human-readable package name from camstack.displayName in package.json */\n readonly packageDisplayName?: string\n readonly addonClass: new () => ICamstackAddon\n}\n\n/**\n * Resolve the addon class from a dynamically imported module.\n *\n * CJS modules built with tsup can produce double-wrapped default exports:\n * mod.default = { default: ActualClass }\n * This helper unwraps that pattern so we always get the constructor.\n */\nfunction resolveAddonClass(mod: Record<string, unknown>): (new () => ICamstackAddon) | undefined {\n let candidate = mod['default'] ?? mod[Object.keys(mod)[0]!]\n\n // Unwrap double-nested default (CJS interop with tsup)\n if (candidate && typeof candidate === 'object' && 'default' in (candidate as Record<string, unknown>)) {\n candidate = (candidate as Record<string, unknown>)['default']\n }\n\n return typeof candidate === 'function'\n ? (candidate as new () => ICamstackAddon)\n : undefined\n}\n\nexport class AddonLoader {\n private addons = new Map<string, RegisteredAddon>()\n\n /** Scan addons directory and load all addon packages.\n * Supports scoped layout: addons/@scope/package-name/ */\n async loadFromDirectory(addonsDir: string): Promise<void> {\n if (!fs.existsSync(addonsDir)) return\n\n const entries = fs.readdirSync(addonsDir, { withFileTypes: true })\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n\n // Scoped directory (e.g., @camstack/) — scan one level deeper\n if (entry.name.startsWith('@')) {\n const scopeDir = path.join(addonsDir, entry.name)\n const scopeEntries = fs.readdirSync(scopeDir, { withFileTypes: true })\n for (const scopeEntry of scopeEntries) {\n if (!scopeEntry.isDirectory()) continue\n await this.tryLoadAddon(path.join(scopeDir, scopeEntry.name))\n }\n continue\n }\n\n // Non-scoped (legacy or flat layout)\n await this.tryLoadAddon(path.join(addonsDir, entry.name))\n }\n }\n\n private async tryLoadAddon(addonDir: string): Promise<void> {\n const pkgJsonPath = path.join(addonDir, 'package.json')\n if (!fs.existsSync(pkgJsonPath)) return\n try {\n await this.loadFromAddonDir(addonDir)\n } catch (err) {\n console.warn(`Failed to load addon from ${addonDir}: ${err}`)\n }\n }\n\n /** Load addon from a specific directory (package.json + dist/) */\n async loadFromAddonDir(addonDir: string): Promise<void> {\n const pkgJsonPath = path.join(addonDir, 'package.json')\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8')) as Record<string, unknown>\n const packageName = pkgJson['name'] as string\n const packageVersion = (pkgJson['version'] as string) ?? '0.0.0'\n const manifest = pkgJson['camstack'] as AddonPackageManifest | undefined\n\n if (!manifest?.addons?.length) return\n\n for (const declaration of manifest.addons) {\n // Resolve entry: try dist/ first\n const entryFile = declaration.entry\n .replace(/^\\.\\//, '')\n .replace(/^src\\//, 'dist/')\n .replace(/\\.ts$/, '.js')\n\n let entryPath = path.resolve(addonDir, entryFile)\n\n // Fallback: try other locations and extensions\n if (!fs.existsSync(entryPath)) {\n const base = entryPath.replace(/\\.(js|cjs|mjs)$/, '')\n const alternatives = [\n `${base}.cjs`,\n `${base}.mjs`,\n path.resolve(addonDir, 'dist', 'index.js'),\n path.resolve(addonDir, 'dist', 'index.cjs'),\n path.resolve(addonDir, 'dist', 'index.mjs'),\n path.resolve(addonDir, declaration.entry),\n ]\n entryPath = alternatives.find(p => fs.existsSync(p)) ?? entryPath\n }\n\n if (!fs.existsSync(entryPath)) {\n throw new Error(`Entry not found: ${entryPath}`)\n }\n\n const mod = (await import(entryPath)) as Record<string, unknown>\n const AddonClass = resolveAddonClass(mod)\n\n if (!AddonClass) {\n throw new Error(`No addon class in ${entryPath}`)\n }\n\n this.addons.set(declaration.id, {\n declaration,\n packageName,\n packageVersion,\n packageDisplayName: manifest.displayName,\n addonClass: AddonClass,\n })\n }\n }\n\n /** Load addon from a direct path (for development/testing) */\n async loadFromPath(\n addonId: string,\n modulePath: string,\n packageName: string,\n declaration?: Partial<AddonDeclaration>,\n packageVersion = '0.0.0',\n ): Promise<void> {\n const mod = (await import(modulePath)) as Record<string, unknown>\n const AddonClass = resolveAddonClass(mod)\n\n if (!AddonClass) {\n throw new Error(`Module ${modulePath} has no default export`)\n }\n\n this.addons.set(addonId, {\n declaration: {\n id: addonId,\n entry: modulePath,\n slot: 'detector' as AddonDeclaration['slot'],\n ...declaration,\n },\n packageName,\n packageVersion,\n addonClass: AddonClass,\n })\n }\n\n /** Get a registered addon by ID */\n getAddon(addonId: string): RegisteredAddon | undefined {\n return this.addons.get(addonId)\n }\n\n /** List all registered addons */\n listAddons(): RegisteredAddon[] {\n return [...this.addons.values()]\n }\n\n /** Check if an addon is registered */\n hasAddon(addonId: string): boolean {\n return this.addons.has(addonId)\n }\n\n /** Create a new instance of an addon (not yet initialized) */\n createInstance(addonId: string): ICamstackAddon {\n const registered = this.addons.get(addonId)\n if (!registered) {\n throw new Error(`Addon \"${addonId}\" is not registered`)\n }\n return new registered.addonClass()\n }\n}\n","import type { ICamstackAddon, AddonContext } from '@camstack/types'\nimport { AddonLoader } from './addon-loader.js'\nimport { createHash } from 'node:crypto'\n\nexport class AddonEngineManager {\n private engines = new Map<string, ICamstackAddon>()\n\n constructor(\n private readonly loader: AddonLoader,\n private readonly baseContext: Partial<Omit<AddonContext, 'addonConfig'>>,\n ) {}\n\n /**\n * Get or create an addon engine for the given effective config.\n * Cameras with the same addonId + effective config share the same engine.\n */\n async getOrCreateEngine(\n addonId: string,\n globalConfig: Record<string, unknown>,\n cameraOverride?: Record<string, unknown>,\n ): Promise<ICamstackAddon> {\n // Merge global + override\n const effectiveConfig = { ...globalConfig, ...cameraOverride }\n const configKey = `${addonId}:${this.hashConfig(effectiveConfig)}`\n\n const existing = this.engines.get(configKey)\n if (existing) return existing\n\n // Create new instance\n const addon = this.loader.createInstance(addonId)\n await addon.initialize({ ...this.baseContext, addonConfig: effectiveConfig } as AddonContext)\n this.engines.set(configKey, addon)\n return addon\n }\n\n /** Get all active engines */\n getActiveEngines(): Map<string, ICamstackAddon> {\n return new Map(this.engines)\n }\n\n /** Shutdown a specific engine by its config key */\n async shutdownEngine(configKey: string): Promise<void> {\n const engine = this.engines.get(configKey)\n if (engine) {\n await engine.shutdown()\n this.engines.delete(configKey)\n }\n }\n\n /** Shutdown all engines */\n async shutdownAll(): Promise<void> {\n for (const [, engine] of this.engines) {\n await engine.shutdown()\n }\n this.engines.clear()\n }\n\n /** Compute a deterministic config key (visible for tests) */\n computeConfigKey(addonId: string, effectiveConfig: Record<string, unknown>): string {\n return `${addonId}:${this.hashConfig(effectiveConfig)}`\n }\n\n private hashConfig(config: Record<string, unknown>): string {\n const sorted = JSON.stringify(config, Object.keys(config).sort())\n return createHash('md5').update(sorted).digest('hex').slice(0, 12)\n }\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\n\n/**\n * Detect whether we're running inside the monorepo workspace.\n * Returns the absolute path to the packages/ directory if found, otherwise null.\n *\n * Single source of truth — replaces identical implementations in:\n * - first-boot-installer.ts\n * - addon-bridge.service.ts\n */\nexport function detectWorkspacePackagesDir(startDir: string): string | null {\n let current = path.resolve(startDir)\n for (let i = 0; i < 6; i++) {\n current = path.dirname(current)\n const packagesDir = path.join(current, 'packages')\n const rootPkgJson = path.join(current, 'package.json')\n if (fs.existsSync(packagesDir) && fs.existsSync(rootPkgJson)) {\n try {\n const rootPkg = JSON.parse(fs.readFileSync(rootPkgJson, 'utf-8')) as Record<string, unknown>\n if (rootPkg.workspaces || rootPkg.name === 'camstack-server' || rootPkg.name === 'camstack') {\n return packagesDir\n }\n } catch {\n // Ignore parse errors\n }\n }\n }\n return null\n}\n","// packages/kernel/src/capability-registry.ts\nimport type {\n CapabilityMode,\n CapabilityDeclaration,\n CapabilityConsumerRegistration,\n CapabilityInfo,\n IScopedLogger,\n} from '@camstack/types'\n\ninterface ProviderEntry {\n readonly addonId: string\n readonly provider: unknown\n}\n\ninterface CapabilityState {\n readonly declaration: CapabilityDeclaration\n /** All registered providers (addon ID → provider) */\n readonly available: Map<string, unknown>\n /** For singleton: the currently active addon ID */\n activeAddonId: string | null\n /** For singleton: the currently active provider */\n activeProvider: unknown | null\n /** For collection: all active providers in registration order */\n readonly activeCollection: ProviderEntry[]\n /** Registered consumers */\n readonly consumers: Set<CapabilityConsumerRegistration>\n}\n\n/**\n * Central registry for all capability providers and consumers.\n * Lives in @camstack/kernel. Mode-aware: singleton (one active) or collection (all active).\n *\n * Uses injected LoggingService, NEVER NestJS static Logger.\n */\nexport class CapabilityRegistry {\n private readonly capabilities = new Map<string, CapabilityState>()\n\n /** Per-device singleton overrides: deviceId → (capability → addonId) */\n private readonly deviceOverrides = new Map<string, Map<string, string>>()\n\n /** Per-device collection filters: deviceId → (capability → addonIds[]) */\n private readonly deviceCollectionFilters = new Map<string, Map<string, string[]>>()\n\n constructor(\n private readonly logger: IScopedLogger,\n private readonly configReader: (capability: string) => string | undefined,\n ) {}\n\n /**\n * Declare a capability (typically called when addon manifests are loaded).\n * Must be called before registerProvider/registerConsumer for that capability.\n */\n declareCapability(declaration: CapabilityDeclaration): void {\n if (this.capabilities.has(declaration.name)) {\n this.logger.debug(`Capability \"${declaration.name}\" already declared, skipping`)\n return\n }\n\n this.capabilities.set(declaration.name, {\n declaration,\n available: new Map(),\n activeAddonId: null,\n activeProvider: null,\n activeCollection: [],\n consumers: new Set(),\n })\n\n this.logger.debug(`Capability declared: ${declaration.name} (mode=${declaration.mode})`)\n }\n\n /**\n * Register a capability provider (called by addon loader when addon is enabled).\n * For singleton: auto-activates if user-preferred or first registered.\n * For collection: adds to active set and notifies consumers.\n */\n registerProvider(capability: string, addonId: string, provider: unknown): void {\n const state = this.capabilities.get(capability)\n if (!state) {\n this.logger.warn(`Cannot register provider for undeclared capability \"${capability}\"`)\n return\n }\n\n state.available.set(addonId, provider)\n this.logger.info(`Provider registered: ${addonId} → ${capability}`)\n\n if (state.declaration.mode === 'singleton') {\n const userChoice = this.configReader(capability)\n\n if (userChoice === addonId) {\n // User explicitly chose this addon\n this.activateSingleton(state, addonId, provider)\n } else if (userChoice === undefined && state.activeAddonId === null) {\n // No user preference and no active provider — first registered = default\n this.activateSingleton(state, addonId, provider)\n }\n } else {\n // collection mode: add to active set and notify\n state.activeCollection.push({ addonId, provider })\n for (const consumer of state.consumers) {\n if (consumer.onAdded) {\n try {\n consumer.onAdded(provider)\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n this.logger.error(`Consumer onAdded failed for ${capability}: ${msg}`)\n }\n }\n }\n }\n }\n\n /**\n * Unregister a provider (called when addon is disabled/uninstalled).\n */\n unregisterProvider(capability: string, addonId: string): void {\n const state = this.capabilities.get(capability)\n if (!state) return\n\n const provider = state.available.get(addonId)\n state.available.delete(addonId)\n\n if (state.declaration.mode === 'singleton') {\n if (state.activeAddonId === addonId) {\n state.activeAddonId = null\n state.activeProvider = null\n this.logger.info(`Singleton deactivated: ${capability} (was ${addonId})`)\n }\n } else {\n // collection: remove from active\n const idx = state.activeCollection.findIndex((e) => e.addonId === addonId)\n if (idx !== -1) {\n state.activeCollection.splice(idx, 1)\n for (const consumer of state.consumers) {\n if (consumer.onRemoved && provider !== undefined) {\n try {\n consumer.onRemoved(provider)\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n this.logger.error(`Consumer onRemoved failed for ${capability}: ${msg}`)\n }\n }\n }\n }\n }\n }\n\n /**\n * Register a consumer that wants to be notified when providers change.\n * If a provider is already active, the consumer is immediately notified.\n * Returns a disposer function for cleanup.\n */\n registerConsumer<T = unknown>(registration: CapabilityConsumerRegistration<T>): () => void {\n const state = this.capabilities.get(registration.capability)\n if (!state) {\n // Auto-declare as singleton if not yet declared (graceful handling)\n this.logger.debug(`Consumer registered for undeclared capability \"${registration.capability}\" — auto-declaring`)\n this.declareCapability({ name: registration.capability, mode: 'singleton' })\n return this.registerConsumer(registration)\n }\n\n const untypedReg = registration as CapabilityConsumerRegistration\n state.consumers.add(untypedReg)\n\n // Immediately notify with current state\n if (state.declaration.mode === 'singleton') {\n if (state.activeProvider !== null && registration.onSet) {\n try {\n registration.onSet(state.activeProvider as T)\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n this.logger.error(`Consumer onSet (immediate) failed for ${registration.capability}: ${msg}`)\n }\n }\n } else {\n // collection: notify with all active\n if (registration.onAdded) {\n for (const entry of state.activeCollection) {\n try {\n registration.onAdded(entry.provider as T)\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n this.logger.error(`Consumer onAdded (immediate) failed for ${registration.capability}: ${msg}`)\n }\n }\n }\n }\n\n return () => {\n state.consumers.delete(untypedReg)\n }\n }\n\n /**\n * Get the active singleton provider for a capability.\n * Returns null if none set.\n */\n getSingleton<T = unknown>(capability: string): T | null {\n const state = this.capabilities.get(capability)\n if (!state || state.declaration.mode !== 'singleton') return null\n return (state.activeProvider as T) ?? null\n }\n\n /**\n * Get all active collection providers for a capability.\n */\n getCollection<T = unknown>(capability: string): readonly T[] {\n const state = this.capabilities.get(capability)\n if (!state || state.declaration.mode !== 'collection') return []\n return state.activeCollection.map((e) => e.provider as T)\n }\n\n /**\n * Set which addon should be the active singleton for a capability.\n * Call with `immediate: true` to also swap the runtime provider now\n * (consumers' onSet will be awaited).\n */\n async setActiveSingleton(capability: string, addonId: string, immediate = false): Promise<void> {\n const state = this.capabilities.get(capability)\n if (!state) {\n throw new Error(`Unknown capability: ${capability}`)\n }\n if (state.declaration.mode !== 'singleton') {\n throw new Error(`Capability \"${capability}\" is not a singleton`)\n }\n\n const provider = state.available.get(addonId)\n if (!provider) {\n throw new Error(`No provider \"${addonId}\" registered for capability \"${capability}\"`)\n }\n\n if (immediate) {\n await this.activateSingletonAsync(state, addonId, provider)\n }\n\n this.logger.info(`Singleton preference set: ${capability} → ${addonId}`)\n }\n\n /**\n * Get the mode declared for a capability.\n */\n getMode(capability: string): CapabilityMode | undefined {\n return this.capabilities.get(capability)?.declaration.mode\n }\n\n /**\n * List all registered capabilities with their providers.\n */\n listCapabilities(): CapabilityInfo[] {\n const result: CapabilityInfo[] = []\n for (const [name, state] of this.capabilities) {\n result.push({\n name,\n mode: state.declaration.mode,\n providers: [...state.available.keys()],\n activeProvider: state.activeAddonId,\n })\n }\n return result\n }\n\n /**\n * Check if all dependencies for a capability are satisfied (have active providers).\n */\n areDependenciesMet(declaration: CapabilityDeclaration): boolean {\n if (!declaration.dependsOn?.length) return true\n return declaration.dependsOn.every((dep) => {\n const state = this.capabilities.get(dep)\n if (!state) return false\n if (state.declaration.mode === 'singleton') {\n return state.activeProvider !== null\n }\n return state.activeCollection.length > 0\n })\n }\n\n /**\n * Get the dependency-ordered list of capability names for boot sequencing.\n * Returns capabilities sorted topologically by dependsOn.\n * Throws if a cycle is detected.\n */\n getBootOrder(): string[] {\n const visited = new Set<string>()\n const visiting = new Set<string>()\n const order: string[] = []\n\n const visit = (name: string): void => {\n if (visited.has(name)) return\n if (visiting.has(name)) {\n throw new Error(`Circular dependency detected involving capability \"${name}\"`)\n }\n\n visiting.add(name)\n const state = this.capabilities.get(name)\n if (state?.declaration.dependsOn) {\n for (const dep of state.declaration.dependsOn) {\n visit(dep)\n }\n }\n visiting.delete(name)\n visited.add(name)\n order.push(name)\n }\n\n for (const name of this.capabilities.keys()) {\n visit(name)\n }\n\n return order\n }\n\n // ---- Per-device overrides ----\n\n /**\n * Set a per-device singleton override. When resolveForDevice is called for\n * this device + capability, the specified addon's provider is returned\n * instead of the global singleton.\n */\n setDeviceOverride(deviceId: string, capability: string, addonId: string): void {\n const state = this.capabilities.get(capability)\n if (!state) {\n this.logger.warn(`Cannot set device override for undeclared capability \"${capability}\"`)\n return\n }\n if (!state.available.has(addonId)) {\n this.logger.warn(`Cannot set device override: addon \"${addonId}\" not registered for \"${capability}\"`)\n return\n }\n\n let deviceMap = this.deviceOverrides.get(deviceId)\n if (!deviceMap) {\n deviceMap = new Map()\n this.deviceOverrides.set(deviceId, deviceMap)\n }\n deviceMap.set(capability, addonId)\n this.logger.info(`Device override set: ${deviceId} → ${capability} = ${addonId}`)\n }\n\n /**\n * Clear a per-device singleton override, reverting to the global singleton.\n */\n clearDeviceOverride(deviceId: string, capability: string): void {\n const deviceMap = this.deviceOverrides.get(deviceId)\n if (!deviceMap) return\n deviceMap.delete(capability)\n if (deviceMap.size === 0) {\n this.deviceOverrides.delete(deviceId)\n }\n this.logger.info(`Device override cleared: ${deviceId} → ${capability}`)\n }\n\n /**\n * Get all per-device singleton overrides for a device.\n * Returns a Map of capability name to addon ID.\n */\n getDeviceOverrides(deviceId: string): Map<string, string> {\n return new Map(this.deviceOverrides.get(deviceId) ?? [])\n }\n\n /**\n * Resolve a singleton provider for a specific device.\n * 1. Check device override — return that addon's provider\n * 2. Fallback to global singleton\n */\n resolveForDevice<T = unknown>(capability: string, deviceId: string): T | null {\n const state = this.capabilities.get(capability)\n if (!state || state.declaration.mode !== 'singleton') return null\n\n // Check device-level override\n const deviceMap = this.deviceOverrides.get(deviceId)\n if (deviceMap) {\n const overrideAddonId = deviceMap.get(capability)\n if (overrideAddonId) {\n const provider = state.available.get(overrideAddonId)\n if (provider) return provider as T\n // Override points to an addon that is no longer registered — fall through\n this.logger.warn(\n `Device override for ${deviceId}/${capability} references unregistered addon \"${overrideAddonId}\" — falling back to global`,\n )\n }\n }\n\n // Fallback to global singleton\n return (state.activeProvider as T) ?? null\n }\n\n /**\n * Set a per-device collection filter. When resolveCollectionForDevice is called\n * for this device + capability, only providers from the specified addon IDs\n * are returned instead of the full collection.\n */\n setDeviceCollectionFilter(deviceId: string, capability: string, addonIds: string[]): void {\n const state = this.capabilities.get(capability)\n if (!state) {\n this.logger.warn(`Cannot set device collection filter for undeclared capability \"${capability}\"`)\n return\n }\n\n let deviceMap = this.deviceCollectionFilters.get(deviceId)\n if (!deviceMap) {\n deviceMap = new Map()\n this.deviceCollectionFilters.set(deviceId, deviceMap)\n }\n deviceMap.set(capability, [...addonIds])\n this.logger.info(`Device collection filter set: ${deviceId} → ${capability} = [${addonIds.join(', ')}]`)\n }\n\n /**\n * Clear a per-device collection filter, reverting to the full collection.\n */\n clearDeviceCollectionFilter(deviceId: string, capability: string): void {\n const deviceMap = this.deviceCollectionFilters.get(deviceId)\n if (!deviceMap) return\n deviceMap.delete(capability)\n if (deviceMap.size === 0) {\n this.deviceCollectionFilters.delete(deviceId)\n }\n this.logger.info(`Device collection filter cleared: ${deviceId} → ${capability}`)\n }\n\n /**\n * Resolve collection providers for a specific device.\n * If a filter exists for the device + capability, only those addon's providers are returned.\n * If no filter exists, the full collection is returned.\n */\n resolveCollectionForDevice<T = unknown>(capability: string, deviceId: string): readonly T[] {\n const state = this.capabilities.get(capability)\n if (!state || state.declaration.mode !== 'collection') return []\n\n // Check device-level filter\n const deviceMap = this.deviceCollectionFilters.get(deviceId)\n if (deviceMap) {\n const filterAddonIds = deviceMap.get(capability)\n if (filterAddonIds) {\n const filterSet = new Set(filterAddonIds)\n return state.activeCollection\n .filter((e) => filterSet.has(e.addonId))\n .map((e) => e.provider as T)\n }\n }\n\n // Fallback to full collection\n return state.activeCollection.map((e) => e.provider as T)\n }\n\n /**\n * Get a specific addon's provider by addon ID, regardless of whether it's the active singleton.\n * Useful for per-device overrides that need to look up any registered provider.\n */\n getProviderByAddonId<T = unknown>(capability: string, addonId: string): T | null {\n const state = this.capabilities.get(capability)\n if (!state) return null\n const provider = state.available.get(addonId)\n return (provider as T) ?? null\n }\n\n private activateSingleton(state: CapabilityState, addonId: string, provider: unknown): void {\n state.activeAddonId = addonId\n state.activeProvider = provider\n this.logger.info(`Singleton activated: ${state.declaration.name} → ${addonId}`)\n\n for (const consumer of state.consumers) {\n if (consumer.onSet) {\n try {\n consumer.onSet(provider)\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n this.logger.error(`Consumer onSet failed for ${state.declaration.name}: ${msg}`)\n }\n }\n }\n }\n\n private async activateSingletonAsync(state: CapabilityState, addonId: string, provider: unknown): Promise<void> {\n state.activeAddonId = addonId\n state.activeProvider = provider\n this.logger.info(`Singleton activated (async): ${state.declaration.name} → ${addonId}`)\n\n for (const consumer of state.consumers) {\n if (consumer.onSet) {\n try {\n await consumer.onSet(provider)\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n this.logger.error(`Consumer onSet (async) failed for ${state.declaration.name}: ${msg}`)\n }\n }\n }\n }\n}\n","// packages/kernel/src/infra-capabilities.ts\n\nexport interface InfraCapability {\n /** Capability name */\n readonly name: string\n /** If true, boot aborts when this capability's addon fails to initialize */\n readonly required: boolean\n}\n\n/**\n * Infrastructure capabilities that must boot before all other addons.\n * Enabled in Phase 1 of the boot sequence. Order matters: storage before logging.\n */\nexport const INFRA_CAPABILITIES: readonly InfraCapability[] = [\n { name: 'storage', required: true },\n { name: 'settings-store', required: true },\n { name: 'log-destination', required: false },\n] as const\n\nconst infraNames = new Set(INFRA_CAPABILITIES.map((c) => c.name))\n\n/** Check if a capability name is an infrastructure capability */\nexport function isInfraCapability(name: string): boolean {\n return infraNames.has(name)\n}\n","import { z } from 'zod'\n\n/** Default data directory — used when CAMSTACK_DATA env var is not set */\nexport const DEFAULT_DATA_PATH = 'camstack-data'\n\n/** Bootstrap config -- loaded from config.yaml ONLY at startup.\n * All other settings live in SQL (system_settings table). */\nexport const bootstrapSchema = z.object({\n /** Server mode: 'hub' (full server) or 'agent' (worker node) */\n mode: z.enum(['hub', 'agent']).default('hub'),\n server: z.object({\n port: z.number().default(4443),\n host: z.string().default('0.0.0.0'),\n dataPath: z.string().default(DEFAULT_DATA_PATH),\n }).default({}),\n auth: z.object({\n jwtSecret: z.string().nullable().default(null),\n adminUsername: z.string().default('admin'),\n adminPassword: z.string().default(process.env.ADMIN_PASSWORD ?? 'changeme'),\n }).default({}),\n /** Hub connection config — only used when mode='agent' */\n hub: z.object({\n url: z.string().default('ws://localhost:4443/agent'),\n token: z.string().default(''),\n }).default({}),\n /** Agent-specific config — only used when mode='agent' */\n agent: z.object({\n name: z.string().default(''),\n /** Port for the agent status page (minimal HTML) */\n statusPort: z.number().default(4444),\n }).default({}),\n /** TLS configuration */\n tls: z.object({\n /** Enable HTTPS (default: true) */\n enabled: z.boolean().default(true),\n /** Path to custom cert file (PEM). If not set, auto-generates self-signed. */\n certPath: z.string().optional(),\n /** Path to custom key file (PEM). Required if certPath is set. */\n keyPath: z.string().optional(),\n }).default({}),\n})\n\nexport type BootstrapConfig = z.infer<typeof bootstrapSchema>\n\n/**\n * Runtime defaults -- used by ConfigManager.get() for backward compatibility\n * until Plan B wires all runtime settings to the system_settings SQL table.\n */\nexport const RUNTIME_DEFAULTS: Record<string, unknown> = {\n 'features.streaming': true,\n 'features.notifications': true,\n 'features.objectDetection': false,\n 'features.remoteAccess': true,\n 'features.agentCluster': false,\n 'features.smartHome': true,\n 'features.recordings': true,\n 'features.backup': true,\n 'features.repl': true,\n 'retention.detectionEventsDays': 30,\n 'retention.audioLevelsDays': 7,\n 'logging.level': 'info',\n 'logging.retentionDays': 30,\n 'eventBus.ringBufferSize': 10000,\n 'storage.provider': 'sqlite-storage',\n 'storage.locations': {\n data: 'camstack-data/data',\n media: 'camstack-data/media',\n recordings: 'camstack-data/recordings',\n cache: '/tmp/camstack-cache',\n logs: 'camstack-data/logs',\n models: 'camstack-data/models',\n },\n 'providers': [],\n // Recording\n 'recording.segmentDurationSeconds': 4,\n 'recording.defaultRetentionDays': 30,\n // Streaming ports are addon-specific (go2rtc owns its defaults)\n // FFmpeg\n 'ffmpeg.binaryPath': 'ffmpeg',\n 'ffmpeg.hwAccel': 'auto',\n 'ffmpeg.threadCount': 0,\n // Detection defaults\n 'detection.defaultMotionFps': 2,\n 'detection.defaultDetectionFps': 5,\n 'detection.defaultCooldownSeconds': 10,\n 'detection.defaultConfidenceThreshold': 0.4,\n 'detection.trackerMaxAgeFrames': 30,\n 'detection.trackerMinHits': 3,\n 'detection.trackerIouThreshold': 0.3,\n // Backup retention is addon-specific (local-backup owns its default)\n // Auth (runtime)\n 'auth.tokenExpiry': '24h',\n}\n\nexport type ServerMode = 'hub' | 'agent'\n\nexport type AppConfig = BootstrapConfig & {\n features: {\n streaming: boolean\n notifications: boolean\n objectDetection: boolean\n remoteAccess: boolean\n agentCluster: boolean\n smartHome: boolean\n recordings: boolean\n backup: boolean\n repl: boolean\n }\n storage: {\n provider: string\n locations: Record<string, string>\n }\n logging: {\n level: string\n retentionDays: number\n }\n eventBus: {\n ringBufferSize: number\n }\n retention: {\n detectionEventsDays: number\n audioLevelsDays: number\n }\n providers: Array<{\n id: string\n type: string\n name: string\n url?: string\n username?: string\n password?: string\n mqtt?: {\n brokerUrl: string\n username?: string\n password?: string\n topicPrefix: string\n }\n }>\n}\n","import * as fs from 'node:fs'\nimport * as yaml from 'js-yaml'\nimport { bootstrapSchema, RUNTIME_DEFAULTS, type BootstrapConfig, type AppConfig } from './config-schema.js'\n\nexport interface ISettingsStore {\n getSystem(key: string): unknown\n setSystem(key: string, value: unknown): void\n getAllSystem(): Record<string, unknown>\n getAllAddon(addonId: string): Record<string, unknown>\n setAllAddon(addonId: string, config: Record<string, unknown>): void\n getAllProvider(providerId: string): Record<string, unknown>\n setProvider(providerId: string, key: string, value: unknown): void\n getAllDevice(deviceId: string): Record<string, unknown>\n setDevice(deviceId: string, key: string, value: unknown): void\n}\n\n/** Feature flags manifest — inlined to avoid dependency on core's feature module */\ninterface FeatureManifest {\n streaming: boolean\n notifications: boolean\n objectDetection: boolean\n remoteAccess: boolean\n agentCluster: boolean\n smartHome: boolean\n recordings: boolean\n backup: boolean\n repl: boolean\n}\n\n/**\n * Maps environment variable names to dot-notation config paths.\n * Only bootstrap-level env vars -- runtime settings come from SQL (Plan B).\n */\nconst ENV_VAR_MAP: Record<string, string> = {\n CAMSTACK_PORT: 'server.port',\n CAMSTACK_HOST: 'server.host',\n CAMSTACK_DATA: 'server.dataPath',\n CAMSTACK_JWT_SECRET: 'auth.jwtSecret',\n CAMSTACK_ADMIN_USER: 'auth.adminUsername',\n CAMSTACK_ADMIN_PASS: 'auth.adminPassword',\n}\n\nexport class ConfigManager {\n // Non-readonly so update() can sync the in-memory view after a write.\n private bootstrapConfig: BootstrapConfig\n private settingsStore: ISettingsStore | null = null\n\n constructor(private readonly configPath: string) {\n const rawYaml = this.loadYaml()\n const merged = this.applyEnvOverrides(rawYaml as Record<string, unknown>)\n this.bootstrapConfig = bootstrapSchema.parse(merged)\n this.warnDefaultCredentials()\n }\n\n /** Called by main.ts after the SQLite DB is ready (Phase 2). */\n setSettingsStore(store: ISettingsStore): void {\n this.settingsStore = store\n }\n\n /**\n * Get a config value by dot-notation path.\n * Priority: bootstrap config -> SQL system_settings -> RUNTIME_DEFAULTS fallback.\n */\n get<T>(path: string): T {\n // First: check bootstrap config (server, auth)\n const bootstrapValue = this.getFromBootstrap(path)\n if (bootstrapValue !== undefined) {\n return bootstrapValue as T\n }\n\n // Second: check SQL system_settings (if store is wired)\n if (this.settingsStore !== null) {\n const sqlValue = this.settingsStore.getSystem(path)\n if (sqlValue !== undefined) {\n return sqlValue as T\n }\n // Also try prefix-based nested lookup against SQL keys\n const sqlNested = this.getNestedFromSystemSettings(path)\n if (sqlNested !== undefined) {\n return sqlNested as T\n }\n }\n\n // Third: check runtime defaults (backward compat when store not yet wired)\n if (path in RUNTIME_DEFAULTS) {\n return RUNTIME_DEFAULTS[path] as T\n }\n\n // Fourth: try nested lookup in runtime defaults\n const nested = this.getFromRuntimeDefaults(path)\n if (nested !== undefined) {\n return nested as T\n }\n\n return undefined as T\n }\n\n /**\n * Write a value to SQL system_settings.\n * Throws if the settings store is not yet wired.\n */\n set(key: string, value: unknown): void {\n if (this.settingsStore === null) {\n throw new Error('[ConfigManager] SettingsStore not initialized -- call setSettingsStore() first')\n }\n this.settingsStore.setSystem(key, value)\n }\n\n /**\n * Bulk-read all system_settings keys that belong to a logical section.\n * A \"section\" is the first segment of a dot-notation key (e.g. 'features', 'logging').\n */\n getSection(section: string): Record<string, unknown> {\n // 1. Try SQL system_settings\n if (this.settingsStore !== null) {\n const nested = this.getNestedFromSystemSettings(section)\n if (nested !== undefined) return nested as Record<string, unknown>\n }\n // 2. Try bootstrap config (YAML) — for sections like 'server', 'auth' that live in config.yaml\n const bootstrapValue = (this.bootstrapConfig as Record<string, unknown>)[section]\n if (bootstrapValue !== undefined && bootstrapValue !== null && typeof bootstrapValue === 'object') {\n return bootstrapValue as Record<string, unknown>\n }\n // 3. Fallback to RUNTIME_DEFAULTS\n return (this.getFromRuntimeDefaults(section) as Record<string, unknown>) ?? {}\n }\n\n /**\n * Bulk-write a section of runtime settings to SQL system_settings.\n * Each entry in `data` is stored as `section.key`.\n */\n setSection(section: string, data: Record<string, unknown>): void {\n if (this.settingsStore === null) {\n throw new Error('[ConfigManager] SettingsStore not initialized -- call setSettingsStore() first')\n }\n for (const [key, value] of Object.entries(data)) {\n this.settingsStore.setSystem(`${section}.${key}`, value)\n }\n }\n\n // ---------------------------------------------------------------------------\n // Addon / Provider / Device scoped config\n // ---------------------------------------------------------------------------\n\n /** Read all config for an addon from addon_settings. */\n getAddonConfig(addonId: string): Record<string, unknown> {\n if (this.settingsStore !== null) {\n return this.settingsStore.getAllAddon(addonId)\n }\n // Fallback: read from bootstrap (legacy)\n return (this.getFromBootstrap(`addons.${addonId}`) as Record<string, unknown>) ?? {}\n }\n\n /** Write (bulk-replace) config for an addon to addon_settings. */\n setAddonConfig(addonId: string, config: Record<string, unknown>): void {\n if (this.settingsStore === null) {\n throw new Error('[ConfigManager] SettingsStore not initialized -- call setSettingsStore() first')\n }\n this.settingsStore.setAllAddon(addonId, config)\n }\n\n /** Read all config for a provider from provider_settings. */\n getProviderConfig(providerId: string): Record<string, unknown> {\n if (this.settingsStore !== null) {\n return this.settingsStore.getAllProvider(providerId)\n }\n return {}\n }\n\n /** Write (upsert) a single key for a provider to provider_settings. */\n setProviderConfig(providerId: string, key: string, value: unknown): void {\n if (this.settingsStore === null) {\n throw new Error('[ConfigManager] SettingsStore not initialized -- call setSettingsStore() first')\n }\n this.settingsStore.setProvider(providerId, key, value)\n }\n\n /** Read all config for a device from device_settings. */\n getDeviceConfig(deviceId: string): Record<string, unknown> {\n if (this.settingsStore !== null) {\n return this.settingsStore.getAllDevice(deviceId)\n }\n return {}\n }\n\n /** Write (upsert) a single key for a device to device_settings. */\n setDeviceConfig(deviceId: string, key: string, value: unknown): void {\n if (this.settingsStore === null) {\n throw new Error('[ConfigManager] SettingsStore not initialized -- call setSettingsStore() first')\n }\n this.settingsStore.setDevice(deviceId, key, value)\n }\n\n /** Get a value from the parsed bootstrap config */\n getBootstrap<T>(path: string): T {\n return this.getFromBootstrap(path) as T\n }\n\n /** Features accessor -- reads from SQL when available, falls back to RUNTIME_DEFAULTS */\n get features(): FeatureManifest {\n const g = (key: string): boolean =>\n (this.get<boolean>(`features.${key}`) ?? RUNTIME_DEFAULTS[`features.${key}`]) as boolean\n return {\n streaming: g('streaming'),\n notifications: g('notifications'),\n objectDetection: g('objectDetection'),\n remoteAccess: g('remoteAccess'),\n agentCluster: g('agentCluster'),\n smartHome: g('smartHome'),\n recordings: g('recordings'),\n backup: g('backup'),\n repl: g('repl'),\n }\n }\n\n /**\n * Returns a merged view of bootstrap config + runtime defaults for backward compat.\n */\n get raw(): AppConfig {\n const features = {\n streaming: RUNTIME_DEFAULTS['features.streaming'] as boolean,\n notifications: RUNTIME_DEFAULTS['features.notifications'] as boolean,\n objectDetection: RUNTIME_DEFAULTS['features.objectDetection'] as boolean,\n remoteAccess: RUNTIME_DEFAULTS['features.remoteAccess'] as boolean,\n agentCluster: RUNTIME_DEFAULTS['features.agentCluster'] as boolean,\n smartHome: RUNTIME_DEFAULTS['features.smartHome'] as boolean,\n recordings: RUNTIME_DEFAULTS['features.recordings'] as boolean,\n backup: RUNTIME_DEFAULTS['features.backup'] as boolean,\n repl: RUNTIME_DEFAULTS['features.repl'] as boolean,\n }\n return {\n ...this.bootstrapConfig,\n features,\n storage: RUNTIME_DEFAULTS['storage.locations'] !== undefined\n ? {\n provider: RUNTIME_DEFAULTS['storage.provider'] as string,\n locations: RUNTIME_DEFAULTS['storage.locations'] as Record<string, string>,\n }\n : { provider: 'sqlite-storage', locations: {} },\n logging: {\n level: RUNTIME_DEFAULTS['logging.level'] as string,\n retentionDays: RUNTIME_DEFAULTS['logging.retentionDays'] as number,\n },\n eventBus: {\n ringBufferSize: RUNTIME_DEFAULTS['eventBus.ringBufferSize'] as number,\n },\n retention: {\n detectionEventsDays: RUNTIME_DEFAULTS['retention.detectionEventsDays'] as number,\n audioLevelsDays: RUNTIME_DEFAULTS['retention.audioLevelsDays'] as number,\n },\n providers: RUNTIME_DEFAULTS['providers'] as AppConfig['providers'],\n }\n }\n\n /** Sections that live in config.yaml. Everything else goes to SQL. */\n private static readonly BOOTSTRAP_SECTIONS = new Set(['server', 'auth', 'mode'])\n\n /**\n * Atomically update one top-level section of config.yaml and sync in-memory.\n * Only bootstrap sections (server, auth, mode) are written to YAML.\n * Runtime settings must use setSection() which writes to SQL.\n */\n update(section: string, data: Record<string, unknown>): void {\n if (!ConfigManager.BOOTSTRAP_SECTIONS.has(section)) {\n throw new Error(\n `[ConfigManager] Section \"${section}\" is a runtime setting — use setSection() to write to DB, not update() which writes to config.yaml`,\n )\n }\n\n // Load latest raw YAML to avoid overwriting concurrent changes\n let raw: Record<string, unknown> = {}\n if (fs.existsSync(this.configPath)) {\n raw = (yaml.load(fs.readFileSync(this.configPath, 'utf-8')) as Record<string, unknown>) ?? {}\n }\n\n // Shallow merge within the section\n const existing = (raw[section] as Record<string, unknown>) ?? {}\n raw[section] = { ...existing, ...data }\n\n // Validate bootstrap sections\n const validation = bootstrapSchema.safeParse(raw)\n if (!validation.success) {\n throw new Error(`[ConfigManager] Invalid config update for section \"${section}\": ${validation.error.message}`)\n }\n\n // Atomic write: temp file -> rename\n const tmpPath = `${this.configPath}.tmp`\n fs.writeFileSync(tmpPath, yaml.dump(raw, { lineWidth: 120, indent: 2, quotingType: '\"' }), 'utf-8')\n fs.renameSync(tmpPath, this.configPath)\n\n // Sync in-memory bootstrap state\n this.bootstrapConfig = validation.data\n }\n\n /**\n * Deep-set a value in a nested plain object using a dot-notation path.\n * Returns a new object (immutable).\n */\n private setNested(obj: Record<string, unknown>, path: string, value: unknown): Record<string, unknown> {\n const [head, ...rest] = path.split('.')\n if (!head) return obj\n if (rest.length === 0) {\n return { ...obj, [head]: value }\n }\n const child = (obj[head] ?? {}) as Record<string, unknown>\n return { ...obj, [head]: this.setNested(child, rest.join('.'), value) }\n }\n\n /**\n * Apply env var overrides onto the raw YAML object.\n * Only bootstrap-level env vars are applied.\n */\n private applyEnvOverrides(raw: Record<string, unknown>): Record<string, unknown> {\n let result = { ...raw }\n\n for (const [envKey, configPath] of Object.entries(ENV_VAR_MAP)) {\n const envValue = process.env[envKey]\n if (envValue === undefined || envValue === '') continue\n\n // Coerce to number for known numeric fields\n const coerced: unknown = configPath === 'server.port' ? Number(envValue) : envValue\n result = this.setNested(result, configPath, coerced)\n console.log(`[ConfigManager] Env override applied: ${envKey} → ${configPath}`)\n }\n\n return result\n }\n\n private loadYaml(): unknown {\n if (!fs.existsSync(this.configPath)) {\n console.warn(\n `[ConfigManager] Config file not found at: ${this.configPath}\\n` +\n ` → Using built-in defaults. Set CONFIG_PATH env var or create the file.\\n` +\n ` → Example path from project root: ./server/backend/data/config.yaml`,\n )\n return {}\n }\n\n const content = fs.readFileSync(this.configPath, 'utf-8')\n const parsed = yaml.load(content) ?? {}\n console.log(`[ConfigManager] Loaded config from: ${this.configPath}`)\n return parsed\n }\n\n private warnDefaultCredentials(): void {\n if (this.bootstrapConfig.auth.adminPassword === 'changeme') {\n console.warn(\n `[ConfigManager] Warning: Using default admin password \"changeme\". ` +\n `Set auth.adminPassword in your config.yaml or the ADMIN_PASSWORD env var.`,\n )\n }\n }\n\n private getFromBootstrap(path: string): unknown {\n const keys = path.split('.')\n let current: unknown = this.bootstrapConfig\n\n for (const key of keys) {\n if (current === null || current === undefined || typeof current !== 'object') {\n return undefined\n }\n current = (current as Record<string, unknown>)[key]\n }\n\n return current\n }\n\n private getFromRuntimeDefaults(path: string): unknown {\n // Look for prefix matches (e.g. 'features' -> checks 'features.*' entries)\n const prefix = path + '.'\n const result: Record<string, unknown> = {}\n let found = false\n\n for (const [key, value] of Object.entries(RUNTIME_DEFAULTS)) {\n if (key.startsWith(prefix)) {\n const subKey = key.slice(prefix.length)\n result[subKey] = value\n found = true\n }\n }\n\n return found ? result : undefined\n }\n\n /**\n * Perform a prefix-based nested lookup against SQL system_settings.\n * e.g. path='features' matches keys 'features.streaming', 'features.notifications', etc.\n * Returns an object keyed by the sub-key, or undefined if nothing is found.\n */\n private getNestedFromSystemSettings(path: string): unknown {\n if (this.settingsStore === null) return undefined\n const all = this.settingsStore.getAllSystem()\n const prefix = path + '.'\n const result: Record<string, unknown> = {}\n let found = false\n for (const [key, value] of Object.entries(all)) {\n if (key.startsWith(prefix)) {\n result[key.slice(prefix.length)] = value\n found = true\n }\n }\n return found ? result : undefined\n }\n}\n","import { fork, type ChildProcess } from 'node:child_process'\nimport type {\n WorkerInfo,\n WorkerState,\n WorkerToMainMessage,\n} from '@camstack/types'\n\n/** Infra addon IDs that must stay in-process */\nconst INFRA_ADDON_IDS = new Set([\n 'filesystem-storage',\n 'sqlite-settings',\n 'winston-logging',\n])\n\nexport interface WorkerHostOptions {\n readonly workerEntryPath: string\n readonly devMode?: boolean\n /** Force all addons in-process (emergency fallback) */\n readonly forceInProcess?: boolean\n readonly heartbeatIntervalMs?: number\n readonly heartbeatTimeoutMs?: number\n readonly maxCrashesInWindow?: number\n readonly crashWindowMs?: number\n readonly shutdownTimeoutMs?: number\n /** Log writer function — receives structured log entries from workers */\n readonly onWorkerLog?: (entry: { addonId: string; level: string; message: string; scope: readonly string[]; meta?: Record<string, unknown> }) => void\n}\n\ninterface ForkConfig {\n readonly addonDir: string\n readonly config: Record<string, unknown>\n readonly storagePaths: Record<string, string>\n readonly dataDir?: string\n readonly locationPaths?: Record<string, string>\n readonly workerToken?: string\n}\n\ninterface ManagedWorker {\n addonId: string\n process: ChildProcess\n state: WorkerState\n startedAt: number\n lastHeartbeat: number\n restartCount: number\n crashTimestamps: number[]\n cpuPercent: number\n memoryRss: number\n workerToken?: string\n /** Stored fork config for restart support */\n forkConfig?: ForkConfig\n}\n\ntype ResolvedWorkerHostOptions = Required<Omit<WorkerHostOptions, 'onWorkerLog'>> & Pick<WorkerHostOptions, 'onWorkerLog'>\n\nexport class AddonWorkerHost {\n private readonly workers = new Map<string, ManagedWorker>()\n private readonly options: ResolvedWorkerHostOptions\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null\n\n /** Set of addons that failed to fork and fell back to in-process */\n private readonly fallbackInProcess = new Set<string>()\n\n constructor(options: WorkerHostOptions) {\n this.options = {\n workerEntryPath: options.workerEntryPath,\n devMode: options.devMode ?? false,\n forceInProcess: options.forceInProcess ?? (process.env.CAMSTACK_FORCE_INPROCESS === 'true'),\n heartbeatIntervalMs: options.heartbeatIntervalMs ?? 5000,\n heartbeatTimeoutMs: options.heartbeatTimeoutMs ?? 30000,\n maxCrashesInWindow: options.maxCrashesInWindow ?? 3,\n crashWindowMs: options.crashWindowMs ?? 60000,\n shutdownTimeoutMs: options.shutdownTimeoutMs ?? 10000,\n onWorkerLog: options.onWorkerLog,\n }\n }\n\n /** Check if an addon is infrastructure (must stay in-process) */\n isInfraAddon(addonId: string): boolean {\n return INFRA_ADDON_IDS.has(addonId)\n }\n\n /** Check if an addon fell back to in-process after fork failure */\n isFallbackInProcess(addonId: string): boolean {\n return this.fallbackInProcess.has(addonId)\n }\n\n /** Mark an addon as fallen back to in-process */\n markFallbackInProcess(addonId: string): void {\n this.fallbackInProcess.add(addonId)\n }\n\n /**\n * Determine if an addon should be forked.\n * Default: fork everything except infra addons.\n * Addons can opt out with `inProcess: true` in declaration.\n * Emergency fallback: CAMSTACK_FORCE_INPROCESS=true disables all forking.\n */\n shouldFork(addonId: string, declaration?: { forkable?: boolean; inProcess?: boolean }): boolean {\n if (this.options.forceInProcess) return false\n if (this.options.devMode) return false\n if (this.isInfraAddon(addonId)) return false\n if (this.fallbackInProcess.has(addonId)) return false\n // Addon can explicitly opt out of forking\n if (declaration?.inProcess === true) return false\n // Must explicitly declare forkable: true to fork\n if (declaration?.forkable !== true) return false\n return true\n }\n\n /** Fork a worker for an addon */\n async forkWorker(\n addonId: string,\n addonDir: string,\n config: Record<string, unknown>,\n storagePaths: Record<string, string>,\n dataDir?: string,\n locationPaths?: Record<string, string>,\n workerToken?: string,\n ): Promise<void> {\n if (this.workers.has(addonId)) {\n throw new Error(`Worker for addon \"${addonId}\" already exists`)\n }\n\n // Worker entry is always a compiled .js file — no tsx needed.\n // Pass all config via environment variables (not IPC INIT message).\n const workerEnv: Record<string, string> = {\n PATH: process.env.PATH ?? '',\n HOME: process.env.HOME ?? '',\n NODE_ENV: process.env.NODE_ENV ?? 'production',\n NODE_TLS_REJECT_UNAUTHORIZED: '0', // Accept self-signed cert\n CAMSTACK_WORKER_HUB_URL: `wss://localhost:${process.env.CAMSTACK_PORT ?? '4443'}/trpc`,\n CAMSTACK_WORKER_TOKEN: workerToken ?? '',\n CAMSTACK_ADDON_ID: addonId,\n CAMSTACK_ADDON_DIR: addonDir,\n CAMSTACK_ADDON_CONFIG: JSON.stringify(config),\n CAMSTACK_DATA_DIR: dataDir ?? `camstack-data/addons-data/${addonId}`,\n CAMSTACK_LOCATION_PATHS: JSON.stringify(locationPaths ?? storagePaths),\n }\n\n const forkOptions: Record<string, unknown> = {\n stdio: ['inherit', 'inherit', 'inherit', 'ipc'],\n execArgv: [],\n env: workerEnv,\n }\n\n const child = fork(this.options.workerEntryPath, [], forkOptions as any)\n\n const worker: ManagedWorker = {\n addonId,\n process: child,\n state: 'starting',\n startedAt: Date.now(),\n lastHeartbeat: Date.now(),\n restartCount: 0,\n crashTimestamps: [],\n cpuPercent: 0,\n memoryRss: 0,\n workerToken,\n forkConfig: { addonDir, config, storagePaths, dataDir, locationPaths, workerToken },\n }\n\n this.workers.set(addonId, worker)\n\n // Route structured IPC messages from worker\n child.on('message', (msg: WorkerToMainMessage) => {\n if (msg.type === 'LOG' && this.options.onWorkerLog) {\n this.options.onWorkerLog({\n addonId,\n level: msg.level,\n message: msg.message,\n scope: ['hub', addonId],\n meta: msg.context,\n })\n } else if (msg.type === 'STATS') {\n worker.cpuPercent = msg.cpu\n worker.memoryRss = msg.memory\n }\n })\n\n // Capture stdout/stderr from worker as fallback (unstructured logs, crash output)\n child.stdout?.on('data', (chunk: Buffer) => {\n const lines = chunk.toString().trim()\n if (!lines) return\n if (this.options.onWorkerLog) {\n this.options.onWorkerLog({\n addonId,\n level: 'info',\n message: lines,\n scope: ['hub', addonId, '__stdout'],\n })\n } else {\n console.log(`[Worker:${addonId}] ${lines}`)\n }\n })\n child.stderr?.on('data', (chunk: Buffer) => {\n const lines = chunk.toString().trim()\n if (!lines) return\n if (this.options.onWorkerLog) {\n this.options.onWorkerLog({\n addonId,\n level: 'error',\n message: lines,\n scope: ['hub', addonId, '__stderr'],\n })\n } else {\n console.error(`[Worker:${addonId}:ERR] ${lines}`)\n }\n })\n\n child.on('exit', (code) => {\n this.handleWorkerExit(addonId, code, addonDir, config, storagePaths, dataDir, locationPaths, workerToken)\n })\n\n child.on('error', (err) => {\n console.error(`[WorkerHost] Worker ${addonId} error:`, err.message)\n })\n\n // No IPC INIT message — config is passed via env vars.\n // Wait for READY signal via IPC (process.send({ type: 'READY' }) from worker).\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new Error(`Worker ${addonId} did not become ready within 30s`))\n }, 30000)\n\n const readyCheck = (msg: { type: string }) => {\n if (msg.type === 'READY') {\n clearTimeout(timeout)\n child.off('message', readyCheck)\n worker.state = 'running'\n resolve()\n }\n }\n child.on('message', readyCheck)\n })\n }\n\n /** List all workers with stats */\n listWorkers(): readonly WorkerInfo[] {\n return [...this.workers.values()].map((w) => ({\n addonId: w.addonId,\n pid: w.process.pid ?? 0,\n state: w.state,\n cpuPercent: w.cpuPercent,\n memoryRss: w.memoryRss,\n uptimeSeconds: Math.round((Date.now() - w.startedAt) / 1000),\n restartCount: w.restartCount,\n subProcesses: [],\n }))\n }\n\n /** Gracefully shutdown a single worker by addon ID */\n async shutdownWorkerById(addonId: string): Promise<void> {\n const worker = this.workers.get(addonId)\n if (!worker) {\n throw new Error(`No worker found for addon \"${addonId}\"`)\n }\n await this.shutdownWorker(addonId, worker)\n this.workers.delete(addonId)\n }\n\n /** Gracefully restart a single worker by addon ID (stop then re-fork with same config) */\n async restartWorker(addonId: string): Promise<void> {\n const worker = this.workers.get(addonId)\n if (!worker) {\n throw new Error(`No worker found for addon \"${addonId}\"`)\n }\n\n const forkConfig = worker.forkConfig\n if (!forkConfig) {\n throw new Error(`No fork config stored for addon \"${addonId}\" — cannot restart`)\n }\n\n // Gracefully stop the existing worker\n await this.shutdownWorker(addonId, worker)\n this.workers.delete(addonId)\n\n // Re-fork with the same configuration\n await this.forkWorker(\n addonId,\n forkConfig.addonDir,\n forkConfig.config,\n forkConfig.storagePaths,\n forkConfig.dataDir,\n forkConfig.locationPaths,\n forkConfig.workerToken,\n )\n }\n\n /** Gracefully shutdown all workers */\n async shutdownAll(): Promise<void> {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n\n const shutdowns = [...this.workers.entries()].map(([addonId, worker]) =>\n this.shutdownWorker(addonId, worker),\n )\n await Promise.allSettled(shutdowns)\n this.workers.clear()\n }\n\n /** Start heartbeat monitoring — workers now report heartbeat via tRPC, not IPC PING */\n startHeartbeatMonitoring(): void {\n // With tRPC-based workers, heartbeat is handled by the agent.heartbeat mutation.\n // Keep this method for API compatibility but no IPC pings are needed.\n this.heartbeatTimer = setInterval(() => {\n const now = Date.now()\n for (const [addonId, worker] of this.workers) {\n if (worker.state !== 'running') continue\n // Workers report heartbeat via tRPC — if no update in timeout window, mark crashed\n if (now - worker.lastHeartbeat > this.options.heartbeatTimeoutMs) {\n console.warn(`[WorkerHost] Worker ${addonId} heartbeat timeout — marking unresponsive`)\n worker.state = 'crashed'\n worker.process.kill('SIGKILL')\n }\n }\n }, this.options.heartbeatIntervalMs)\n }\n\n /** Update heartbeat timestamp for a worker (called when agent.heartbeat arrives) */\n updateWorkerHeartbeat(addonId: string, cpuPercent?: number, memoryRss?: number): void {\n const worker = this.workers.get(addonId)\n if (!worker) return\n worker.lastHeartbeat = Date.now()\n if (cpuPercent !== undefined) worker.cpuPercent = cpuPercent\n if (memoryRss !== undefined) worker.memoryRss = memoryRss\n }\n\n private handleWorkerExit(\n addonId: string,\n code: number | null,\n addonDir: string,\n config: Record<string, unknown>,\n storagePaths: Record<string, string>,\n dataDir?: string,\n locationPaths?: Record<string, string>,\n workerToken?: string,\n ): void {\n const worker = this.workers.get(addonId)\n if (!worker) return\n\n if (worker.state === 'stopping') {\n worker.state = 'stopped'\n return\n }\n\n worker.state = 'crashed'\n console.error(`[WorkerHost] Worker ${addonId} exited with code ${code}`)\n\n // Check crash rate\n const now = Date.now()\n worker.crashTimestamps.push(now)\n const recentCrashes = worker.crashTimestamps.filter(\n (t) => now - t < this.options.crashWindowMs,\n )\n worker.crashTimestamps = recentCrashes\n\n if (recentCrashes.length >= this.options.maxCrashesInWindow) {\n console.error(`[WorkerHost] Worker ${addonId} crashed ${recentCrashes.length} times in ${this.options.crashWindowMs}ms — not restarting`)\n return\n }\n\n // Restart after backoff\n const backoff = Math.min(2000 * (worker.restartCount + 1), 30000)\n console.log(`[WorkerHost] Restarting worker ${addonId} in ${backoff}ms`)\n setTimeout(() => {\n worker.restartCount++\n this.workers.delete(addonId)\n this.forkWorker(addonId, addonDir, config, storagePaths, dataDir, locationPaths, workerToken).catch((err) => {\n console.error(`[WorkerHost] Failed to restart worker ${addonId}:`, err)\n })\n }, backoff)\n }\n\n private async shutdownWorker(addonId: string, worker: ManagedWorker): Promise<void> {\n worker.state = 'stopping'\n // Send SIGTERM — worker handles graceful shutdown internally\n worker.process.kill('SIGTERM')\n\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n console.warn(`[WorkerHost] Worker ${addonId} did not exit within ${this.options.shutdownTimeoutMs}ms — SIGKILL`)\n worker.process.kill('SIGKILL')\n resolve()\n }, this.options.shutdownTimeoutMs)\n\n worker.process.on('exit', () => {\n clearTimeout(timeout)\n resolve()\n })\n })\n }\n}\n","// Addon loading\nexport type { RegisteredAddon } from './addon-loader.js'\nexport { AddonLoader } from './addon-loader.js'\nexport { AddonEngineManager } from './addon-engine-manager.js'\nexport type { AddonInstallerConfig, InstalledPackage } from './addon-installer.js'\nexport { AddonInstaller } from './addon-installer.js'\n\n// Shared filesystem utilities\nexport {\n copyDirRecursive,\n ensureDir,\n stripCamstackDeps,\n copyExtraFileDirs,\n symlinkAddonsToNodeModules,\n ensureLibraryBuilt,\n installPackageFromNpmSync,\n isSourceNewer,\n} from './fs-utils.js'\n\n// Workspace detection\nexport { detectWorkspacePackagesDir } from './workspace-detect.js'\n\n// Capability\nexport { CapabilityRegistry } from './capability-registry.js'\nexport { INFRA_CAPABILITIES, isInfraCapability } from './infra-capabilities.js'\nexport type { InfraCapability } from './infra-capabilities.js'\n\n// Config\nexport { ConfigManager } from './config-manager.js'\nexport type { ISettingsStore } from './config-manager.js'\nexport { bootstrapSchema, RUNTIME_DEFAULTS, DEFAULT_DATA_PATH } from './config-schema.js'\nexport type { BootstrapConfig, AppConfig, ServerMode } from './config-schema.js'\n\n// Worker (process isolation)\nexport { AddonWorkerHost, type WorkerHostOptions } from './worker/addon-worker-host.js'\nexport { WorkerProcessManager } from './worker/worker-process-manager.js'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,QAAA,KAAA,aAAA,UAAA,IAAA,CAAA;AACA,QAAA,OAAA,aAAA,UAAA,MAAA,CAAA;AAkBA,aAAS,kBAAkB,KAA4B;AACrD,UAAI,YAAY,IAAI,SAAS,KAAK,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC,CAAE;AAG1D,UAAI,aAAa,OAAO,cAAc,YAAY,aAAc,WAAuC;AACrG,oBAAa,UAAsC,SAAS;MAC9D;AAEA,aAAO,OAAO,cAAc,aACvB,YACD;IACN;AAEA,QAAaA,eAAb,MAAwB;MACd,SAAS,oBAAI,IAAG;;;MAIxB,MAAM,kBAAkB,WAAiB;AACvC,YAAI,CAAC,GAAG,WAAW,SAAS;AAAG;AAE/B,cAAM,UAAU,GAAG,YAAY,WAAW,EAAE,eAAe,KAAI,CAAE;AACjE,mBAAW,SAAS,SAAS;AAC3B,cAAI,CAAC,MAAM,YAAW;AAAI;AAG1B,cAAI,MAAM,KAAK,WAAW,GAAG,GAAG;AAC9B,kBAAM,WAAW,KAAK,KAAK,WAAW,MAAM,IAAI;AAChD,kBAAM,eAAe,GAAG,YAAY,UAAU,EAAE,eAAe,KAAI,CAAE;AACrE,uBAAW,cAAc,cAAc;AACrC,kBAAI,CAAC,WAAW,YAAW;AAAI;AAC/B,oBAAM,KAAK,aAAa,KAAK,KAAK,UAAU,WAAW,IAAI,CAAC;YAC9D;AACA;UACF;AAGA,gBAAM,KAAK,aAAa,KAAK,KAAK,WAAW,MAAM,IAAI,CAAC;QAC1D;MACF;MAEQ,MAAM,aAAa,UAAgB;AACzC,cAAM,cAAc,KAAK,KAAK,UAAU,cAAc;AACtD,YAAI,CAAC,GAAG,WAAW,WAAW;AAAG;AACjC,YAAI;AACF,gBAAM,KAAK,iBAAiB,QAAQ;QACtC,SAAS,KAAK;AACZ,kBAAQ,KAAK,6BAA6B,QAAQ,KAAK,GAAG,EAAE;QAC9D;MACF;;MAGA,MAAM,iBAAiB,UAAgB;AACrC,cAAM,cAAc,KAAK,KAAK,UAAU,cAAc;AACtD,cAAM,UAAU,KAAK,MAAM,GAAG,aAAa,aAAa,OAAO,CAAC;AAChE,cAAM,cAAc,QAAQ,MAAM;AAClC,cAAM,iBAAkB,QAAQ,SAAS,KAAgB;AACzD,cAAM,WAAW,QAAQ,UAAU;AAEnC,YAAI,CAAC,UAAU,QAAQ;AAAQ;AAE/B,mBAAW,eAAe,SAAS,QAAQ;AAEzC,gBAAM,YAAY,YAAY,MAC3B,QAAQ,SAAS,EAAE,EACnB,QAAQ,UAAU,OAAO,EACzB,QAAQ,SAAS,KAAK;AAEzB,cAAI,YAAY,KAAK,QAAQ,UAAU,SAAS;AAGhD,cAAI,CAAC,GAAG,WAAW,SAAS,GAAG;AAC7B,kBAAM,OAAO,UAAU,QAAQ,mBAAmB,EAAE;AACpD,kBAAM,eAAe;cACnB,GAAG,IAAI;cACP,GAAG,IAAI;cACP,KAAK,QAAQ,UAAU,QAAQ,UAAU;cACzC,KAAK,QAAQ,UAAU,QAAQ,WAAW;cAC1C,KAAK,QAAQ,UAAU,QAAQ,WAAW;cAC1C,KAAK,QAAQ,UAAU,YAAY,KAAK;;AAE1C,wBAAY,aAAa,KAAK,OAAK,GAAG,WAAW,CAAC,CAAC,KAAK;UAC1D;AAEA,cAAI,CAAC,GAAG,WAAW,SAAS,GAAG;AAC7B,kBAAM,IAAI,MAAM,oBAAoB,SAAS,EAAE;UACjD;AAEA,gBAAM,MAAO,MAAA,QAAA,QAAA,GAAa,SAAS,EAAA,EAAA,KAAA,OAAA,aAAA,UAAA,CAAA,CAAA,CAAA;AACnC,gBAAM,aAAa,kBAAkB,GAAG;AAExC,cAAI,CAAC,YAAY;AACf,kBAAM,IAAI,MAAM,qBAAqB,SAAS,EAAE;UAClD;AAEA,eAAK,OAAO,IAAI,YAAY,IAAI;YAC9B;YACA;YACA;YACA,oBAAoB,SAAS;YAC7B,YAAY;WACb;QACH;MACF;;MAGA,MAAM,aACJ,SACA,YACA,aACA,aACA,iBAAiB,SAAO;AAExB,cAAM,MAAO,MAAA,QAAA,QAAA,GAAa,UAAU,EAAA,EAAA,KAAA,OAAA,aAAA,UAAA,CAAA,CAAA,CAAA;AACpC,cAAM,aAAa,kBAAkB,GAAG;AAExC,YAAI,CAAC,YAAY;AACf,gBAAM,IAAI,MAAM,UAAU,UAAU,wBAAwB;QAC9D;AAEA,aAAK,OAAO,IAAI,SAAS;UACvB,aAAa;YACX,IAAI;YACJ,OAAO;YACP,MAAM;YACN,GAAG;;UAEL;UACA;UACA,YAAY;SACb;MACH;;MAGA,SAAS,SAAe;AACtB,eAAO,KAAK,OAAO,IAAI,OAAO;MAChC;;MAGA,aAAU;AACR,eAAO,CAAC,GAAG,KAAK,OAAO,OAAM,CAAE;MACjC;;MAGA,SAAS,SAAe;AACtB,eAAO,KAAK,OAAO,IAAI,OAAO;MAChC;;MAGA,eAAe,SAAe;AAC5B,cAAM,aAAa,KAAK,OAAO,IAAI,OAAO;AAC1C,YAAI,CAAC,YAAY;AACf,gBAAM,IAAI,MAAM,UAAU,OAAO,qBAAqB;QACxD;AACA,eAAO,IAAI,WAAW,WAAU;MAClC;;AA9IF,YAAA,cAAAA;;;;;;;;;;AC/BA,QAAA,gBAAA,UAAA,QAAA;AAEA,QAAaC,sBAAb,MAA+B;MAIV;MACA;MAJX,UAAU,oBAAI,IAAG;MAEzB,YACmB,QACA,aAAuD;AADvD,aAAA,SAAA;AACA,aAAA,cAAA;MAChB;;;;;MAMH,MAAM,kBACJ,SACA,cACA,gBAAwC;AAGxC,cAAM,kBAAkB,EAAE,GAAG,cAAc,GAAG,eAAc;AAC5D,cAAM,YAAY,GAAG,OAAO,IAAI,KAAK,WAAW,eAAe,CAAC;AAEhE,cAAM,WAAW,KAAK,QAAQ,IAAI,SAAS;AAC3C,YAAI;AAAU,iBAAO;AAGrB,cAAM,QAAQ,KAAK,OAAO,eAAe,OAAO;AAChD,cAAM,MAAM,WAAW,EAAE,GAAG,KAAK,aAAa,aAAa,gBAAe,CAAkB;AAC5F,aAAK,QAAQ,IAAI,WAAW,KAAK;AACjC,eAAO;MACT;;MAGA,mBAAgB;AACd,eAAO,IAAI,IAAI,KAAK,OAAO;MAC7B;;MAGA,MAAM,eAAe,WAAiB;AACpC,cAAM,SAAS,KAAK,QAAQ,IAAI,SAAS;AACzC,YAAI,QAAQ;AACV,gBAAM,OAAO,SAAQ;AACrB,eAAK,QAAQ,OAAO,SAAS;QAC/B;MACF;;MAGA,MAAM,cAAW;AACf,mBAAW,CAAC,EAAE,MAAM,KAAK,KAAK,SAAS;AACrC,gBAAM,OAAO,SAAQ;QACvB;AACA,aAAK,QAAQ,MAAK;MACpB;;MAGA,iBAAiB,SAAiB,iBAAwC;AACxE,eAAO,GAAG,OAAO,IAAI,KAAK,WAAW,eAAe,CAAC;MACvD;MAEQ,WAAW,QAA+B;AAChD,cAAM,SAAS,KAAK,UAAU,QAAQ,OAAO,KAAK,MAAM,EAAE,KAAI,CAAE;AAChE,gBAAO,GAAA,cAAA,YAAW,KAAK,EAAE,OAAO,MAAM,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;MACnE;;AA7DF,YAAA,qBAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACOA,YAAA,6BAAAC;AAXA,QAAA,KAAA,aAAA,UAAA,IAAA,CAAA;AACA,QAAA,OAAA,aAAA,UAAA,MAAA,CAAA;AAUA,aAAgBA,4BAA2B,UAAgB;AACzD,UAAI,UAAU,KAAK,QAAQ,QAAQ;AACnC,eAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,kBAAU,KAAK,QAAQ,OAAO;AAC9B,cAAM,cAAc,KAAK,KAAK,SAAS,UAAU;AACjD,cAAM,cAAc,KAAK,KAAK,SAAS,cAAc;AACrD,YAAI,GAAG,WAAW,WAAW,KAAK,GAAG,WAAW,WAAW,GAAG;AAC5D,cAAI;AACF,kBAAM,UAAU,KAAK,MAAM,GAAG,aAAa,aAAa,OAAO,CAAC;AAChE,gBAAI,QAAQ,cAAc,QAAQ,SAAS,qBAAqB,QAAQ,SAAS,YAAY;AAC3F,qBAAO;YACT;UACF,QAAQ;UAER;QACF;MACF;AACA,aAAO;IACT;;;;;;;;;;ACKA,QAAaC,sBAAb,MAA+B;MAUV;MACA;MAVF,eAAe,oBAAI,IAAG;;MAGtB,kBAAkB,oBAAI,IAAG;;MAGzB,0BAA0B,oBAAI,IAAG;MAElD,YACmB,QACA,cAAwD;AADxD,aAAA,SAAA;AACA,aAAA,eAAA;MAChB;;;;;MAMH,kBAAkB,aAAkC;AAClD,YAAI,KAAK,aAAa,IAAI,YAAY,IAAI,GAAG;AAC3C,eAAK,OAAO,MAAM,eAAe,YAAY,IAAI,8BAA8B;AAC/E;QACF;AAEA,aAAK,aAAa,IAAI,YAAY,MAAM;UACtC;UACA,WAAW,oBAAI,IAAG;UAClB,eAAe;UACf,gBAAgB;UAChB,kBAAkB,CAAA;UAClB,WAAW,oBAAI,IAAG;SACnB;AAED,aAAK,OAAO,MAAM,wBAAwB,YAAY,IAAI,UAAU,YAAY,IAAI,GAAG;MACzF;;;;;;MAOA,iBAAiB,YAAoB,SAAiB,UAAiB;AACrE,cAAM,QAAQ,KAAK,aAAa,IAAI,UAAU;AAC9C,YAAI,CAAC,OAAO;AACV,eAAK,OAAO,KAAK,uDAAuD,UAAU,GAAG;AACrF;QACF;AAEA,cAAM,UAAU,IAAI,SAAS,QAAQ;AACrC,aAAK,OAAO,KAAK,wBAAwB,OAAO,WAAM,UAAU,EAAE;AAElE,YAAI,MAAM,YAAY,SAAS,aAAa;AAC1C,gBAAM,aAAa,KAAK,aAAa,UAAU;AAE/C,cAAI,eAAe,SAAS;AAE1B,iBAAK,kBAAkB,OAAO,SAAS,QAAQ;UACjD,WAAW,eAAe,UAAa,MAAM,kBAAkB,MAAM;AAEnE,iBAAK,kBAAkB,OAAO,SAAS,QAAQ;UACjD;QACF,OAAO;AAEL,gBAAM,iBAAiB,KAAK,EAAE,SAAS,SAAQ,CAAE;AACjD,qBAAW,YAAY,MAAM,WAAW;AACtC,gBAAI,SAAS,SAAS;AACpB,kBAAI;AACF,yBAAS,QAAQ,QAAQ;cAC3B,SAAS,OAAgB;AACvB,sBAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,qBAAK,OAAO,MAAM,+BAA+B,UAAU,KAAK,GAAG,EAAE;cACvE;YACF;UACF;QACF;MACF;;;;MAKA,mBAAmB,YAAoB,SAAe;AACpD,cAAM,QAAQ,KAAK,aAAa,IAAI,UAAU;AAC9C,YAAI,CAAC;AAAO;AAEZ,cAAM,WAAW,MAAM,UAAU,IAAI,OAAO;AAC5C,cAAM,UAAU,OAAO,OAAO;AAE9B,YAAI,MAAM,YAAY,SAAS,aAAa;AAC1C,cAAI,MAAM,kBAAkB,SAAS;AACnC,kBAAM,gBAAgB;AACtB,kBAAM,iBAAiB;AACvB,iBAAK,OAAO,KAAK,0BAA0B,UAAU,SAAS,OAAO,GAAG;UAC1E;QACF,OAAO;AAEL,gBAAM,MAAM,MAAM,iBAAiB,UAAU,CAAC,MAAM,EAAE,YAAY,OAAO;AACzE,cAAI,QAAQ,IAAI;AACd,kBAAM,iBAAiB,OAAO,KAAK,CAAC;AACpC,uBAAW,YAAY,MAAM,WAAW;AACtC,kBAAI,SAAS,aAAa,aAAa,QAAW;AAChD,oBAAI;AACF,2BAAS,UAAU,QAAQ;gBAC7B,SAAS,OAAgB;AACvB,wBAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,uBAAK,OAAO,MAAM,iCAAiC,UAAU,KAAK,GAAG,EAAE;gBACzE;cACF;YACF;UACF;QACF;MACF;;;;;;MAOA,iBAA8B,cAA+C;AAC3E,cAAM,QAAQ,KAAK,aAAa,IAAI,aAAa,UAAU;AAC3D,YAAI,CAAC,OAAO;AAEV,eAAK,OAAO,MAAM,kDAAkD,aAAa,UAAU,yBAAoB;AAC/G,eAAK,kBAAkB,EAAE,MAAM,aAAa,YAAY,MAAM,YAAW,CAAE;AAC3E,iBAAO,KAAK,iBAAiB,YAAY;QAC3C;AAEA,cAAM,aAAa;AACnB,cAAM,UAAU,IAAI,UAAU;AAG9B,YAAI,MAAM,YAAY,SAAS,aAAa;AAC1C,cAAI,MAAM,mBAAmB,QAAQ,aAAa,OAAO;AACvD,gBAAI;AACF,2BAAa,MAAM,MAAM,cAAmB;YAC9C,SAAS,OAAgB;AACvB,oBAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,mBAAK,OAAO,MAAM,yCAAyC,aAAa,UAAU,KAAK,GAAG,EAAE;YAC9F;UACF;QACF,OAAO;AAEL,cAAI,aAAa,SAAS;AACxB,uBAAW,SAAS,MAAM,kBAAkB;AAC1C,kBAAI;AACF,6BAAa,QAAQ,MAAM,QAAa;cAC1C,SAAS,OAAgB;AACvB,sBAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,qBAAK,OAAO,MAAM,2CAA2C,aAAa,UAAU,KAAK,GAAG,EAAE;cAChG;YACF;UACF;QACF;AAEA,eAAO,MAAK;AACV,gBAAM,UAAU,OAAO,UAAU;QACnC;MACF;;;;;MAMA,aAA0B,YAAkB;AAC1C,cAAM,QAAQ,KAAK,aAAa,IAAI,UAAU;AAC9C,YAAI,CAAC,SAAS,MAAM,YAAY,SAAS;AAAa,iBAAO;AAC7D,eAAQ,MAAM,kBAAwB;MACxC;;;;MAKA,cAA2B,YAAkB;AAC3C,cAAM,QAAQ,KAAK,aAAa,IAAI,UAAU;AAC9C,YAAI,CAAC,SAAS,MAAM,YAAY,SAAS;AAAc,iBAAO,CAAA;AAC9D,eAAO,MAAM,iBAAiB,IAAI,CAAC,MAAM,EAAE,QAAa;MAC1D;;;;;;MAOA,MAAM,mBAAmB,YAAoB,SAAiB,YAAY,OAAK;AAC7E,cAAM,QAAQ,KAAK,aAAa,IAAI,UAAU;AAC9C,YAAI,CAAC,OAAO;AACV,gBAAM,IAAI,MAAM,uBAAuB,UAAU,EAAE;QACrD;AACA,YAAI,MAAM,YAAY,SAAS,aAAa;AAC1C,gBAAM,IAAI,MAAM,eAAe,UAAU,sBAAsB;QACjE;AAEA,cAAM,WAAW,MAAM,UAAU,IAAI,OAAO;AAC5C,YAAI,CAAC,UAAU;AACb,gBAAM,IAAI,MAAM,gBAAgB,OAAO,gCAAgC,UAAU,GAAG;QACtF;AAEA,YAAI,WAAW;AACb,gBAAM,KAAK,uBAAuB,OAAO,SAAS,QAAQ;QAC5D;AAEA,aAAK,OAAO,KAAK,6BAA6B,UAAU,WAAM,OAAO,EAAE;MACzE;;;;MAKA,QAAQ,YAAkB;AACxB,eAAO,KAAK,aAAa,IAAI,UAAU,GAAG,YAAY;MACxD;;;;MAKA,mBAAgB;AACd,cAAM,SAA2B,CAAA;AACjC,mBAAW,CAAC,MAAM,KAAK,KAAK,KAAK,cAAc;AAC7C,iBAAO,KAAK;YACV;YACA,MAAM,MAAM,YAAY;YACxB,WAAW,CAAC,GAAG,MAAM,UAAU,KAAI,CAAE;YACrC,gBAAgB,MAAM;WACvB;QACH;AACA,eAAO;MACT;;;;MAKA,mBAAmB,aAAkC;AACnD,YAAI,CAAC,YAAY,WAAW;AAAQ,iBAAO;AAC3C,eAAO,YAAY,UAAU,MAAM,CAAC,QAAO;AACzC,gBAAM,QAAQ,KAAK,aAAa,IAAI,GAAG;AACvC,cAAI,CAAC;AAAO,mBAAO;AACnB,cAAI,MAAM,YAAY,SAAS,aAAa;AAC1C,mBAAO,MAAM,mBAAmB;UAClC;AACA,iBAAO,MAAM,iBAAiB,SAAS;QACzC,CAAC;MACH;;;;;;MAOA,eAAY;AACV,cAAM,UAAU,oBAAI,IAAG;AACvB,cAAM,WAAW,oBAAI,IAAG;AACxB,cAAM,QAAkB,CAAA;AAExB,cAAM,QAAQ,CAAC,SAAsB;AACnC,cAAI,QAAQ,IAAI,IAAI;AAAG;AACvB,cAAI,SAAS,IAAI,IAAI,GAAG;AACtB,kBAAM,IAAI,MAAM,sDAAsD,IAAI,GAAG;UAC/E;AAEA,mBAAS,IAAI,IAAI;AACjB,gBAAM,QAAQ,KAAK,aAAa,IAAI,IAAI;AACxC,cAAI,OAAO,YAAY,WAAW;AAChC,uBAAW,OAAO,MAAM,YAAY,WAAW;AAC7C,oBAAM,GAAG;YACX;UACF;AACA,mBAAS,OAAO,IAAI;AACpB,kBAAQ,IAAI,IAAI;AAChB,gBAAM,KAAK,IAAI;QACjB;AAEA,mBAAW,QAAQ,KAAK,aAAa,KAAI,GAAI;AAC3C,gBAAM,IAAI;QACZ;AAEA,eAAO;MACT;;;;;;;MASA,kBAAkB,UAAkB,YAAoB,SAAe;AACrE,cAAM,QAAQ,KAAK,aAAa,IAAI,UAAU;AAC9C,YAAI,CAAC,OAAO;AACV,eAAK,OAAO,KAAK,yDAAyD,UAAU,GAAG;AACvF;QACF;AACA,YAAI,CAAC,MAAM,UAAU,IAAI,OAAO,GAAG;AACjC,eAAK,OAAO,KAAK,sCAAsC,OAAO,yBAAyB,UAAU,GAAG;AACpG;QACF;AAEA,YAAI,YAAY,KAAK,gBAAgB,IAAI,QAAQ;AACjD,YAAI,CAAC,WAAW;AACd,sBAAY,oBAAI,IAAG;AACnB,eAAK,gBAAgB,IAAI,UAAU,SAAS;QAC9C;AACA,kBAAU,IAAI,YAAY,OAAO;AACjC,aAAK,OAAO,KAAK,wBAAwB,QAAQ,WAAM,UAAU,MAAM,OAAO,EAAE;MAClF;;;;MAKA,oBAAoB,UAAkB,YAAkB;AACtD,cAAM,YAAY,KAAK,gBAAgB,IAAI,QAAQ;AACnD,YAAI,CAAC;AAAW;AAChB,kBAAU,OAAO,UAAU;AAC3B,YAAI,UAAU,SAAS,GAAG;AACxB,eAAK,gBAAgB,OAAO,QAAQ;QACtC;AACA,aAAK,OAAO,KAAK,4BAA4B,QAAQ,WAAM,UAAU,EAAE;MACzE;;;;;MAMA,mBAAmB,UAAgB;AACjC,eAAO,IAAI,IAAI,KAAK,gBAAgB,IAAI,QAAQ,KAAK,CAAA,CAAE;MACzD;;;;;;MAOA,iBAA8B,YAAoB,UAAgB;AAChE,cAAM,QAAQ,KAAK,aAAa,IAAI,UAAU;AAC9C,YAAI,CAAC,SAAS,MAAM,YAAY,SAAS;AAAa,iBAAO;AAG7D,cAAM,YAAY,KAAK,gBAAgB,IAAI,QAAQ;AACnD,YAAI,WAAW;AACb,gBAAM,kBAAkB,UAAU,IAAI,UAAU;AAChD,cAAI,iBAAiB;AACnB,kBAAM,WAAW,MAAM,UAAU,IAAI,eAAe;AACpD,gBAAI;AAAU,qBAAO;AAErB,iBAAK,OAAO,KACV,uBAAuB,QAAQ,IAAI,UAAU,mCAAmC,eAAe,iCAA4B;UAE/H;QACF;AAGA,eAAQ,MAAM,kBAAwB;MACxC;;;;;;MAOA,0BAA0B,UAAkB,YAAoB,UAAkB;AAChF,cAAM,QAAQ,KAAK,aAAa,IAAI,UAAU;AAC9C,YAAI,CAAC,OAAO;AACV,eAAK,OAAO,KAAK,kEAAkE,UAAU,GAAG;AAChG;QACF;AAEA,YAAI,YAAY,KAAK,wBAAwB,IAAI,QAAQ;AACzD,YAAI,CAAC,WAAW;AACd,sBAAY,oBAAI,IAAG;AACnB,eAAK,wBAAwB,IAAI,UAAU,SAAS;QACtD;AACA,kBAAU,IAAI,YAAY,CAAC,GAAG,QAAQ,CAAC;AACvC,aAAK,OAAO,KAAK,iCAAiC,QAAQ,WAAM,UAAU,OAAO,SAAS,KAAK,IAAI,CAAC,GAAG;MACzG;;;;MAKA,4BAA4B,UAAkB,YAAkB;AAC9D,cAAM,YAAY,KAAK,wBAAwB,IAAI,QAAQ;AAC3D,YAAI,CAAC;AAAW;AAChB,kBAAU,OAAO,UAAU;AAC3B,YAAI,UAAU,SAAS,GAAG;AACxB,eAAK,wBAAwB,OAAO,QAAQ;QAC9C;AACA,aAAK,OAAO,KAAK,qCAAqC,QAAQ,WAAM,UAAU,EAAE;MAClF;;;;;;MAOA,2BAAwC,YAAoB,UAAgB;AAC1E,cAAM,QAAQ,KAAK,aAAa,IAAI,UAAU;AAC9C,YAAI,CAAC,SAAS,MAAM,YAAY,SAAS;AAAc,iBAAO,CAAA;AAG9D,cAAM,YAAY,KAAK,wBAAwB,IAAI,QAAQ;AAC3D,YAAI,WAAW;AACb,gBAAM,iBAAiB,UAAU,IAAI,UAAU;AAC/C,cAAI,gBAAgB;AAClB,kBAAM,YAAY,IAAI,IAAI,cAAc;AACxC,mBAAO,MAAM,iBACV,OAAO,CAAC,MAAM,UAAU,IAAI,EAAE,OAAO,CAAC,EACtC,IAAI,CAAC,MAAM,EAAE,QAAa;UAC/B;QACF;AAGA,eAAO,MAAM,iBAAiB,IAAI,CAAC,MAAM,EAAE,QAAa;MAC1D;;;;;MAMA,qBAAkC,YAAoB,SAAe;AACnE,cAAM,QAAQ,KAAK,aAAa,IAAI,UAAU;AAC9C,YAAI,CAAC;AAAO,iBAAO;AACnB,cAAM,WAAW,MAAM,UAAU,IAAI,OAAO;AAC5C,eAAQ,YAAkB;MAC5B;MAEQ,kBAAkB,OAAwB,SAAiB,UAAiB;AAClF,cAAM,gBAAgB;AACtB,cAAM,iBAAiB;AACvB,aAAK,OAAO,KAAK,wBAAwB,MAAM,YAAY,IAAI,WAAM,OAAO,EAAE;AAE9E,mBAAW,YAAY,MAAM,WAAW;AACtC,cAAI,SAAS,OAAO;AAClB,gBAAI;AACF,uBAAS,MAAM,QAAQ;YACzB,SAAS,OAAgB;AACvB,oBAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,mBAAK,OAAO,MAAM,6BAA6B,MAAM,YAAY,IAAI,KAAK,GAAG,EAAE;YACjF;UACF;QACF;MACF;MAEQ,MAAM,uBAAuB,OAAwB,SAAiB,UAAiB;AAC7F,cAAM,gBAAgB;AACtB,cAAM,iBAAiB;AACvB,aAAK,OAAO,KAAK,gCAAgC,MAAM,YAAY,IAAI,WAAM,OAAO,EAAE;AAEtF,mBAAW,YAAY,MAAM,WAAW;AACtC,cAAI,SAAS,OAAO;AAClB,gBAAI;AACF,oBAAM,SAAS,MAAM,QAAQ;YAC/B,SAAS,OAAgB;AACvB,oBAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,mBAAK,OAAO,MAAM,qCAAqC,MAAM,YAAY,IAAI,KAAK,GAAG,EAAE;YACzF;UACF;QACF;MACF;;AArcF,YAAA,qBAAAA;;;;;;;;;;ACZA,YAAA,oBAAAC;AATa,YAAA,qBAAiD;MAC5D,EAAE,MAAM,WAAW,UAAU,KAAI;MACjC,EAAE,MAAM,kBAAkB,UAAU,KAAI;MACxC,EAAE,MAAM,mBAAmB,UAAU,MAAK;;AAG5C,QAAM,aAAa,IAAI,IAAI,QAAA,mBAAmB,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAGhE,aAAgBA,mBAAkB,MAAY;AAC5C,aAAO,WAAW,IAAI,IAAI;IAC5B;;;;;;;;;;ACxBA,QAAA,QAAA,UAAA,KAAA;AAGa,YAAA,oBAAoB;AAIpB,YAAA,kBAAkB,MAAA,EAAE,OAAO;;MAEtC,MAAM,MAAA,EAAE,KAAK,CAAC,OAAO,OAAO,CAAC,EAAE,QAAQ,KAAK;MAC5C,QAAQ,MAAA,EAAE,OAAO;QACf,MAAM,MAAA,EAAE,OAAM,EAAG,QAAQ,IAAI;QAC7B,MAAM,MAAA,EAAE,OAAM,EAAG,QAAQ,SAAS;QAClC,UAAU,MAAA,EAAE,OAAM,EAAG,QAAQ,QAAA,iBAAiB;OAC/C,EAAE,QAAQ,CAAA,CAAE;MACb,MAAM,MAAA,EAAE,OAAO;QACb,WAAW,MAAA,EAAE,OAAM,EAAG,SAAQ,EAAG,QAAQ,IAAI;QAC7C,eAAe,MAAA,EAAE,OAAM,EAAG,QAAQ,OAAO;QACzC,eAAe,MAAA,EAAE,OAAM,EAAG,QAAQ,QAAQ,IAAI,kBAAkB,UAAU;OAC3E,EAAE,QAAQ,CAAA,CAAE;;MAEb,KAAK,MAAA,EAAE,OAAO;QACZ,KAAK,MAAA,EAAE,OAAM,EAAG,QAAQ,2BAA2B;QACnD,OAAO,MAAA,EAAE,OAAM,EAAG,QAAQ,EAAE;OAC7B,EAAE,QAAQ,CAAA,CAAE;;MAEb,OAAO,MAAA,EAAE,OAAO;QACd,MAAM,MAAA,EAAE,OAAM,EAAG,QAAQ,EAAE;;QAE3B,YAAY,MAAA,EAAE,OAAM,EAAG,QAAQ,IAAI;OACpC,EAAE,QAAQ,CAAA,CAAE;;MAEb,KAAK,MAAA,EAAE,OAAO;;QAEZ,SAAS,MAAA,EAAE,QAAO,EAAG,QAAQ,IAAI;;QAEjC,UAAU,MAAA,EAAE,OAAM,EAAG,SAAQ;;QAE7B,SAAS,MAAA,EAAE,OAAM,EAAG,SAAQ;OAC7B,EAAE,QAAQ,CAAA,CAAE;KACd;AAQY,YAAA,mBAA4C;MACvD,sBAAsB;MACtB,0BAA0B;MAC1B,4BAA4B;MAC5B,yBAAyB;MACzB,yBAAyB;MACzB,sBAAsB;MACtB,uBAAuB;MACvB,mBAAmB;MACnB,iBAAiB;MACjB,iCAAiC;MACjC,6BAA6B;MAC7B,iBAAiB;MACjB,yBAAyB;MACzB,2BAA2B;MAC3B,oBAAoB;MACpB,qBAAqB;QACnB,MAAM;QACN,OAAO;QACP,YAAY;QACZ,OAAO;QACP,MAAM;QACN,QAAQ;;MAEV,aAAa,CAAA;;MAEb,oCAAoC;MACpC,kCAAkC;;;MAGlC,qBAAqB;MACrB,kBAAkB;MAClB,sBAAsB;;MAEtB,8BAA8B;MAC9B,iCAAiC;MACjC,oCAAoC;MACpC,wCAAwC;MACxC,iCAAiC;MACjC,4BAA4B;MAC5B,iCAAiC;;;MAGjC,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC3FtB,QAAA,KAAA,aAAA,UAAA,IAAA,CAAA;AACA,QAAA,OAAA,aAAA,UAAA,SAAA,CAAA;AACA,QAAA,qBAAA;AA+BA,QAAM,cAAsC;MAC1C,eAAe;MACf,eAAe;MACf,eAAe;MACf,qBAAqB;MACrB,qBAAqB;MACrB,qBAAqB;;AAGvB,QAAaC,iBAAb,MAAa,eAAa;MAKK;;MAHrB;MACA,gBAAuC;MAE/C,YAA6B,YAAkB;AAAlB,aAAA,aAAA;AAC3B,cAAM,UAAU,KAAK,SAAQ;AAC7B,cAAM,SAAS,KAAK,kBAAkB,OAAkC;AACxE,aAAK,kBAAkB,mBAAA,gBAAgB,MAAM,MAAM;AACnD,aAAK,uBAAsB;MAC7B;;MAGA,iBAAiB,OAAqB;AACpC,aAAK,gBAAgB;MACvB;;;;;MAMA,IAAO,MAAY;AAEjB,cAAM,iBAAiB,KAAK,iBAAiB,IAAI;AACjD,YAAI,mBAAmB,QAAW;AAChC,iBAAO;QACT;AAGA,YAAI,KAAK,kBAAkB,MAAM;AAC/B,gBAAM,WAAW,KAAK,cAAc,UAAU,IAAI;AAClD,cAAI,aAAa,QAAW;AAC1B,mBAAO;UACT;AAEA,gBAAM,YAAY,KAAK,4BAA4B,IAAI;AACvD,cAAI,cAAc,QAAW;AAC3B,mBAAO;UACT;QACF;AAGA,YAAI,QAAQ,mBAAA,kBAAkB;AAC5B,iBAAO,mBAAA,iBAAiB,IAAI;QAC9B;AAGA,cAAM,SAAS,KAAK,uBAAuB,IAAI;AAC/C,YAAI,WAAW,QAAW;AACxB,iBAAO;QACT;AAEA,eAAO;MACT;;;;;MAMA,IAAI,KAAa,OAAc;AAC7B,YAAI,KAAK,kBAAkB,MAAM;AAC/B,gBAAM,IAAI,MAAM,gFAAgF;QAClG;AACA,aAAK,cAAc,UAAU,KAAK,KAAK;MACzC;;;;;MAMA,WAAW,SAAe;AAExB,YAAI,KAAK,kBAAkB,MAAM;AAC/B,gBAAM,SAAS,KAAK,4BAA4B,OAAO;AACvD,cAAI,WAAW;AAAW,mBAAO;QACnC;AAEA,cAAM,iBAAkB,KAAK,gBAA4C,OAAO;AAChF,YAAI,mBAAmB,UAAa,mBAAmB,QAAQ,OAAO,mBAAmB,UAAU;AACjG,iBAAO;QACT;AAEA,eAAQ,KAAK,uBAAuB,OAAO,KAAiC,CAAA;MAC9E;;;;;MAMA,WAAW,SAAiB,MAA6B;AACvD,YAAI,KAAK,kBAAkB,MAAM;AAC/B,gBAAM,IAAI,MAAM,gFAAgF;QAClG;AACA,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,eAAK,cAAc,UAAU,GAAG,OAAO,IAAI,GAAG,IAAI,KAAK;QACzD;MACF;;;;;MAOA,eAAe,SAAe;AAC5B,YAAI,KAAK,kBAAkB,MAAM;AAC/B,iBAAO,KAAK,cAAc,YAAY,OAAO;QAC/C;AAEA,eAAQ,KAAK,iBAAiB,UAAU,OAAO,EAAE,KAAiC,CAAA;MACpF;;MAGA,eAAe,SAAiB,QAA+B;AAC7D,YAAI,KAAK,kBAAkB,MAAM;AAC/B,gBAAM,IAAI,MAAM,gFAAgF;QAClG;AACA,aAAK,cAAc,YAAY,SAAS,MAAM;MAChD;;MAGA,kBAAkB,YAAkB;AAClC,YAAI,KAAK,kBAAkB,MAAM;AAC/B,iBAAO,KAAK,cAAc,eAAe,UAAU;QACrD;AACA,eAAO,CAAA;MACT;;MAGA,kBAAkB,YAAoB,KAAa,OAAc;AAC/D,YAAI,KAAK,kBAAkB,MAAM;AAC/B,gBAAM,IAAI,MAAM,gFAAgF;QAClG;AACA,aAAK,cAAc,YAAY,YAAY,KAAK,KAAK;MACvD;;MAGA,gBAAgB,UAAgB;AAC9B,YAAI,KAAK,kBAAkB,MAAM;AAC/B,iBAAO,KAAK,cAAc,aAAa,QAAQ;QACjD;AACA,eAAO,CAAA;MACT;;MAGA,gBAAgB,UAAkB,KAAa,OAAc;AAC3D,YAAI,KAAK,kBAAkB,MAAM;AAC/B,gBAAM,IAAI,MAAM,gFAAgF;QAClG;AACA,aAAK,cAAc,UAAU,UAAU,KAAK,KAAK;MACnD;;MAGA,aAAgB,MAAY;AAC1B,eAAO,KAAK,iBAAiB,IAAI;MACnC;;MAGA,IAAI,WAAQ;AACV,cAAM,IAAI,CAAC,QACR,KAAK,IAAa,YAAY,GAAG,EAAE,KAAK,mBAAA,iBAAiB,YAAY,GAAG,EAAE;AAC7E,eAAO;UACL,WAAW,EAAE,WAAW;UACxB,eAAe,EAAE,eAAe;UAChC,iBAAiB,EAAE,iBAAiB;UACpC,cAAc,EAAE,cAAc;UAC9B,cAAc,EAAE,cAAc;UAC9B,WAAW,EAAE,WAAW;UACxB,YAAY,EAAE,YAAY;UAC1B,QAAQ,EAAE,QAAQ;UAClB,MAAM,EAAE,MAAM;;MAElB;;;;MAKA,IAAI,MAAG;AACL,cAAM,WAAW;UACf,WAAW,mBAAA,iBAAiB,oBAAoB;UAChD,eAAe,mBAAA,iBAAiB,wBAAwB;UACxD,iBAAiB,mBAAA,iBAAiB,0BAA0B;UAC5D,cAAc,mBAAA,iBAAiB,uBAAuB;UACtD,cAAc,mBAAA,iBAAiB,uBAAuB;UACtD,WAAW,mBAAA,iBAAiB,oBAAoB;UAChD,YAAY,mBAAA,iBAAiB,qBAAqB;UAClD,QAAQ,mBAAA,iBAAiB,iBAAiB;UAC1C,MAAM,mBAAA,iBAAiB,eAAe;;AAExC,eAAO;UACL,GAAG,KAAK;UACR;UACA,SAAS,mBAAA,iBAAiB,mBAAmB,MAAM,SAC/C;YACE,UAAU,mBAAA,iBAAiB,kBAAkB;YAC7C,WAAW,mBAAA,iBAAiB,mBAAmB;cAEjD,EAAE,UAAU,kBAAkB,WAAW,CAAA,EAAE;UAC/C,SAAS;YACP,OAAO,mBAAA,iBAAiB,eAAe;YACvC,eAAe,mBAAA,iBAAiB,uBAAuB;;UAEzD,UAAU;YACR,gBAAgB,mBAAA,iBAAiB,yBAAyB;;UAE5D,WAAW;YACT,qBAAqB,mBAAA,iBAAiB,+BAA+B;YACrE,iBAAiB,mBAAA,iBAAiB,2BAA2B;;UAE/D,WAAW,mBAAA,iBAAiB,WAAW;;MAE3C;;MAGQ,OAAgB,qBAAqB,oBAAI,IAAI,CAAC,UAAU,QAAQ,MAAM,CAAC;;;;;;MAO/E,OAAO,SAAiB,MAA6B;AACnD,YAAI,CAAC,eAAc,mBAAmB,IAAI,OAAO,GAAG;AAClD,gBAAM,IAAI,MACR,4BAA4B,OAAO,yGAAoG;QAE3I;AAGA,YAAI,MAA+B,CAAA;AACnC,YAAI,GAAG,WAAW,KAAK,UAAU,GAAG;AAClC,gBAAO,KAAK,KAAK,GAAG,aAAa,KAAK,YAAY,OAAO,CAAC,KAAiC,CAAA;QAC7F;AAGA,cAAM,WAAY,IAAI,OAAO,KAAiC,CAAA;AAC9D,YAAI,OAAO,IAAI,EAAE,GAAG,UAAU,GAAG,KAAI;AAGrC,cAAM,aAAa,mBAAA,gBAAgB,UAAU,GAAG;AAChD,YAAI,CAAC,WAAW,SAAS;AACvB,gBAAM,IAAI,MAAM,sDAAsD,OAAO,MAAM,WAAW,MAAM,OAAO,EAAE;QAC/G;AAGA,cAAM,UAAU,GAAG,KAAK,UAAU;AAClC,WAAG,cAAc,SAAS,KAAK,KAAK,KAAK,EAAE,WAAW,KAAK,QAAQ,GAAG,aAAa,IAAG,CAAE,GAAG,OAAO;AAClG,WAAG,WAAW,SAAS,KAAK,UAAU;AAGtC,aAAK,kBAAkB,WAAW;MACpC;;;;;MAMQ,UAAU,KAA8B,MAAc,OAAc;AAC1E,cAAM,CAAC,MAAM,GAAG,IAAI,IAAI,KAAK,MAAM,GAAG;AACtC,YAAI,CAAC;AAAM,iBAAO;AAClB,YAAI,KAAK,WAAW,GAAG;AACrB,iBAAO,EAAE,GAAG,KAAK,CAAC,IAAI,GAAG,MAAK;QAChC;AACA,cAAM,QAAS,IAAI,IAAI,KAAK,CAAA;AAC5B,eAAO,EAAE,GAAG,KAAK,CAAC,IAAI,GAAG,KAAK,UAAU,OAAO,KAAK,KAAK,GAAG,GAAG,KAAK,EAAC;MACvE;;;;;MAMQ,kBAAkB,KAA4B;AACpD,YAAI,SAAS,EAAE,GAAG,IAAG;AAErB,mBAAW,CAAC,QAAQ,UAAU,KAAK,OAAO,QAAQ,WAAW,GAAG;AAC9D,gBAAM,WAAW,QAAQ,IAAI,MAAM;AACnC,cAAI,aAAa,UAAa,aAAa;AAAI;AAG/C,gBAAM,UAAmB,eAAe,gBAAgB,OAAO,QAAQ,IAAI;AAC3E,mBAAS,KAAK,UAAU,QAAQ,YAAY,OAAO;AACnD,kBAAQ,IAAI,yCAAyC,MAAM,WAAM,UAAU,EAAE;QAC/E;AAEA,eAAO;MACT;MAEQ,WAAQ;AACd,YAAI,CAAC,GAAG,WAAW,KAAK,UAAU,GAAG;AACnC,kBAAQ,KACN,6CAA6C,KAAK,UAAU;;2EAEa;AAE3E,iBAAO,CAAA;QACT;AAEA,cAAM,UAAU,GAAG,aAAa,KAAK,YAAY,OAAO;AACxD,cAAM,SAAS,KAAK,KAAK,OAAO,KAAK,CAAA;AACrC,gBAAQ,IAAI,uCAAuC,KAAK,UAAU,EAAE;AACpE,eAAO;MACT;MAEQ,yBAAsB;AAC5B,YAAI,KAAK,gBAAgB,KAAK,kBAAkB,YAAY;AAC1D,kBAAQ,KACN,6IAC6E;QAEjF;MACF;MAEQ,iBAAiB,MAAY;AACnC,cAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,YAAI,UAAmB,KAAK;AAE5B,mBAAW,OAAO,MAAM;AACtB,cAAI,YAAY,QAAQ,YAAY,UAAa,OAAO,YAAY,UAAU;AAC5E,mBAAO;UACT;AACA,oBAAW,QAAoC,GAAG;QACpD;AAEA,eAAO;MACT;MAEQ,uBAAuB,MAAY;AAEzC,cAAM,SAAS,OAAO;AACtB,cAAM,SAAkC,CAAA;AACxC,YAAI,QAAQ;AAEZ,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,mBAAA,gBAAgB,GAAG;AAC3D,cAAI,IAAI,WAAW,MAAM,GAAG;AAC1B,kBAAM,SAAS,IAAI,MAAM,OAAO,MAAM;AACtC,mBAAO,MAAM,IAAI;AACjB,oBAAQ;UACV;QACF;AAEA,eAAO,QAAQ,SAAS;MAC1B;;;;;;MAOQ,4BAA4B,MAAY;AAC9C,YAAI,KAAK,kBAAkB;AAAM,iBAAO;AACxC,cAAM,MAAM,KAAK,cAAc,aAAY;AAC3C,cAAM,SAAS,OAAO;AACtB,cAAM,SAAkC,CAAA;AACxC,YAAI,QAAQ;AACZ,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,cAAI,IAAI,WAAW,MAAM,GAAG;AAC1B,mBAAO,IAAI,MAAM,OAAO,MAAM,CAAC,IAAI;AACnC,oBAAQ;UACV;QACF;AACA,eAAO,QAAQ,SAAS;MAC1B;;AAxWF,YAAA,gBAAAA;;;;;;;;;;AC1CA,QAAA,uBAAA,UAAA,eAAA;AAQA,QAAM,kBAAkB,oBAAI,IAAI;MAC9B;MACA;MACA;KACD;AA+BD,QAAaC,mBAAb,MAA4B;MACT,UAAU,oBAAI,IAAG;MACjB;MACT,iBAAwD;;MAG/C,oBAAoB,oBAAI,IAAG;MAE5C,YAAY,SAA0B;AACpC,aAAK,UAAU;UACb,iBAAiB,QAAQ;UACzB,SAAS,QAAQ,WAAW;UAC5B,gBAAgB,QAAQ,kBAAmB,QAAQ,IAAI,6BAA6B;UACpF,qBAAqB,QAAQ,uBAAuB;UACpD,oBAAoB,QAAQ,sBAAsB;UAClD,oBAAoB,QAAQ,sBAAsB;UAClD,eAAe,QAAQ,iBAAiB;UACxC,mBAAmB,QAAQ,qBAAqB;UAChD,aAAa,QAAQ;;MAEzB;;MAGA,aAAa,SAAe;AAC1B,eAAO,gBAAgB,IAAI,OAAO;MACpC;;MAGA,oBAAoB,SAAe;AACjC,eAAO,KAAK,kBAAkB,IAAI,OAAO;MAC3C;;MAGA,sBAAsB,SAAe;AACnC,aAAK,kBAAkB,IAAI,OAAO;MACpC;;;;;;;MAQA,WAAW,SAAiB,aAAyD;AACnF,YAAI,KAAK,QAAQ;AAAgB,iBAAO;AACxC,YAAI,KAAK,QAAQ;AAAS,iBAAO;AACjC,YAAI,KAAK,aAAa,OAAO;AAAG,iBAAO;AACvC,YAAI,KAAK,kBAAkB,IAAI,OAAO;AAAG,iBAAO;AAEhD,YAAI,aAAa,cAAc;AAAM,iBAAO;AAE5C,YAAI,aAAa,aAAa;AAAM,iBAAO;AAC3C,eAAO;MACT;;MAGA,MAAM,WACJ,SACA,UACA,QACA,cACA,SACA,eACA,aAAoB;AAEpB,YAAI,KAAK,QAAQ,IAAI,OAAO,GAAG;AAC7B,gBAAM,IAAI,MAAM,qBAAqB,OAAO,kBAAkB;QAChE;AAIA,cAAM,YAAoC;UACxC,MAAM,QAAQ,IAAI,QAAQ;UAC1B,MAAM,QAAQ,IAAI,QAAQ;UAC1B,UAAU,QAAQ,IAAI,YAAY;UAClC,8BAA8B;;UAC9B,yBAAyB,mBAAmB,QAAQ,IAAI,iBAAiB,MAAM;UAC/E,uBAAuB,eAAe;UACtC,mBAAmB;UACnB,oBAAoB;UACpB,uBAAuB,KAAK,UAAU,MAAM;UAC5C,mBAAmB,WAAW,6BAA6B,OAAO;UAClE,yBAAyB,KAAK,UAAU,iBAAiB,YAAY;;AAGvE,cAAM,cAAuC;UAC3C,OAAO,CAAC,WAAW,WAAW,WAAW,KAAK;UAC9C,UAAU,CAAA;UACV,KAAK;;AAGP,cAAM,SAAQ,GAAA,qBAAA,MAAK,KAAK,QAAQ,iBAAiB,CAAA,GAAI,WAAkB;AAEvE,cAAM,SAAwB;UAC5B;UACA,SAAS;UACT,OAAO;UACP,WAAW,KAAK,IAAG;UACnB,eAAe,KAAK,IAAG;UACvB,cAAc;UACd,iBAAiB,CAAA;UACjB,YAAY;UACZ,WAAW;UACX;;AAGF,aAAK,QAAQ,IAAI,SAAS,MAAM;AAGhC,cAAM,GAAG,WAAW,CAAC,QAA4B;AAC/C,cAAI,IAAI,SAAS,SAAS,KAAK,QAAQ,aAAa;AAClD,iBAAK,QAAQ,YAAY;cACvB;cACA,OAAO,IAAI;cACX,SAAS,IAAI;cACb,OAAO,CAAC,OAAO,OAAO;cACtB,MAAM,IAAI;aACX;UACH,WAAW,IAAI,SAAS,SAAS;AAC/B,mBAAO,aAAa,IAAI;AACxB,mBAAO,YAAY,IAAI;UACzB;QACF,CAAC;AAGD,cAAM,QAAQ,GAAG,QAAQ,CAAC,UAAiB;AACzC,gBAAM,QAAQ,MAAM,SAAQ,EAAG,KAAI;AACnC,cAAI,CAAC;AAAO;AACZ,cAAI,KAAK,QAAQ,aAAa;AAC5B,iBAAK,QAAQ,YAAY;cACvB;cACA,OAAO;cACP,SAAS;cACT,OAAO,CAAC,OAAO,SAAS,UAAU;aACnC;UACH,OAAO;AACL,oBAAQ,IAAI,WAAW,OAAO,KAAK,KAAK,EAAE;UAC5C;QACF,CAAC;AACD,cAAM,QAAQ,GAAG,QAAQ,CAAC,UAAiB;AACzC,gBAAM,QAAQ,MAAM,SAAQ,EAAG,KAAI;AACnC,cAAI,CAAC;AAAO;AACZ,cAAI,KAAK,QAAQ,aAAa;AAC5B,iBAAK,QAAQ,YAAY;cACvB;cACA,OAAO;cACP,SAAS;cACT,OAAO,CAAC,OAAO,SAAS,UAAU;aACnC;UACH,OAAO;AACL,oBAAQ,MAAM,WAAW,OAAO,SAAS,KAAK,EAAE;UAClD;QACF,CAAC;AAED,cAAM,GAAG,QAAQ,CAAC,SAAQ;AACxB,eAAK,iBAAiB,SAAS,MAAM,UAAU,QAAQ,cAAc,SAAS,eAAe,WAAW;QAC1G,CAAC;AAED,cAAM,GAAG,SAAS,CAAC,QAAO;AACxB,kBAAQ,MAAM,uBAAuB,OAAO,WAAW,IAAI,OAAO;QACpE,CAAC;AAID,cAAM,IAAI,QAAc,CAAC,SAAS,WAAU;AAC1C,gBAAM,UAAU,WAAW,MAAK;AAC9B,mBAAO,IAAI,MAAM,UAAU,OAAO,kCAAkC,CAAC;UACvE,GAAG,GAAK;AAER,gBAAM,aAAa,CAAC,QAAyB;AAC3C,gBAAI,IAAI,SAAS,SAAS;AACxB,2BAAa,OAAO;AACpB,oBAAM,IAAI,WAAW,UAAU;AAC/B,qBAAO,QAAQ;AACf,sBAAO;YACT;UACF;AACA,gBAAM,GAAG,WAAW,UAAU;QAChC,CAAC;MACH;;MAGA,cAAW;AACT,eAAO,CAAC,GAAG,KAAK,QAAQ,OAAM,CAAE,EAAE,IAAI,CAAC,OAAO;UAC5C,SAAS,EAAE;UACX,KAAK,EAAE,QAAQ,OAAO;UACtB,OAAO,EAAE;UACT,YAAY,EAAE;UACd,WAAW,EAAE;UACb,eAAe,KAAK,OAAO,KAAK,IAAG,IAAK,EAAE,aAAa,GAAI;UAC3D,cAAc,EAAE;UAChB,cAAc,CAAA;UACd;MACJ;;MAGA,MAAM,mBAAmB,SAAe;AACtC,cAAM,SAAS,KAAK,QAAQ,IAAI,OAAO;AACvC,YAAI,CAAC,QAAQ;AACX,gBAAM,IAAI,MAAM,8BAA8B,OAAO,GAAG;QAC1D;AACA,cAAM,KAAK,eAAe,SAAS,MAAM;AACzC,aAAK,QAAQ,OAAO,OAAO;MAC7B;;MAGA,MAAM,cAAW;AACf,YAAI,KAAK,gBAAgB;AACvB,wBAAc,KAAK,cAAc;AACjC,eAAK,iBAAiB;QACxB;AAEA,cAAM,YAAY,CAAC,GAAG,KAAK,QAAQ,QAAO,CAAE,EAAE,IAAI,CAAC,CAAC,SAAS,MAAM,MACjE,KAAK,eAAe,SAAS,MAAM,CAAC;AAEtC,cAAM,QAAQ,WAAW,SAAS;AAClC,aAAK,QAAQ,MAAK;MACpB;;MAGA,2BAAwB;AAGtB,aAAK,iBAAiB,YAAY,MAAK;AACrC,gBAAM,MAAM,KAAK,IAAG;AACpB,qBAAW,CAAC,SAAS,MAAM,KAAK,KAAK,SAAS;AAC5C,gBAAI,OAAO,UAAU;AAAW;AAEhC,gBAAI,MAAM,OAAO,gBAAgB,KAAK,QAAQ,oBAAoB;AAChE,sBAAQ,KAAK,uBAAuB,OAAO,gDAA2C;AACtF,qBAAO,QAAQ;AACf,qBAAO,QAAQ,KAAK,SAAS;YAC/B;UACF;QACF,GAAG,KAAK,QAAQ,mBAAmB;MACrC;;MAGA,sBAAsB,SAAiB,YAAqB,WAAkB;AAC5E,cAAM,SAAS,KAAK,QAAQ,IAAI,OAAO;AACvC,YAAI,CAAC;AAAQ;AACb,eAAO,gBAAgB,KAAK,IAAG;AAC/B,YAAI,eAAe;AAAW,iBAAO,aAAa;AAClD,YAAI,cAAc;AAAW,iBAAO,YAAY;MAClD;MAEQ,iBACN,SACA,MACA,UACA,QACA,cACA,SACA,eACA,aAAoB;AAEpB,cAAM,SAAS,KAAK,QAAQ,IAAI,OAAO;AACvC,YAAI,CAAC;AAAQ;AAEb,YAAI,OAAO,UAAU,YAAY;AAC/B,iBAAO,QAAQ;AACf;QACF;AAEA,eAAO,QAAQ;AACf,gBAAQ,MAAM,uBAAuB,OAAO,qBAAqB,IAAI,EAAE;AAGvE,cAAM,MAAM,KAAK,IAAG;AACpB,eAAO,gBAAgB,KAAK,GAAG;AAC/B,cAAM,gBAAgB,OAAO,gBAAgB,OAC3C,CAAC,MAAM,MAAM,IAAI,KAAK,QAAQ,aAAa;AAE7C,eAAO,kBAAkB;AAEzB,YAAI,cAAc,UAAU,KAAK,QAAQ,oBAAoB;AAC3D,kBAAQ,MAAM,uBAAuB,OAAO,YAAY,cAAc,MAAM,aAAa,KAAK,QAAQ,aAAa,0BAAqB;AACxI;QACF;AAGA,cAAM,UAAU,KAAK,IAAI,OAAQ,OAAO,eAAe,IAAI,GAAK;AAChE,gBAAQ,IAAI,kCAAkC,OAAO,OAAO,OAAO,IAAI;AACvE,mBAAW,MAAK;AACd,iBAAO;AACP,eAAK,QAAQ,OAAO,OAAO;AAC3B,eAAK,WAAW,SAAS,UAAU,QAAQ,cAAc,SAAS,eAAe,WAAW,EAAE,MAAM,CAAC,QAAO;AAC1G,oBAAQ,MAAM,yCAAyC,OAAO,KAAK,GAAG;UACxE,CAAC;QACH,GAAG,OAAO;MACZ;MAEQ,MAAM,eAAe,SAAiB,QAAqB;AACjE,eAAO,QAAQ;AAEf,eAAO,QAAQ,KAAK,SAAS;AAE7B,cAAM,IAAI,QAAc,CAAC,YAAW;AAClC,gBAAM,UAAU,WAAW,MAAK;AAC9B,oBAAQ,KAAK,uBAAuB,OAAO,wBAAwB,KAAK,QAAQ,iBAAiB,mBAAc;AAC/G,mBAAO,QAAQ,KAAK,SAAS;AAC7B,oBAAO;UACT,GAAG,KAAK,QAAQ,iBAAiB;AAEjC,iBAAO,QAAQ,GAAG,QAAQ,MAAK;AAC7B,yBAAa,OAAO;AACpB,oBAAO;UACT,CAAC;QACH,CAAC;MACH;;AArTF,YAAA,kBAAAA;;;;;ACzCA,0BAA4B;AAC5B,kCAAmC;AAEnC,6BAA+B;AAG/B,sBASO;AAGP,8BAA2C;AAG3C,iCAAmC;AACnC,gCAAsD;AAItD,4BAA8B;AAE9B,2BAAqE;AAIrE,+BAAwD;AACxD,oCAAqC;","names":["AddonLoader","AddonEngineManager","detectWorkspacePackagesDir","CapabilityRegistry","isInfraCapability","ConfigManager","AddonWorkerHost"]}
|
|
1
|
+
{"version":3,"sources":["../src/addon-loader.ts","../src/addon-engine-manager.ts","../src/workspace-detect.ts","../src/capability-registry.ts","../src/infra-capabilities.ts","../src/config-manager.ts","../src/config-schema.ts","../src/worker/addon-worker-host.ts"],"sourcesContent":["import type { AddonDeclaration, AddonPackageManifest, ICamstackAddon } from '@camstack/types'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\n\nexport interface RegisteredAddon {\n readonly declaration: AddonDeclaration\n readonly packageName: string\n readonly packageVersion: string\n /** Human-readable package name from camstack.displayName in package.json */\n readonly packageDisplayName?: string\n readonly addonClass: new () => ICamstackAddon\n}\n\n/**\n * Resolve the addon class from a dynamically imported module.\n *\n * CJS modules built with tsup can produce double-wrapped default exports:\n * mod.default = { default: ActualClass }\n * This helper unwraps that pattern so we always get the constructor.\n */\nfunction resolveAddonClass(mod: Record<string, unknown>): (new () => ICamstackAddon) | undefined {\n let candidate = mod['default'] ?? mod[Object.keys(mod)[0]!]\n\n // Unwrap double-nested default (CJS interop with tsup)\n if (candidate && typeof candidate === 'object' && 'default' in (candidate as Record<string, unknown>)) {\n candidate = (candidate as Record<string, unknown>)['default']\n }\n\n // CJS module imported via ESM: default is an object with named exports\n // Find the first function (class) in the object\n if (candidate && typeof candidate === 'object') {\n const obj = candidate as Record<string, unknown>\n const fn = Object.values(obj).find(v => typeof v === 'function')\n if (fn) candidate = fn\n }\n\n // Fallback: try named exports directly (skip non-function 'default')\n if (typeof candidate !== 'function') {\n candidate = Object.values(mod).find(v => typeof v === 'function')\n }\n\n return typeof candidate === 'function'\n ? (candidate as new () => ICamstackAddon)\n : undefined\n}\n\nexport class AddonLoader {\n private addons = new Map<string, RegisteredAddon>()\n\n /** Scan addons directory and load all addon packages.\n * Supports scoped layout: addons/@scope/package-name/ */\n async loadFromDirectory(addonsDir: string): Promise<void> {\n if (!fs.existsSync(addonsDir)) return\n\n const entries = fs.readdirSync(addonsDir, { withFileTypes: true })\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n\n // Scoped directory (e.g., @camstack/) — scan one level deeper\n if (entry.name.startsWith('@')) {\n const scopeDir = path.join(addonsDir, entry.name)\n const scopeEntries = fs.readdirSync(scopeDir, { withFileTypes: true })\n for (const scopeEntry of scopeEntries) {\n if (!scopeEntry.isDirectory()) continue\n await this.tryLoadAddon(path.join(scopeDir, scopeEntry.name))\n }\n continue\n }\n\n // Non-scoped (legacy or flat layout)\n await this.tryLoadAddon(path.join(addonsDir, entry.name))\n }\n }\n\n private async tryLoadAddon(addonDir: string): Promise<void> {\n const pkgJsonPath = path.join(addonDir, 'package.json')\n if (!fs.existsSync(pkgJsonPath)) return\n try {\n await this.loadFromAddonDir(addonDir)\n } catch (err) {\n console.warn(`Failed to load addon from ${addonDir}: ${err}`)\n }\n }\n\n /** Load addon from a specific directory (package.json + dist/) */\n async loadFromAddonDir(addonDir: string): Promise<void> {\n const pkgJsonPath = path.join(addonDir, 'package.json')\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8')) as Record<string, unknown>\n const packageName = pkgJson['name'] as string\n const packageVersion = (pkgJson['version'] as string) ?? '0.0.0'\n const manifest = pkgJson['camstack'] as AddonPackageManifest | undefined\n\n if (!manifest?.addons?.length) return\n\n for (const declaration of manifest.addons) {\n // Resolve entry: try dist/ first\n const entryFile = declaration.entry\n .replace(/^\\.\\//, '')\n .replace(/^src\\//, 'dist/')\n .replace(/\\.ts$/, '.js')\n\n let entryPath = path.resolve(addonDir, entryFile)\n\n // Fallback: try other locations and extensions\n if (!fs.existsSync(entryPath)) {\n const base = entryPath.replace(/\\.(js|cjs|mjs)$/, '')\n const alternatives = [\n `${base}.cjs`,\n `${base}.mjs`,\n path.resolve(addonDir, 'dist', 'index.js'),\n path.resolve(addonDir, 'dist', 'index.cjs'),\n path.resolve(addonDir, 'dist', 'index.mjs'),\n path.resolve(addonDir, declaration.entry),\n ]\n entryPath = alternatives.find(p => fs.existsSync(p)) ?? entryPath\n }\n\n if (!fs.existsSync(entryPath)) {\n throw new Error(`Entry not found: ${entryPath}`)\n }\n\n const mod = (await import(entryPath)) as Record<string, unknown>\n const AddonClass = resolveAddonClass(mod)\n\n if (!AddonClass) {\n throw new Error(`No addon class in ${entryPath}`)\n }\n\n this.addons.set(declaration.id, {\n declaration,\n packageName,\n packageVersion,\n packageDisplayName: manifest.displayName,\n addonClass: AddonClass,\n })\n }\n }\n\n /** Load addon from a direct path (for development/testing) */\n async loadFromPath(\n addonId: string,\n modulePath: string,\n packageName: string,\n declaration?: Partial<AddonDeclaration>,\n packageVersion = '0.0.0',\n ): Promise<void> {\n const mod = (await import(modulePath)) as Record<string, unknown>\n const AddonClass = resolveAddonClass(mod)\n\n if (!AddonClass) {\n throw new Error(`Module ${modulePath} has no default export`)\n }\n\n this.addons.set(addonId, {\n declaration: {\n id: addonId,\n entry: modulePath,\n slot: 'detector' as AddonDeclaration['slot'],\n ...declaration,\n },\n packageName,\n packageVersion,\n addonClass: AddonClass,\n })\n }\n\n /** Get a registered addon by ID */\n getAddon(addonId: string): RegisteredAddon | undefined {\n return this.addons.get(addonId)\n }\n\n /** List all registered addons */\n listAddons(): RegisteredAddon[] {\n return [...this.addons.values()]\n }\n\n /** Check if an addon is registered */\n hasAddon(addonId: string): boolean {\n return this.addons.has(addonId)\n }\n\n /** Create a new instance of an addon (not yet initialized) */\n createInstance(addonId: string): ICamstackAddon {\n const registered = this.addons.get(addonId)\n if (!registered) {\n throw new Error(`Addon \"${addonId}\" is not registered`)\n }\n return new registered.addonClass()\n }\n}\n","import type { ICamstackAddon, AddonContext } from '@camstack/types'\nimport { AddonLoader } from './addon-loader.js'\nimport { createHash } from 'node:crypto'\n\nexport class AddonEngineManager {\n private engines = new Map<string, ICamstackAddon>()\n\n constructor(\n private readonly loader: AddonLoader,\n private readonly baseContext: Partial<Omit<AddonContext, 'addonConfig'>>,\n ) {}\n\n /**\n * Get or create an addon engine for the given effective config.\n * Cameras with the same addonId + effective config share the same engine.\n */\n async getOrCreateEngine(\n addonId: string,\n globalConfig: Record<string, unknown>,\n cameraOverride?: Record<string, unknown>,\n ): Promise<ICamstackAddon> {\n // Merge global + override\n const effectiveConfig = { ...globalConfig, ...cameraOverride }\n const configKey = `${addonId}:${this.hashConfig(effectiveConfig)}`\n\n const existing = this.engines.get(configKey)\n if (existing) return existing\n\n // Create new instance\n const addon = this.loader.createInstance(addonId)\n await addon.initialize({ ...this.baseContext, addonConfig: effectiveConfig } as AddonContext)\n this.engines.set(configKey, addon)\n return addon\n }\n\n /** Get all active engines */\n getActiveEngines(): Map<string, ICamstackAddon> {\n return new Map(this.engines)\n }\n\n /** Shutdown a specific engine by its config key */\n async shutdownEngine(configKey: string): Promise<void> {\n const engine = this.engines.get(configKey)\n if (engine) {\n await engine.shutdown()\n this.engines.delete(configKey)\n }\n }\n\n /** Shutdown all engines */\n async shutdownAll(): Promise<void> {\n for (const [, engine] of this.engines) {\n await engine.shutdown()\n }\n this.engines.clear()\n }\n\n /** Compute a deterministic config key (visible for tests) */\n computeConfigKey(addonId: string, effectiveConfig: Record<string, unknown>): string {\n return `${addonId}:${this.hashConfig(effectiveConfig)}`\n }\n\n private hashConfig(config: Record<string, unknown>): string {\n const sorted = JSON.stringify(config, Object.keys(config).sort())\n return createHash('md5').update(sorted).digest('hex').slice(0, 12)\n }\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\n\n/**\n * Detect whether we're running inside the monorepo workspace.\n * Returns the absolute path to the packages/ directory if found, otherwise null.\n *\n * Single source of truth — replaces identical implementations in:\n * - first-boot-installer.ts\n * - addon-bridge.service.ts\n */\nexport function detectWorkspacePackagesDir(startDir: string): string | null {\n let current = path.resolve(startDir)\n for (let i = 0; i < 6; i++) {\n current = path.dirname(current)\n const packagesDir = path.join(current, 'packages')\n const rootPkgJson = path.join(current, 'package.json')\n if (fs.existsSync(packagesDir) && fs.existsSync(rootPkgJson)) {\n try {\n const rootPkg = JSON.parse(fs.readFileSync(rootPkgJson, 'utf-8')) as Record<string, unknown>\n if (rootPkg.workspaces || rootPkg.name === 'camstack-server' || rootPkg.name === 'camstack') {\n return packagesDir\n }\n } catch {\n // Ignore parse errors\n }\n }\n }\n return null\n}\n","// packages/kernel/src/capability-registry.ts\nimport type {\n CapabilityMode,\n CapabilityDeclaration,\n CapabilityConsumerRegistration,\n CapabilityInfo,\n IScopedLogger,\n} from '@camstack/types'\n\ninterface ProviderEntry {\n readonly addonId: string\n readonly provider: unknown\n}\n\ninterface CapabilityState {\n readonly declaration: CapabilityDeclaration\n /** All registered providers (addon ID → provider) */\n readonly available: Map<string, unknown>\n /** For singleton: the currently active addon ID */\n activeAddonId: string | null\n /** For singleton: the currently active provider */\n activeProvider: unknown | null\n /** For collection: all active providers in registration order */\n readonly activeCollection: ProviderEntry[]\n /** Registered consumers */\n readonly consumers: Set<CapabilityConsumerRegistration>\n}\n\n/**\n * Central registry for all capability providers and consumers.\n * Lives in @camstack/kernel. Mode-aware: singleton (one active) or collection (all active).\n *\n * Uses injected LoggingService, NEVER NestJS static Logger.\n */\nexport class CapabilityRegistry {\n private readonly capabilities = new Map<string, CapabilityState>()\n\n /** Per-device singleton overrides: deviceId → (capability → addonId) */\n private readonly deviceOverrides = new Map<string, Map<string, string>>()\n\n /** Per-device collection filters: deviceId → (capability → addonIds[]) */\n private readonly deviceCollectionFilters = new Map<string, Map<string, string[]>>()\n\n constructor(\n private readonly logger: IScopedLogger,\n private readonly configReader: (capability: string) => string | undefined,\n ) {}\n\n /**\n * Declare a capability (typically called when addon manifests are loaded).\n * Must be called before registerProvider/registerConsumer for that capability.\n */\n declareCapability(declaration: CapabilityDeclaration): void {\n if (this.capabilities.has(declaration.name)) {\n this.logger.debug(`Capability \"${declaration.name}\" already declared, skipping`)\n return\n }\n\n this.capabilities.set(declaration.name, {\n declaration,\n available: new Map(),\n activeAddonId: null,\n activeProvider: null,\n activeCollection: [],\n consumers: new Set(),\n })\n\n this.logger.debug(`Capability declared: ${declaration.name} (mode=${declaration.mode})`)\n }\n\n /**\n * Register a capability provider (called by addon loader when addon is enabled).\n * For singleton: auto-activates if user-preferred or first registered.\n * For collection: adds to active set and notifies consumers.\n */\n registerProvider(capability: string, addonId: string, provider: unknown): void {\n const state = this.capabilities.get(capability)\n if (!state) {\n this.logger.warn(`Cannot register provider for undeclared capability \"${capability}\"`)\n return\n }\n\n state.available.set(addonId, provider)\n this.logger.info(`Provider registered: ${addonId} → ${capability}`)\n\n if (state.declaration.mode === 'singleton') {\n const userChoice = this.configReader(capability)\n\n if (userChoice === addonId) {\n // User explicitly chose this addon\n this.activateSingleton(state, addonId, provider)\n } else if (userChoice === undefined && state.activeAddonId === null) {\n // No user preference and no active provider — first registered = default\n this.activateSingleton(state, addonId, provider)\n }\n } else {\n // collection mode: add to active set and notify\n state.activeCollection.push({ addonId, provider })\n for (const consumer of state.consumers) {\n if (consumer.onAdded) {\n try {\n consumer.onAdded(provider)\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n this.logger.error(`Consumer onAdded failed for ${capability}: ${msg}`)\n }\n }\n }\n }\n }\n\n /**\n * Unregister a provider (called when addon is disabled/uninstalled).\n */\n unregisterProvider(capability: string, addonId: string): void {\n const state = this.capabilities.get(capability)\n if (!state) return\n\n const provider = state.available.get(addonId)\n state.available.delete(addonId)\n\n if (state.declaration.mode === 'singleton') {\n if (state.activeAddonId === addonId) {\n state.activeAddonId = null\n state.activeProvider = null\n this.logger.info(`Singleton deactivated: ${capability} (was ${addonId})`)\n }\n } else {\n // collection: remove from active\n const idx = state.activeCollection.findIndex((e) => e.addonId === addonId)\n if (idx !== -1) {\n state.activeCollection.splice(idx, 1)\n for (const consumer of state.consumers) {\n if (consumer.onRemoved && provider !== undefined) {\n try {\n consumer.onRemoved(provider)\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n this.logger.error(`Consumer onRemoved failed for ${capability}: ${msg}`)\n }\n }\n }\n }\n }\n }\n\n /**\n * Register a consumer that wants to be notified when providers change.\n * If a provider is already active, the consumer is immediately notified.\n * Returns a disposer function for cleanup.\n */\n registerConsumer<T = unknown>(registration: CapabilityConsumerRegistration<T>): () => void {\n const state = this.capabilities.get(registration.capability)\n if (!state) {\n // Auto-declare as singleton if not yet declared (graceful handling)\n this.logger.debug(`Consumer registered for undeclared capability \"${registration.capability}\" — auto-declaring`)\n this.declareCapability({ name: registration.capability, mode: 'singleton' })\n return this.registerConsumer(registration)\n }\n\n const untypedReg = registration as CapabilityConsumerRegistration\n state.consumers.add(untypedReg)\n\n // Immediately notify with current state\n if (state.declaration.mode === 'singleton') {\n if (state.activeProvider !== null && registration.onSet) {\n try {\n registration.onSet(state.activeProvider as T)\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n this.logger.error(`Consumer onSet (immediate) failed for ${registration.capability}: ${msg}`)\n }\n }\n } else {\n // collection: notify with all active\n if (registration.onAdded) {\n for (const entry of state.activeCollection) {\n try {\n registration.onAdded(entry.provider as T)\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n this.logger.error(`Consumer onAdded (immediate) failed for ${registration.capability}: ${msg}`)\n }\n }\n }\n }\n\n return () => {\n state.consumers.delete(untypedReg)\n }\n }\n\n /**\n * Get the active singleton provider for a capability.\n * Returns null if none set.\n */\n getSingleton<T = unknown>(capability: string): T | null {\n const state = this.capabilities.get(capability)\n if (!state || state.declaration.mode !== 'singleton') return null\n return (state.activeProvider as T) ?? null\n }\n\n /**\n * Get all active collection providers for a capability.\n */\n getCollection<T = unknown>(capability: string): readonly T[] {\n const state = this.capabilities.get(capability)\n if (!state || state.declaration.mode !== 'collection') return []\n return state.activeCollection.map((e) => e.provider as T)\n }\n\n /**\n * Set which addon should be the active singleton for a capability.\n * Call with `immediate: true` to also swap the runtime provider now\n * (consumers' onSet will be awaited).\n */\n async setActiveSingleton(capability: string, addonId: string, immediate = false): Promise<void> {\n const state = this.capabilities.get(capability)\n if (!state) {\n throw new Error(`Unknown capability: ${capability}`)\n }\n if (state.declaration.mode !== 'singleton') {\n throw new Error(`Capability \"${capability}\" is not a singleton`)\n }\n\n const provider = state.available.get(addonId)\n if (!provider) {\n throw new Error(`No provider \"${addonId}\" registered for capability \"${capability}\"`)\n }\n\n if (immediate) {\n await this.activateSingletonAsync(state, addonId, provider)\n }\n\n this.logger.info(`Singleton preference set: ${capability} → ${addonId}`)\n }\n\n /**\n * Get the mode declared for a capability.\n */\n getMode(capability: string): CapabilityMode | undefined {\n return this.capabilities.get(capability)?.declaration.mode\n }\n\n /**\n * List all registered capabilities with their providers.\n */\n listCapabilities(): CapabilityInfo[] {\n const result: CapabilityInfo[] = []\n for (const [name, state] of this.capabilities) {\n result.push({\n name,\n mode: state.declaration.mode,\n providers: [...state.available.keys()],\n activeProvider: state.activeAddonId,\n })\n }\n return result\n }\n\n /**\n * Check if all dependencies for a capability are satisfied (have active providers).\n */\n areDependenciesMet(declaration: CapabilityDeclaration): boolean {\n if (!declaration.dependsOn?.length) return true\n return declaration.dependsOn.every((dep) => {\n const state = this.capabilities.get(dep)\n if (!state) return false\n if (state.declaration.mode === 'singleton') {\n return state.activeProvider !== null\n }\n return state.activeCollection.length > 0\n })\n }\n\n /**\n * Get the dependency-ordered list of capability names for boot sequencing.\n * Returns capabilities sorted topologically by dependsOn.\n * Throws if a cycle is detected.\n */\n getBootOrder(): string[] {\n const visited = new Set<string>()\n const visiting = new Set<string>()\n const order: string[] = []\n\n const visit = (name: string): void => {\n if (visited.has(name)) return\n if (visiting.has(name)) {\n throw new Error(`Circular dependency detected involving capability \"${name}\"`)\n }\n\n visiting.add(name)\n const state = this.capabilities.get(name)\n if (state?.declaration.dependsOn) {\n for (const dep of state.declaration.dependsOn) {\n visit(dep)\n }\n }\n visiting.delete(name)\n visited.add(name)\n order.push(name)\n }\n\n for (const name of this.capabilities.keys()) {\n visit(name)\n }\n\n return order\n }\n\n // ---- Per-device overrides ----\n\n /**\n * Set a per-device singleton override. When resolveForDevice is called for\n * this device + capability, the specified addon's provider is returned\n * instead of the global singleton.\n */\n setDeviceOverride(deviceId: string, capability: string, addonId: string): void {\n const state = this.capabilities.get(capability)\n if (!state) {\n this.logger.warn(`Cannot set device override for undeclared capability \"${capability}\"`)\n return\n }\n if (!state.available.has(addonId)) {\n this.logger.warn(`Cannot set device override: addon \"${addonId}\" not registered for \"${capability}\"`)\n return\n }\n\n let deviceMap = this.deviceOverrides.get(deviceId)\n if (!deviceMap) {\n deviceMap = new Map()\n this.deviceOverrides.set(deviceId, deviceMap)\n }\n deviceMap.set(capability, addonId)\n this.logger.info(`Device override set: ${deviceId} → ${capability} = ${addonId}`)\n }\n\n /**\n * Clear a per-device singleton override, reverting to the global singleton.\n */\n clearDeviceOverride(deviceId: string, capability: string): void {\n const deviceMap = this.deviceOverrides.get(deviceId)\n if (!deviceMap) return\n deviceMap.delete(capability)\n if (deviceMap.size === 0) {\n this.deviceOverrides.delete(deviceId)\n }\n this.logger.info(`Device override cleared: ${deviceId} → ${capability}`)\n }\n\n /**\n * Get all per-device singleton overrides for a device.\n * Returns a Map of capability name to addon ID.\n */\n getDeviceOverrides(deviceId: string): Map<string, string> {\n return new Map(this.deviceOverrides.get(deviceId) ?? [])\n }\n\n /**\n * Resolve a singleton provider for a specific device.\n * 1. Check device override — return that addon's provider\n * 2. Fallback to global singleton\n */\n resolveForDevice<T = unknown>(capability: string, deviceId: string): T | null {\n const state = this.capabilities.get(capability)\n if (!state || state.declaration.mode !== 'singleton') return null\n\n // Check device-level override\n const deviceMap = this.deviceOverrides.get(deviceId)\n if (deviceMap) {\n const overrideAddonId = deviceMap.get(capability)\n if (overrideAddonId) {\n const provider = state.available.get(overrideAddonId)\n if (provider) return provider as T\n // Override points to an addon that is no longer registered — fall through\n this.logger.warn(\n `Device override for ${deviceId}/${capability} references unregistered addon \"${overrideAddonId}\" — falling back to global`,\n )\n }\n }\n\n // Fallback to global singleton\n return (state.activeProvider as T) ?? null\n }\n\n /**\n * Set a per-device collection filter. When resolveCollectionForDevice is called\n * for this device + capability, only providers from the specified addon IDs\n * are returned instead of the full collection.\n */\n setDeviceCollectionFilter(deviceId: string, capability: string, addonIds: string[]): void {\n const state = this.capabilities.get(capability)\n if (!state) {\n this.logger.warn(`Cannot set device collection filter for undeclared capability \"${capability}\"`)\n return\n }\n\n let deviceMap = this.deviceCollectionFilters.get(deviceId)\n if (!deviceMap) {\n deviceMap = new Map()\n this.deviceCollectionFilters.set(deviceId, deviceMap)\n }\n deviceMap.set(capability, [...addonIds])\n this.logger.info(`Device collection filter set: ${deviceId} → ${capability} = [${addonIds.join(', ')}]`)\n }\n\n /**\n * Clear a per-device collection filter, reverting to the full collection.\n */\n clearDeviceCollectionFilter(deviceId: string, capability: string): void {\n const deviceMap = this.deviceCollectionFilters.get(deviceId)\n if (!deviceMap) return\n deviceMap.delete(capability)\n if (deviceMap.size === 0) {\n this.deviceCollectionFilters.delete(deviceId)\n }\n this.logger.info(`Device collection filter cleared: ${deviceId} → ${capability}`)\n }\n\n /**\n * Resolve collection providers for a specific device.\n * If a filter exists for the device + capability, only those addon's providers are returned.\n * If no filter exists, the full collection is returned.\n */\n resolveCollectionForDevice<T = unknown>(capability: string, deviceId: string): readonly T[] {\n const state = this.capabilities.get(capability)\n if (!state || state.declaration.mode !== 'collection') return []\n\n // Check device-level filter\n const deviceMap = this.deviceCollectionFilters.get(deviceId)\n if (deviceMap) {\n const filterAddonIds = deviceMap.get(capability)\n if (filterAddonIds) {\n const filterSet = new Set(filterAddonIds)\n return state.activeCollection\n .filter((e) => filterSet.has(e.addonId))\n .map((e) => e.provider as T)\n }\n }\n\n // Fallback to full collection\n return state.activeCollection.map((e) => e.provider as T)\n }\n\n /**\n * Get a specific addon's provider by addon ID, regardless of whether it's the active singleton.\n * Useful for per-device overrides that need to look up any registered provider.\n */\n getProviderByAddonId<T = unknown>(capability: string, addonId: string): T | null {\n const state = this.capabilities.get(capability)\n if (!state) return null\n const provider = state.available.get(addonId)\n return (provider as T) ?? null\n }\n\n private activateSingleton(state: CapabilityState, addonId: string, provider: unknown): void {\n state.activeAddonId = addonId\n state.activeProvider = provider\n this.logger.info(`Singleton activated: ${state.declaration.name} → ${addonId}`)\n\n for (const consumer of state.consumers) {\n if (consumer.onSet) {\n try {\n consumer.onSet(provider)\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n this.logger.error(`Consumer onSet failed for ${state.declaration.name}: ${msg}`)\n }\n }\n }\n }\n\n private async activateSingletonAsync(state: CapabilityState, addonId: string, provider: unknown): Promise<void> {\n state.activeAddonId = addonId\n state.activeProvider = provider\n this.logger.info(`Singleton activated (async): ${state.declaration.name} → ${addonId}`)\n\n for (const consumer of state.consumers) {\n if (consumer.onSet) {\n try {\n await consumer.onSet(provider)\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n this.logger.error(`Consumer onSet (async) failed for ${state.declaration.name}: ${msg}`)\n }\n }\n }\n }\n}\n","// packages/kernel/src/infra-capabilities.ts\n\nexport interface InfraCapability {\n /** Capability name */\n readonly name: string\n /** If true, boot aborts when this capability's addon fails to initialize */\n readonly required: boolean\n}\n\n/**\n * Infrastructure capabilities that must boot before all other addons.\n * Enabled in Phase 1 of the boot sequence. Order matters: storage before logging.\n */\nexport const INFRA_CAPABILITIES: readonly InfraCapability[] = [\n { name: 'storage', required: true },\n { name: 'settings-store', required: true },\n { name: 'log-destination', required: false },\n] as const\n\nconst infraNames = new Set(INFRA_CAPABILITIES.map((c) => c.name))\n\n/** Check if a capability name is an infrastructure capability */\nexport function isInfraCapability(name: string): boolean {\n return infraNames.has(name)\n}\n","import * as fs from 'node:fs'\nimport * as yaml from 'js-yaml'\nimport { bootstrapSchema, RUNTIME_DEFAULTS, type BootstrapConfig, type AppConfig } from './config-schema.js'\n\nexport interface ISettingsStore {\n getSystem(key: string): unknown\n setSystem(key: string, value: unknown): void\n getAllSystem(): Record<string, unknown>\n getAllAddon(addonId: string): Record<string, unknown>\n setAllAddon(addonId: string, config: Record<string, unknown>): void\n getAllProvider(providerId: string): Record<string, unknown>\n setProvider(providerId: string, key: string, value: unknown): void\n getAllDevice(deviceId: string): Record<string, unknown>\n setDevice(deviceId: string, key: string, value: unknown): void\n}\n\n/** Feature flags manifest — inlined to avoid dependency on core's feature module */\ninterface FeatureManifest {\n streaming: boolean\n notifications: boolean\n objectDetection: boolean\n remoteAccess: boolean\n agentCluster: boolean\n smartHome: boolean\n recordings: boolean\n backup: boolean\n repl: boolean\n}\n\n/**\n * Maps environment variable names to dot-notation config paths.\n * Only bootstrap-level env vars -- runtime settings come from SQL (Plan B).\n */\nconst ENV_VAR_MAP: Record<string, string> = {\n CAMSTACK_PORT: 'server.port',\n CAMSTACK_HOST: 'server.host',\n CAMSTACK_DATA: 'server.dataPath',\n CAMSTACK_JWT_SECRET: 'auth.jwtSecret',\n CAMSTACK_ADMIN_USER: 'auth.adminUsername',\n CAMSTACK_ADMIN_PASS: 'auth.adminPassword',\n}\n\nexport class ConfigManager {\n // Non-readonly so update() can sync the in-memory view after a write.\n private bootstrapConfig: BootstrapConfig\n private settingsStore: ISettingsStore | null = null\n\n constructor(private readonly configPath: string) {\n const rawYaml = this.loadYaml()\n const merged = this.applyEnvOverrides(rawYaml as Record<string, unknown>)\n this.bootstrapConfig = bootstrapSchema.parse(merged)\n this.warnDefaultCredentials()\n }\n\n /** Called by main.ts after the SQLite DB is ready (Phase 2). */\n setSettingsStore(store: ISettingsStore): void {\n this.settingsStore = store\n }\n\n /**\n * Get a config value by dot-notation path.\n * Priority: bootstrap config -> SQL system_settings -> RUNTIME_DEFAULTS fallback.\n */\n get<T>(path: string): T {\n // First: check bootstrap config (server, auth)\n const bootstrapValue = this.getFromBootstrap(path)\n if (bootstrapValue !== undefined) {\n return bootstrapValue as T\n }\n\n // Second: check SQL system_settings (if store is wired)\n if (this.settingsStore !== null) {\n const sqlValue = this.settingsStore.getSystem(path)\n if (sqlValue !== undefined) {\n return sqlValue as T\n }\n // Also try prefix-based nested lookup against SQL keys\n const sqlNested = this.getNestedFromSystemSettings(path)\n if (sqlNested !== undefined) {\n return sqlNested as T\n }\n }\n\n // Third: check runtime defaults (backward compat when store not yet wired)\n if (path in RUNTIME_DEFAULTS) {\n return RUNTIME_DEFAULTS[path] as T\n }\n\n // Fourth: try nested lookup in runtime defaults\n const nested = this.getFromRuntimeDefaults(path)\n if (nested !== undefined) {\n return nested as T\n }\n\n return undefined as T\n }\n\n /**\n * Write a value to SQL system_settings.\n * Throws if the settings store is not yet wired.\n */\n set(key: string, value: unknown): void {\n if (this.settingsStore === null) {\n throw new Error('[ConfigManager] SettingsStore not initialized -- call setSettingsStore() first')\n }\n this.settingsStore.setSystem(key, value)\n }\n\n /**\n * Bulk-read all system_settings keys that belong to a logical section.\n * A \"section\" is the first segment of a dot-notation key (e.g. 'features', 'logging').\n */\n getSection(section: string): Record<string, unknown> {\n // 1. Try SQL system_settings\n if (this.settingsStore !== null) {\n const nested = this.getNestedFromSystemSettings(section)\n if (nested !== undefined) return nested as Record<string, unknown>\n }\n // 2. Try bootstrap config (YAML) — for sections like 'server', 'auth' that live in config.yaml\n const bootstrapValue = (this.bootstrapConfig as Record<string, unknown>)[section]\n if (bootstrapValue !== undefined && bootstrapValue !== null && typeof bootstrapValue === 'object') {\n return bootstrapValue as Record<string, unknown>\n }\n // 3. Fallback to RUNTIME_DEFAULTS\n return (this.getFromRuntimeDefaults(section) as Record<string, unknown>) ?? {}\n }\n\n /**\n * Bulk-write a section of runtime settings to SQL system_settings.\n * Each entry in `data` is stored as `section.key`.\n */\n setSection(section: string, data: Record<string, unknown>): void {\n if (this.settingsStore === null) {\n throw new Error('[ConfigManager] SettingsStore not initialized -- call setSettingsStore() first')\n }\n for (const [key, value] of Object.entries(data)) {\n this.settingsStore.setSystem(`${section}.${key}`, value)\n }\n }\n\n // ---------------------------------------------------------------------------\n // Addon / Provider / Device scoped config\n // ---------------------------------------------------------------------------\n\n /** Read all config for an addon from addon_settings. */\n getAddonConfig(addonId: string): Record<string, unknown> {\n if (this.settingsStore !== null) {\n return this.settingsStore.getAllAddon(addonId)\n }\n // Fallback: read from bootstrap (legacy)\n return (this.getFromBootstrap(`addons.${addonId}`) as Record<string, unknown>) ?? {}\n }\n\n /** Write (bulk-replace) config for an addon to addon_settings. */\n setAddonConfig(addonId: string, config: Record<string, unknown>): void {\n if (this.settingsStore === null) {\n throw new Error('[ConfigManager] SettingsStore not initialized -- call setSettingsStore() first')\n }\n this.settingsStore.setAllAddon(addonId, config)\n }\n\n /** Read all config for a provider from provider_settings. */\n getProviderConfig(providerId: string): Record<string, unknown> {\n if (this.settingsStore !== null) {\n return this.settingsStore.getAllProvider(providerId)\n }\n return {}\n }\n\n /** Write (upsert) a single key for a provider to provider_settings. */\n setProviderConfig(providerId: string, key: string, value: unknown): void {\n if (this.settingsStore === null) {\n throw new Error('[ConfigManager] SettingsStore not initialized -- call setSettingsStore() first')\n }\n this.settingsStore.setProvider(providerId, key, value)\n }\n\n /** Read all config for a device from device_settings. */\n getDeviceConfig(deviceId: string): Record<string, unknown> {\n if (this.settingsStore !== null) {\n return this.settingsStore.getAllDevice(deviceId)\n }\n return {}\n }\n\n /** Write (upsert) a single key for a device to device_settings. */\n setDeviceConfig(deviceId: string, key: string, value: unknown): void {\n if (this.settingsStore === null) {\n throw new Error('[ConfigManager] SettingsStore not initialized -- call setSettingsStore() first')\n }\n this.settingsStore.setDevice(deviceId, key, value)\n }\n\n /** Get a value from the parsed bootstrap config */\n getBootstrap<T>(path: string): T {\n return this.getFromBootstrap(path) as T\n }\n\n /** Features accessor -- reads from SQL when available, falls back to RUNTIME_DEFAULTS */\n get features(): FeatureManifest {\n const g = (key: string): boolean =>\n (this.get<boolean>(`features.${key}`) ?? RUNTIME_DEFAULTS[`features.${key}`]) as boolean\n return {\n streaming: g('streaming'),\n notifications: g('notifications'),\n objectDetection: g('objectDetection'),\n remoteAccess: g('remoteAccess'),\n agentCluster: g('agentCluster'),\n smartHome: g('smartHome'),\n recordings: g('recordings'),\n backup: g('backup'),\n repl: g('repl'),\n }\n }\n\n /**\n * Returns a merged view of bootstrap config + runtime defaults for backward compat.\n */\n get raw(): AppConfig {\n const features = {\n streaming: RUNTIME_DEFAULTS['features.streaming'] as boolean,\n notifications: RUNTIME_DEFAULTS['features.notifications'] as boolean,\n objectDetection: RUNTIME_DEFAULTS['features.objectDetection'] as boolean,\n remoteAccess: RUNTIME_DEFAULTS['features.remoteAccess'] as boolean,\n agentCluster: RUNTIME_DEFAULTS['features.agentCluster'] as boolean,\n smartHome: RUNTIME_DEFAULTS['features.smartHome'] as boolean,\n recordings: RUNTIME_DEFAULTS['features.recordings'] as boolean,\n backup: RUNTIME_DEFAULTS['features.backup'] as boolean,\n repl: RUNTIME_DEFAULTS['features.repl'] as boolean,\n }\n return {\n ...this.bootstrapConfig,\n features,\n storage: RUNTIME_DEFAULTS['storage.locations'] !== undefined\n ? {\n provider: RUNTIME_DEFAULTS['storage.provider'] as string,\n locations: RUNTIME_DEFAULTS['storage.locations'] as Record<string, string>,\n }\n : { provider: 'sqlite-storage', locations: {} },\n logging: {\n level: RUNTIME_DEFAULTS['logging.level'] as string,\n retentionDays: RUNTIME_DEFAULTS['logging.retentionDays'] as number,\n },\n eventBus: {\n ringBufferSize: RUNTIME_DEFAULTS['eventBus.ringBufferSize'] as number,\n },\n retention: {\n detectionEventsDays: RUNTIME_DEFAULTS['retention.detectionEventsDays'] as number,\n audioLevelsDays: RUNTIME_DEFAULTS['retention.audioLevelsDays'] as number,\n },\n providers: RUNTIME_DEFAULTS['providers'] as AppConfig['providers'],\n }\n }\n\n /** Sections that live in config.yaml. Everything else goes to SQL. */\n private static readonly BOOTSTRAP_SECTIONS = new Set(['server', 'auth', 'mode'])\n\n /**\n * Atomically update one top-level section of config.yaml and sync in-memory.\n * Only bootstrap sections (server, auth, mode) are written to YAML.\n * Runtime settings must use setSection() which writes to SQL.\n */\n update(section: string, data: Record<string, unknown>): void {\n if (!ConfigManager.BOOTSTRAP_SECTIONS.has(section)) {\n throw new Error(\n `[ConfigManager] Section \"${section}\" is a runtime setting — use setSection() to write to DB, not update() which writes to config.yaml`,\n )\n }\n\n // Load latest raw YAML to avoid overwriting concurrent changes\n let raw: Record<string, unknown> = {}\n if (fs.existsSync(this.configPath)) {\n raw = (yaml.load(fs.readFileSync(this.configPath, 'utf-8')) as Record<string, unknown>) ?? {}\n }\n\n // Shallow merge within the section\n const existing = (raw[section] as Record<string, unknown>) ?? {}\n raw[section] = { ...existing, ...data }\n\n // Validate bootstrap sections\n const validation = bootstrapSchema.safeParse(raw)\n if (!validation.success) {\n throw new Error(`[ConfigManager] Invalid config update for section \"${section}\": ${validation.error.message}`)\n }\n\n // Atomic write: temp file -> rename\n const tmpPath = `${this.configPath}.tmp`\n fs.writeFileSync(tmpPath, yaml.dump(raw, { lineWidth: 120, indent: 2, quotingType: '\"' }), 'utf-8')\n fs.renameSync(tmpPath, this.configPath)\n\n // Sync in-memory bootstrap state\n this.bootstrapConfig = validation.data\n }\n\n /**\n * Deep-set a value in a nested plain object using a dot-notation path.\n * Returns a new object (immutable).\n */\n private setNested(obj: Record<string, unknown>, path: string, value: unknown): Record<string, unknown> {\n const [head, ...rest] = path.split('.')\n if (!head) return obj\n if (rest.length === 0) {\n return { ...obj, [head]: value }\n }\n const child = (obj[head] ?? {}) as Record<string, unknown>\n return { ...obj, [head]: this.setNested(child, rest.join('.'), value) }\n }\n\n /**\n * Apply env var overrides onto the raw YAML object.\n * Only bootstrap-level env vars are applied.\n */\n private applyEnvOverrides(raw: Record<string, unknown>): Record<string, unknown> {\n let result = { ...raw }\n\n for (const [envKey, configPath] of Object.entries(ENV_VAR_MAP)) {\n const envValue = process.env[envKey]\n if (envValue === undefined || envValue === '') continue\n\n // Coerce to number for known numeric fields\n const coerced: unknown = configPath === 'server.port' ? Number(envValue) : envValue\n result = this.setNested(result, configPath, coerced)\n console.log(`[ConfigManager] Env override applied: ${envKey} → ${configPath}`)\n }\n\n return result\n }\n\n private loadYaml(): unknown {\n if (!fs.existsSync(this.configPath)) {\n console.warn(\n `[ConfigManager] Config file not found at: ${this.configPath}\\n` +\n ` → Using built-in defaults. Set CONFIG_PATH env var or create the file.\\n` +\n ` → Example path from project root: ./server/backend/data/config.yaml`,\n )\n return {}\n }\n\n const content = fs.readFileSync(this.configPath, 'utf-8')\n const parsed = yaml.load(content) ?? {}\n console.log(`[ConfigManager] Loaded config from: ${this.configPath}`)\n return parsed\n }\n\n private warnDefaultCredentials(): void {\n if (this.bootstrapConfig.auth.adminPassword === 'changeme') {\n console.warn(\n `[ConfigManager] Warning: Using default admin password \"changeme\". ` +\n `Set auth.adminPassword in your config.yaml or the ADMIN_PASSWORD env var.`,\n )\n }\n }\n\n private getFromBootstrap(path: string): unknown {\n const keys = path.split('.')\n let current: unknown = this.bootstrapConfig\n\n for (const key of keys) {\n if (current === null || current === undefined || typeof current !== 'object') {\n return undefined\n }\n current = (current as Record<string, unknown>)[key]\n }\n\n return current\n }\n\n private getFromRuntimeDefaults(path: string): unknown {\n // Look for prefix matches (e.g. 'features' -> checks 'features.*' entries)\n const prefix = path + '.'\n const result: Record<string, unknown> = {}\n let found = false\n\n for (const [key, value] of Object.entries(RUNTIME_DEFAULTS)) {\n if (key.startsWith(prefix)) {\n const subKey = key.slice(prefix.length)\n result[subKey] = value\n found = true\n }\n }\n\n return found ? result : undefined\n }\n\n /**\n * Perform a prefix-based nested lookup against SQL system_settings.\n * e.g. path='features' matches keys 'features.streaming', 'features.notifications', etc.\n * Returns an object keyed by the sub-key, or undefined if nothing is found.\n */\n private getNestedFromSystemSettings(path: string): unknown {\n if (this.settingsStore === null) return undefined\n const all = this.settingsStore.getAllSystem()\n const prefix = path + '.'\n const result: Record<string, unknown> = {}\n let found = false\n for (const [key, value] of Object.entries(all)) {\n if (key.startsWith(prefix)) {\n result[key.slice(prefix.length)] = value\n found = true\n }\n }\n return found ? result : undefined\n }\n}\n","import { z } from 'zod'\n\n/** Default data directory — used when CAMSTACK_DATA env var is not set */\nexport const DEFAULT_DATA_PATH = 'camstack-data'\n\n/** Bootstrap config -- loaded from config.yaml ONLY at startup.\n * All other settings live in SQL (system_settings table). */\nexport const bootstrapSchema = z.object({\n /** Server mode: 'hub' (full server) or 'agent' (worker node) */\n mode: z.enum(['hub', 'agent']).default('hub'),\n server: z.object({\n port: z.number().default(4443),\n host: z.string().default('0.0.0.0'),\n dataPath: z.string().default(DEFAULT_DATA_PATH),\n }).default({}),\n auth: z.object({\n jwtSecret: z.string().nullable().default(null),\n adminUsername: z.string().default('admin'),\n adminPassword: z.string().default(process.env.ADMIN_PASSWORD ?? 'changeme'),\n }).default({}),\n /** Hub connection config — only used when mode='agent' */\n hub: z.object({\n url: z.string().default('ws://localhost:4443/agent'),\n token: z.string().default(''),\n }).default({}),\n /** Agent-specific config — only used when mode='agent' */\n agent: z.object({\n name: z.string().default(''),\n /** Port for the agent status page (minimal HTML) */\n statusPort: z.number().default(4444),\n }).default({}),\n /** TLS configuration */\n tls: z.object({\n /** Enable HTTPS (default: true) */\n enabled: z.boolean().default(true),\n /** Path to custom cert file (PEM). If not set, auto-generates self-signed. */\n certPath: z.string().optional(),\n /** Path to custom key file (PEM). Required if certPath is set. */\n keyPath: z.string().optional(),\n }).default({}),\n})\n\nexport type BootstrapConfig = z.infer<typeof bootstrapSchema>\n\n/**\n * Runtime defaults -- used by ConfigManager.get() for backward compatibility\n * until Plan B wires all runtime settings to the system_settings SQL table.\n */\nexport const RUNTIME_DEFAULTS: Record<string, unknown> = {\n 'features.streaming': true,\n 'features.notifications': true,\n 'features.objectDetection': false,\n 'features.remoteAccess': true,\n 'features.agentCluster': false,\n 'features.smartHome': true,\n 'features.recordings': true,\n 'features.backup': true,\n 'features.repl': true,\n 'retention.detectionEventsDays': 30,\n 'retention.audioLevelsDays': 7,\n 'logging.level': 'info',\n 'logging.retentionDays': 30,\n 'eventBus.ringBufferSize': 10000,\n 'storage.provider': 'sqlite-storage',\n 'storage.locations': {\n data: 'camstack-data/data',\n media: 'camstack-data/media',\n recordings: 'camstack-data/recordings',\n cache: '/tmp/camstack-cache',\n logs: 'camstack-data/logs',\n models: 'camstack-data/models',\n },\n 'providers': [],\n // Recording\n 'recording.segmentDurationSeconds': 4,\n 'recording.defaultRetentionDays': 30,\n // Streaming ports are addon-specific (go2rtc owns its defaults)\n // FFmpeg\n 'ffmpeg.binaryPath': 'ffmpeg',\n 'ffmpeg.hwAccel': 'auto',\n 'ffmpeg.threadCount': 0,\n // Detection defaults\n 'detection.defaultMotionFps': 2,\n 'detection.defaultDetectionFps': 5,\n 'detection.defaultCooldownSeconds': 10,\n 'detection.defaultConfidenceThreshold': 0.4,\n 'detection.trackerMaxAgeFrames': 30,\n 'detection.trackerMinHits': 3,\n 'detection.trackerIouThreshold': 0.3,\n // Backup retention is addon-specific (local-backup owns its default)\n // Auth (runtime)\n 'auth.tokenExpiry': '24h',\n}\n\nexport type ServerMode = 'hub' | 'agent'\n\nexport type AppConfig = BootstrapConfig & {\n features: {\n streaming: boolean\n notifications: boolean\n objectDetection: boolean\n remoteAccess: boolean\n agentCluster: boolean\n smartHome: boolean\n recordings: boolean\n backup: boolean\n repl: boolean\n }\n storage: {\n provider: string\n locations: Record<string, string>\n }\n logging: {\n level: string\n retentionDays: number\n }\n eventBus: {\n ringBufferSize: number\n }\n retention: {\n detectionEventsDays: number\n audioLevelsDays: number\n }\n providers: Array<{\n id: string\n type: string\n name: string\n url?: string\n username?: string\n password?: string\n mqtt?: {\n brokerUrl: string\n username?: string\n password?: string\n topicPrefix: string\n }\n }>\n}\n","import { fork, type ChildProcess } from 'node:child_process'\nimport type {\n WorkerInfo,\n WorkerState,\n WorkerToMainMessage,\n} from '@camstack/types'\n\n/** Infra addon IDs that must stay in-process */\nconst INFRA_ADDON_IDS = new Set([\n 'filesystem-storage',\n 'sqlite-settings',\n 'winston-logging',\n])\n\nexport interface WorkerHostOptions {\n readonly workerEntryPath: string\n readonly devMode?: boolean\n /** Force all addons in-process (emergency fallback) */\n readonly forceInProcess?: boolean\n readonly heartbeatIntervalMs?: number\n readonly heartbeatTimeoutMs?: number\n readonly maxCrashesInWindow?: number\n readonly crashWindowMs?: number\n readonly shutdownTimeoutMs?: number\n /** Log writer function — receives structured log entries from workers */\n readonly onWorkerLog?: (entry: { addonId: string; level: string; message: string; scope: readonly string[]; meta?: Record<string, unknown> }) => void\n}\n\ninterface ForkConfig {\n readonly addonDir: string\n readonly config: Record<string, unknown>\n readonly storagePaths: Record<string, string>\n readonly dataDir?: string\n readonly locationPaths?: Record<string, string>\n readonly workerToken?: string\n}\n\ninterface ManagedWorker {\n addonId: string\n process: ChildProcess\n state: WorkerState\n startedAt: number\n lastHeartbeat: number\n restartCount: number\n crashTimestamps: number[]\n cpuPercent: number\n memoryRss: number\n workerToken?: string\n /** Stored fork config for restart support */\n forkConfig?: ForkConfig\n}\n\ntype ResolvedWorkerHostOptions = Required<Omit<WorkerHostOptions, 'onWorkerLog'>> & Pick<WorkerHostOptions, 'onWorkerLog'>\n\nexport class AddonWorkerHost {\n private readonly workers = new Map<string, ManagedWorker>()\n private readonly options: ResolvedWorkerHostOptions\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null\n\n /** Set of addons that failed to fork and fell back to in-process */\n private readonly fallbackInProcess = new Set<string>()\n\n constructor(options: WorkerHostOptions) {\n this.options = {\n workerEntryPath: options.workerEntryPath,\n devMode: options.devMode ?? false,\n forceInProcess: options.forceInProcess ?? (process.env.CAMSTACK_FORCE_INPROCESS === 'true'),\n heartbeatIntervalMs: options.heartbeatIntervalMs ?? 5000,\n heartbeatTimeoutMs: options.heartbeatTimeoutMs ?? 30000,\n maxCrashesInWindow: options.maxCrashesInWindow ?? 3,\n crashWindowMs: options.crashWindowMs ?? 60000,\n shutdownTimeoutMs: options.shutdownTimeoutMs ?? 10000,\n onWorkerLog: options.onWorkerLog,\n }\n }\n\n /** Check if an addon is infrastructure (must stay in-process) */\n isInfraAddon(addonId: string): boolean {\n return INFRA_ADDON_IDS.has(addonId)\n }\n\n /** Check if an addon fell back to in-process after fork failure */\n isFallbackInProcess(addonId: string): boolean {\n return this.fallbackInProcess.has(addonId)\n }\n\n /** Mark an addon as fallen back to in-process */\n markFallbackInProcess(addonId: string): void {\n this.fallbackInProcess.add(addonId)\n }\n\n /**\n * Determine if an addon should be forked.\n * Default: fork everything except infra addons.\n * Addons can opt out with `inProcess: true` in declaration.\n * Emergency fallback: CAMSTACK_FORCE_INPROCESS=true disables all forking.\n */\n shouldFork(addonId: string, declaration?: { forkable?: boolean; inProcess?: boolean }): boolean {\n if (this.options.forceInProcess) return false\n if (this.options.devMode) return false\n if (this.isInfraAddon(addonId)) return false\n if (this.fallbackInProcess.has(addonId)) return false\n // Addon can explicitly opt out of forking\n if (declaration?.inProcess === true) return false\n // Must explicitly declare forkable: true to fork\n if (declaration?.forkable !== true) return false\n return true\n }\n\n /** Fork a worker for an addon */\n async forkWorker(\n addonId: string,\n addonDir: string,\n config: Record<string, unknown>,\n storagePaths: Record<string, string>,\n dataDir?: string,\n locationPaths?: Record<string, string>,\n workerToken?: string,\n ): Promise<void> {\n if (this.workers.has(addonId)) {\n throw new Error(`Worker for addon \"${addonId}\" already exists`)\n }\n\n // Worker entry is always a compiled .js file — no tsx needed.\n // Pass all config via environment variables (not IPC INIT message).\n const workerEnv: Record<string, string> = {\n PATH: process.env.PATH ?? '',\n HOME: process.env.HOME ?? '',\n NODE_ENV: process.env.NODE_ENV ?? 'production',\n NODE_TLS_REJECT_UNAUTHORIZED: '0', // Accept self-signed cert\n CAMSTACK_WORKER_HUB_URL: `wss://localhost:${process.env.CAMSTACK_PORT ?? '4443'}/trpc`,\n CAMSTACK_WORKER_TOKEN: workerToken ?? '',\n CAMSTACK_ADDON_ID: addonId,\n CAMSTACK_ADDON_DIR: addonDir,\n CAMSTACK_ADDON_CONFIG: JSON.stringify(config),\n CAMSTACK_DATA_DIR: dataDir ?? `camstack-data/addons-data/${addonId}`,\n CAMSTACK_LOCATION_PATHS: JSON.stringify(locationPaths ?? storagePaths),\n }\n\n const forkOptions: Record<string, unknown> = {\n stdio: ['inherit', 'inherit', 'inherit', 'ipc'],\n execArgv: [],\n env: workerEnv,\n }\n\n const child = fork(this.options.workerEntryPath, [], forkOptions as any)\n\n const worker: ManagedWorker = {\n addonId,\n process: child,\n state: 'starting',\n startedAt: Date.now(),\n lastHeartbeat: Date.now(),\n restartCount: 0,\n crashTimestamps: [],\n cpuPercent: 0,\n memoryRss: 0,\n workerToken,\n forkConfig: { addonDir, config, storagePaths, dataDir, locationPaths, workerToken },\n }\n\n this.workers.set(addonId, worker)\n\n // Route structured IPC messages from worker\n child.on('message', (msg: WorkerToMainMessage) => {\n if (msg.type === 'LOG' && this.options.onWorkerLog) {\n this.options.onWorkerLog({\n addonId,\n level: msg.level,\n message: msg.message,\n scope: ['hub', addonId],\n meta: msg.context,\n })\n } else if (msg.type === 'STATS') {\n worker.cpuPercent = msg.cpu\n worker.memoryRss = msg.memory\n }\n })\n\n // Capture stdout/stderr from worker as fallback (unstructured logs, crash output)\n child.stdout?.on('data', (chunk: Buffer) => {\n const lines = chunk.toString().trim()\n if (!lines) return\n if (this.options.onWorkerLog) {\n this.options.onWorkerLog({\n addonId,\n level: 'info',\n message: lines,\n scope: ['hub', addonId, '__stdout'],\n })\n } else {\n console.log(`[Worker:${addonId}] ${lines}`)\n }\n })\n child.stderr?.on('data', (chunk: Buffer) => {\n const lines = chunk.toString().trim()\n if (!lines) return\n if (this.options.onWorkerLog) {\n this.options.onWorkerLog({\n addonId,\n level: 'error',\n message: lines,\n scope: ['hub', addonId, '__stderr'],\n })\n } else {\n console.error(`[Worker:${addonId}:ERR] ${lines}`)\n }\n })\n\n child.on('exit', (code) => {\n this.handleWorkerExit(addonId, code, addonDir, config, storagePaths, dataDir, locationPaths, workerToken)\n })\n\n child.on('error', (err) => {\n console.error(`[WorkerHost] Worker ${addonId} error:`, err.message)\n })\n\n // No IPC INIT message — config is passed via env vars.\n // Wait for READY signal via IPC (process.send({ type: 'READY' }) from worker).\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new Error(`Worker ${addonId} did not become ready within 30s`))\n }, 30000)\n\n const readyCheck = (msg: { type: string }) => {\n if (msg.type === 'READY') {\n clearTimeout(timeout)\n child.off('message', readyCheck)\n worker.state = 'running'\n resolve()\n }\n }\n child.on('message', readyCheck)\n })\n }\n\n /** List all workers with stats */\n listWorkers(): readonly WorkerInfo[] {\n return [...this.workers.values()].map((w) => ({\n addonId: w.addonId,\n pid: w.process.pid ?? 0,\n state: w.state,\n cpuPercent: w.cpuPercent,\n memoryRss: w.memoryRss,\n uptimeSeconds: Math.round((Date.now() - w.startedAt) / 1000),\n restartCount: w.restartCount,\n subProcesses: [],\n }))\n }\n\n /** Gracefully shutdown a single worker by addon ID */\n async shutdownWorkerById(addonId: string): Promise<void> {\n const worker = this.workers.get(addonId)\n if (!worker) {\n throw new Error(`No worker found for addon \"${addonId}\"`)\n }\n await this.shutdownWorker(addonId, worker)\n this.workers.delete(addonId)\n }\n\n /** Gracefully restart a single worker by addon ID (stop then re-fork with same config) */\n async restartWorker(addonId: string): Promise<void> {\n const worker = this.workers.get(addonId)\n if (!worker) {\n throw new Error(`No worker found for addon \"${addonId}\"`)\n }\n\n const forkConfig = worker.forkConfig\n if (!forkConfig) {\n throw new Error(`No fork config stored for addon \"${addonId}\" — cannot restart`)\n }\n\n // Gracefully stop the existing worker\n await this.shutdownWorker(addonId, worker)\n this.workers.delete(addonId)\n\n // Re-fork with the same configuration\n await this.forkWorker(\n addonId,\n forkConfig.addonDir,\n forkConfig.config,\n forkConfig.storagePaths,\n forkConfig.dataDir,\n forkConfig.locationPaths,\n forkConfig.workerToken,\n )\n }\n\n /** Gracefully shutdown all workers */\n async shutdownAll(): Promise<void> {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n\n const shutdowns = [...this.workers.entries()].map(([addonId, worker]) =>\n this.shutdownWorker(addonId, worker),\n )\n await Promise.allSettled(shutdowns)\n this.workers.clear()\n }\n\n /** Start heartbeat monitoring — workers now report heartbeat via tRPC, not IPC PING */\n startHeartbeatMonitoring(): void {\n // With tRPC-based workers, heartbeat is handled by the agent.heartbeat mutation.\n // Keep this method for API compatibility but no IPC pings are needed.\n this.heartbeatTimer = setInterval(() => {\n const now = Date.now()\n for (const [addonId, worker] of this.workers) {\n if (worker.state !== 'running') continue\n // Workers report heartbeat via tRPC — if no update in timeout window, mark crashed\n if (now - worker.lastHeartbeat > this.options.heartbeatTimeoutMs) {\n console.warn(`[WorkerHost] Worker ${addonId} heartbeat timeout — marking unresponsive`)\n worker.state = 'crashed'\n worker.process.kill('SIGKILL')\n }\n }\n }, this.options.heartbeatIntervalMs)\n }\n\n /** Update heartbeat timestamp for a worker (called when agent.heartbeat arrives) */\n updateWorkerHeartbeat(addonId: string, cpuPercent?: number, memoryRss?: number): void {\n const worker = this.workers.get(addonId)\n if (!worker) return\n worker.lastHeartbeat = Date.now()\n if (cpuPercent !== undefined) worker.cpuPercent = cpuPercent\n if (memoryRss !== undefined) worker.memoryRss = memoryRss\n }\n\n private handleWorkerExit(\n addonId: string,\n code: number | null,\n addonDir: string,\n config: Record<string, unknown>,\n storagePaths: Record<string, string>,\n dataDir?: string,\n locationPaths?: Record<string, string>,\n workerToken?: string,\n ): void {\n const worker = this.workers.get(addonId)\n if (!worker) return\n\n if (worker.state === 'stopping') {\n worker.state = 'stopped'\n return\n }\n\n worker.state = 'crashed'\n console.error(`[WorkerHost] Worker ${addonId} exited with code ${code}`)\n\n // Check crash rate\n const now = Date.now()\n worker.crashTimestamps.push(now)\n const recentCrashes = worker.crashTimestamps.filter(\n (t) => now - t < this.options.crashWindowMs,\n )\n worker.crashTimestamps = recentCrashes\n\n if (recentCrashes.length >= this.options.maxCrashesInWindow) {\n console.error(`[WorkerHost] Worker ${addonId} crashed ${recentCrashes.length} times in ${this.options.crashWindowMs}ms — not restarting`)\n return\n }\n\n // Restart after backoff\n const backoff = Math.min(2000 * (worker.restartCount + 1), 30000)\n console.log(`[WorkerHost] Restarting worker ${addonId} in ${backoff}ms`)\n setTimeout(() => {\n worker.restartCount++\n this.workers.delete(addonId)\n this.forkWorker(addonId, addonDir, config, storagePaths, dataDir, locationPaths, workerToken).catch((err) => {\n console.error(`[WorkerHost] Failed to restart worker ${addonId}:`, err)\n })\n }, backoff)\n }\n\n private async shutdownWorker(addonId: string, worker: ManagedWorker): Promise<void> {\n worker.state = 'stopping'\n // Send SIGTERM — worker handles graceful shutdown internally\n worker.process.kill('SIGTERM')\n\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n console.warn(`[WorkerHost] Worker ${addonId} did not exit within ${this.options.shutdownTimeoutMs}ms — SIGKILL`)\n worker.process.kill('SIGKILL')\n resolve()\n }, this.options.shutdownTimeoutMs)\n\n worker.process.on('exit', () => {\n clearTimeout(timeout)\n resolve()\n })\n })\n }\n}\n"],"mappings":"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","names":["fs","path","fs","path","resolve"]}
|