@camstack/agent 1.0.1 → 1.0.2
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/{chunk-JOSKC25O.mjs → chunk-5JHGVZFN.mjs} +5 -5
- package/dist/chunk-5JHGVZFN.mjs.map +1 -0
- package/dist/cli.js +39 -39
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +39 -39
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +2 -3
- package/dist/chunk-JOSKC25O.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/agent-config.ts","../src/agent-service.ts","../src/agent-http.ts","../src/agent-bootstrap.ts","../src/agent-cap-dispatch-service.ts","../src/register-agent-cap-dispatch.ts"],"sourcesContent":["import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport * as crypto from 'node:crypto'\nimport * as os from 'node:os'\n\ninterface AgentConfig {\n readonly nodeId: string\n readonly name: string\n readonly hubAddress?: string\n readonly dataDir: string\n readonly addonsDir: string\n readonly logLevel: string\n readonly secret?: string\n /** Resolved path to the config file (for persisting renames). */\n readonly configPath: string\n /** HTTP port for the agent status API + optional UI (default: 4444). */\n readonly statusPort: number\n}\n\n/**\n * Generate a stable nodeId and persist it to the config file.\n *\n * Resolution order (first match wins, persisted on first boot):\n * 1. Existing `nodeId` in the config file — survives env var changes\n * and host renames once seeded.\n * 2. `CAMSTACK_NODE_ID` env var on first boot — lets dev workflows\n * pin a stable, human-readable nodeId (e.g. `dev-agent-0`)\n * without clobbering already-persisted random ids.\n * 3. A fresh random hex (`agent-a1b2c3`) — production fallback when\n * no env var is set.\n *\n * Seeded values get written to the config file so subsequent boots\n * round-trip exactly the same identity (Moleculer nodeID, capability\n * routing, persisted agentSettings — all keyed by this string).\n */\nfunction ensurePersistedNodeId(configPath: string, dataDir: string): string {\n const resolvedPath = path.resolve(dataDir, configPath)\n let raw: Record<string, unknown> = {}\n if (fs.existsSync(resolvedPath)) {\n try {\n raw = JSON.parse(fs.readFileSync(resolvedPath, 'utf-8')) as Record<string, unknown>\n } catch {\n /* corrupt file */\n }\n }\n if (typeof raw.nodeId === 'string' && raw.nodeId.length > 0) {\n return raw.nodeId\n }\n const envSeed = process.env.CAMSTACK_NODE_ID\n const id =\n envSeed && envSeed.length > 0 ? envSeed : `agent-${crypto.randomBytes(4).toString('hex')}`\n raw.nodeId = id\n try {\n fs.mkdirSync(path.dirname(resolvedPath), { recursive: true })\n fs.writeFileSync(resolvedPath, JSON.stringify(raw, null, 2), 'utf-8')\n } catch {\n /* best-effort */\n }\n return id\n}\n\nfunction loadAgentConfig(configPath?: string, dataDirOverride?: string): AgentConfig {\n const filePath = configPath ?? process.env.CAMSTACK_AGENT_CONFIG ?? 'agent.json'\n const dataDir = dataDirOverride ?? process.env.CAMSTACK_DATA_DIR ?? './camstack-data'\n const resolvedDataDir = path.resolve(dataDir)\n\n // Name: config file takes priority over env (allows UI rename to stick).\n // Env is the initial seed, config file is the persisted override.\n const envName =\n process.env.CAMSTACK_NODE_ID ??\n process.env.CAMSTACK_AGENT_NAME ??\n `${os.hostname()}-${os.arch()}`\n\n // Environment variables for hub connection (optional — agent starts without)\n const envHubAddress = process.env.CAMSTACK_HUB_ADDRESS || undefined\n const envSecret = process.env.CAMSTACK_CLUSTER_SECRET || undefined\n const configFullPath = path.resolve(resolvedDataDir, filePath)\n const nodeId = ensurePersistedNodeId(filePath, resolvedDataDir)\n\n // Read persisted config — these override env vars\n let fileHubAddress: string | undefined\n let fileName: string | undefined\n let fileSecret: string | undefined\n if (fs.existsSync(configFullPath)) {\n try {\n const raw = JSON.parse(fs.readFileSync(configFullPath, 'utf-8')) as Record<string, unknown>\n fileHubAddress = typeof raw.hubAddress === 'string' ? raw.hubAddress : undefined\n fileName = typeof raw.name === 'string' ? raw.name : undefined\n fileSecret = typeof raw.secret === 'string' ? raw.secret : undefined\n } catch {\n /* corrupt config */\n }\n }\n\n // Config file wins over env for all user-editable fields\n const effectiveName = fileName ?? envName\n const effectiveHub = fileHubAddress ?? envHubAddress\n const effectiveSecret = fileSecret ?? envSecret\n\n return {\n nodeId,\n name: effectiveName,\n hubAddress: effectiveHub,\n dataDir: resolvedDataDir,\n addonsDir: path.resolve(resolvedDataDir, 'addons'),\n logLevel: process.env.CAMSTACK_LOG_LEVEL ?? 'info',\n secret: effectiveSecret,\n configPath: configFullPath,\n statusPort: Number(process.env.CAMSTACK_STATUS_PORT) || 4444,\n }\n}\n\nexport { loadAgentConfig }\nexport type { AgentConfig }\n","import * as os from 'node:os'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport type { ServiceSchema, Context } from 'moleculer'\nimport { Errors } from 'moleculer'\nimport type { AgentHealth, IMetricsProvider } from '@camstack/types'\nimport type { RegisterNodeParams, RegisterNodeResult } from '@camstack/kernel'\nimport { clusterSecretMatches, CLUSTER_SECRET_MISMATCH_TYPE } from '@camstack/kernel'\n\n/**\n * Narrow interface for the Moleculer broker surface used in this file.\n * moleculer's index.d.ts chains through eventemitter2 whose package.json\n * has no `types` field; typescript-eslint's no-unsafe-* rules flag every\n * broker/ctx access. Cast once at each handler boundary via CtxLike.\n */\ninterface BrokerLike {\n readonly nodeID: string\n logger: {\n info(msg: string, ...args: unknown[]): void\n warn(msg: string, ...args: unknown[]): void\n }\n destroyService(name: string): Promise<void>\n}\n\ninterface CtxLike<P> {\n params: P\n broker: BrokerLike\n}\n\nfunction getLocalIps(): string[] {\n const interfaces = os.networkInterfaces()\n const ips: string[] = []\n for (const ifaces of Object.values(interfaces)) {\n if (!ifaces) continue\n for (const iface of ifaces) {\n if (iface.internal) continue\n ips.push(iface.address)\n }\n }\n return ips\n}\n\ninterface LoadedAddonEntry {\n readonly id: string\n readonly status: string\n readonly version?: string\n readonly packageName?: string\n readonly addon?: {\n shutdown?(): Promise<void>\n onHubReachable?(): Promise<void> | void\n }\n}\n\ninterface AgentServiceDeps {\n readonly addonsDir: string\n readonly dataDir: string\n /** Human-readable agent name from env/config (e.g. \"dev-agent-0\"). */\n agentName: string\n /** Path to the agent config file (for persisting renames). */\n readonly configPath: string\n readonly loadedAddons: Map<string, LoadedAddonEntry>\n /**\n * Resolver for the current metrics-provider cap. Looked up lazily so the\n * service still answers `$agent.status` even before the metrics addon has\n * finished initializing.\n */\n readonly getMetricsProvider?: () => IMetricsProvider | null\n /** Package version of the agent runtime (informational, surfaced via `$agent.health`). */\n readonly agentVersion?: string\n /**\n * Re-runs the agent's `loadDeployedAddons` discovery pass — picks up new\n * tarballs written under `addonsDir/` by `$agent.deploy` and spawns/wires\n * them without an agent restart. Returns the addon IDs that were freshly\n * loaded this pass. Wired by `agent-bootstrap.ts`; left optional so test\n * harnesses can build a service without driving the full bootstrap.\n */\n readonly reloadDeployedAddons?: () => Promise<readonly string[]>\n /**\n * Called when a group-runner child calls `$agent.registerNode`. The\n * agent-bootstrap wires this to aggregate the child's manifest into the\n * agent's subtree registry and re-register the complete union upward with\n * the hub. Optional so test harnesses can build the service without the\n * full bootstrap subtree machinery.\n */\n readonly onChildRegistered?: (params: RegisterNodeParams) => void\n /**\n * SHA-256 hash of the agent's own cluster secret, or `undefined` when none\n * is configured. When set, `$agent.registerNode` rejects a child runner not\n * presenting a matching `clusterSecretHash`. (Cluster-secret gate.)\n */\n readonly expectedClusterSecretHash?: string\n}\n\ninterface AgentConfigFileShape {\n readonly hubAddress?: string\n}\n\nfunction readHubAddressFromConfig(configPath: string): string | null {\n if (!configPath) return null\n try {\n if (!fs.existsSync(configPath)) return null\n const raw = JSON.parse(fs.readFileSync(configPath, 'utf-8')) as AgentConfigFileShape\n return typeof raw.hubAddress === 'string' && raw.hubAddress.length > 0 ? raw.hubAddress : null\n } catch {\n return null\n }\n}\n\n/**\n * Read the addon declaration ids contributed by a deployed package.\n *\n * `loadedAddons` is keyed by `camstack.addons[].id` (the declaration\n * id), not the package name. A redeploy's `$agent.deploy` param is the\n * package name, so the deploy handler reads the freshly-extracted\n * `package.json` to recover the real ids it must evict before\n * `$agent.reload`. Best-effort — returns `[]` on a missing/corrupt\n * manifest (the reload then just falls back to its on-disk scan).\n */\nfunction readDeployedAddonIds(addonDir: string): readonly string[] {\n try {\n const manifestPath = path.join(addonDir, 'package.json')\n if (!fs.existsSync(manifestPath)) return []\n const raw = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) as {\n camstack?: { addons?: readonly { id?: unknown }[] }\n }\n const entries = raw.camstack?.addons ?? []\n const ids: string[] = []\n for (const entry of entries) {\n if (typeof entry.id === 'string' && entry.id.length > 0) ids.push(entry.id)\n }\n return ids\n } catch {\n return []\n }\n}\n\nfunction isHubConnected(broker: BrokerLike): boolean {\n try {\n const registry = (broker as unknown as Record<string, unknown>).registry as\n | { getNodeList?: (opts: { onlyAvailable: boolean }) => readonly { id: string }[] }\n | undefined\n const nodes = registry?.getNodeList?.({ onlyAvailable: true }) ?? []\n return nodes.some((n) => n.id === 'hub')\n } catch {\n return false\n }\n}\n\nfunction createAgentService(deps: AgentServiceDeps): ServiceSchema {\n return {\n name: '$agent',\n actions: {\n status: {\n handler: async (ctx: Context) => {\n const { broker } = ctx as unknown as CtxLike<Record<string, never>>\n const cpus = os.cpus()\n let cpuPercent = 0\n let memoryPercent = 0\n const metrics = deps.getMetricsProvider?.()\n if (metrics) {\n const snapshot = await metrics.getCached()\n if (snapshot) {\n cpuPercent = snapshot.cpu.total\n memoryPercent = snapshot.memory.percent\n }\n }\n return {\n nodeId: broker.nodeID,\n name: deps.agentName,\n platform: os.platform(),\n arch: os.arch(),\n hostname: os.hostname(),\n cpuCores: cpus.length,\n cpuModel: cpus[0]?.model,\n totalMemoryMB: Math.round(os.totalmem() / 1024 / 1024),\n freeMemoryMB: Math.round(os.freemem() / 1024 / 1024),\n cpuPercent,\n memoryPercent,\n uptime: os.uptime(),\n localIps: getLocalIps(),\n addons: [...deps.loadedAddons.values()].map((a) => ({\n id: a.id,\n status: a.status,\n version: a.version,\n packageName: a.packageName,\n })),\n }\n },\n },\n\n health: {\n handler: async (ctx: Context): Promise<AgentHealth> => {\n const { broker } = ctx as unknown as CtxLike<Record<string, never>>\n let cpuPercent = 0\n let memoryPercent = 0\n const metrics = deps.getMetricsProvider?.()\n if (metrics) {\n try {\n const snapshot = await metrics.getCached()\n if (snapshot) {\n cpuPercent = snapshot.cpu.total\n memoryPercent = snapshot.memory.percent\n }\n } catch {\n /* metrics may be transiently unavailable */\n }\n }\n let total = 0\n let running = 0\n let errored = 0\n for (const a of deps.loadedAddons.values()) {\n total++\n if (a.status === 'running') running++\n else if (a.status === 'error') errored++\n }\n const hubAddress = readHubAddressFromConfig(deps.configPath)\n return {\n ok: errored === 0,\n nodeId: broker.nodeID,\n name: deps.agentName,\n version: deps.agentVersion ?? 'unknown',\n uptimeSeconds: Math.round(process.uptime()),\n pid: process.pid,\n hubConnected: isHubConnected(broker),\n hubAddress,\n addons: { total, running, error: errored },\n cpuPercent,\n memoryPercent,\n checkedAt: new Date().toISOString(),\n }\n },\n },\n\n shutdown: {\n handler() {\n // Graceful shutdown — schedule so the Moleculer response goes out first\n setTimeout(() => process.exit(0), 500)\n return { success: true }\n },\n },\n\n rename: {\n handler(ctx: Context<{ name: string }>) {\n const { params, broker } = ctx as unknown as CtxLike<{ name: string }>\n const newName = params.name\n if (!newName || typeof newName !== 'string') {\n throw new Error('$agent.rename: name is required')\n }\n const oldName = deps.agentName\n // Update in-memory name (affects subsequent $agent.status responses)\n deps.agentName = newName.trim()\n broker.logger.info(`Agent renamed: \"${oldName}\" → \"${deps.agentName}\"`)\n // Persist to config file\n try {\n const configFile = path.resolve(deps.configPath)\n let raw: Record<string, unknown> = {}\n if (fs.existsSync(configFile)) {\n try {\n raw = JSON.parse(fs.readFileSync(configFile, 'utf-8')) as Record<string, unknown>\n } catch {\n /* corrupt */\n }\n }\n raw.name = deps.agentName\n fs.mkdirSync(path.dirname(configFile), { recursive: true })\n fs.writeFileSync(configFile, JSON.stringify(raw, null, 2), 'utf-8')\n broker.logger.info(`Agent name persisted to ${configFile}`)\n } catch (err) {\n broker.logger.warn(\n 'Agent rename: config file write failed (in-memory rename still active)',\n { error: String(err) },\n )\n }\n return { success: true, name: deps.agentName }\n },\n },\n\n listAddons: {\n handler() {\n return [...deps.loadedAddons.keys()]\n },\n },\n\n deploy: {\n handler: async (\n ctx: Context<{\n addonId: string\n bundle: Buffer | string\n config?: Record<string, unknown>\n }>,\n ) => {\n const { params } = ctx as unknown as CtxLike<{\n addonId: string\n bundle: Buffer | string\n config?: Record<string, unknown>\n }>\n const { addonId, bundle } = params\n const addonDir = path.join(deps.addonsDir, addonId)\n\n fs.mkdirSync(deps.addonsDir, { recursive: true })\n\n const bundlePath = path.join(deps.addonsDir, `${addonId}.tgz`)\n const bufferData = typeof bundle === 'string' ? Buffer.from(bundle, 'base64') : bundle\n fs.writeFileSync(bundlePath, bufferData)\n\n // execFileSync (no shell) instead of execSync — addonId comes\n // from RPC params and was previously interpolated into a shell\n // command, which is a command-injection vector. argv form\n // doesn't go through a shell so any payload in addonId stays\n // a literal path arg.\n const { execFileSync } = await import('node:child_process')\n // Redeploys (per-node `updatePackage`) ship a NEW version of an\n // addon package that may already be loaded. Clear the stale\n // dir before extraction so removed/renamed files don't linger.\n if (fs.existsSync(addonDir)) {\n fs.rmSync(addonDir, { recursive: true, force: true })\n }\n fs.mkdirSync(addonDir, { recursive: true })\n execFileSync('tar', ['-xzf', bundlePath, '-C', addonDir, '--strip-components=1'], {\n timeout: 30000,\n })\n\n fs.unlinkSync(bundlePath)\n\n // Evict every addon DECLARATION id this package contributes\n // from `loadedAddons` so the follow-up `$agent.reload` actually\n // re-instantiates it. `loadDeployedAddons` skips any addon\n // still present in `loadedAddons`; without this eviction a\n // redeploy would leave the agent pinned to the pre-update\n // version. The `deploy` param `addonId` is the PACKAGE name\n // (used only as the on-disk dir), whereas `loadedAddons` is\n // keyed by the addon DECLARATION id — they differ for scoped\n // packages, so we read the extracted manifest to bridge them.\n for (const declId of readDeployedAddonIds(addonDir)) {\n deps.loadedAddons.delete(declId)\n }\n\n return { success: true, addonId, path: addonDir }\n },\n },\n\n /**\n * Genuinely drop a deployed addon from this agent.\n *\n * A plain `loadedAddons.delete()` only forgets the bookkeeping entry —\n * the addon instance keeps running and, crucially, its Moleculer\n * service keeps advertising the addon's capabilities into the cluster\n * (the hub still sees the provider as a live `<cap>@<agent>` entry).\n * To truly undeploy we must, in order:\n * 1. `shutdown()` the running instance (releases timers, sockets,\n * native handles) — same hook the agent's SIGTERM path uses.\n * 2. `broker.destroyService(addonId)` — the deployed-addon Moleculer\n * service is named after the addon DECLARATION id (see\n * `createAddonService` → `name: declaration.id`). Destroying it\n * unregisters every capability action so the cluster stops\n * routing to / advertising this agent's provider.\n * 3. Drop the `loadedAddons` entry and delete the on-disk folder.\n *\n * `addonId` here is the addon DECLARATION id — the same key\n * `loadedAddons` uses and the same value `$agent.status` reports, so\n * the hub reconciler can match it directly.\n */\n undeploy: {\n handler: async (ctx: Context<{ addonId: string }>) => {\n const { params, broker } = ctx as unknown as CtxLike<{ addonId: string }>\n const { addonId } = params\n const entry = deps.loadedAddons.get(addonId)\n\n // 1. Shut the running instance down (best-effort — a throwing\n // disposer must not block the rest of the teardown).\n if (entry?.addon?.shutdown) {\n try {\n await entry.addon.shutdown()\n } catch (err) {\n broker.logger.warn(`$agent.undeploy: ${addonId} shutdown() threw`, {\n error: String(err),\n })\n }\n }\n\n // 2. Destroy the Moleculer service so the cluster stops seeing\n // this agent's capability providers for the addon.\n try {\n await broker.destroyService(addonId)\n } catch {\n // Service may not exist (group-spawned addons have no per-addon\n // service on the agent broker, or it was never created).\n }\n\n // 3. Forget the entry and remove the deployed folder. The deploy\n // handler keys the on-disk dir by the PACKAGE name, but $agent\n // redeploys for a single addon use addonId === packageName for\n // unscoped packages; remove whichever folder matches.\n deps.loadedAddons.delete(addonId)\n const addonDir = path.join(deps.addonsDir, addonId)\n if (fs.existsSync(addonDir)) {\n fs.rmSync(addonDir, { recursive: true, force: true })\n }\n const pkgName = entry?.packageName\n if (pkgName && pkgName !== addonId) {\n const pkgDir = path.join(deps.addonsDir, pkgName)\n if (fs.existsSync(pkgDir)) {\n fs.rmSync(pkgDir, { recursive: true, force: true })\n }\n }\n\n broker.logger.info(`$agent.undeploy: ${addonId} disposed (instance + service + folder)`)\n return { success: true, addonId }\n },\n },\n\n /**\n * Re-run `loadDeployedAddons` so addons just dropped onto disk via\n * `$agent.deploy` (or via the hub broadcasting an upload) start\n * immediately. Without this the agent only discovers them on boot.\n * Returns the ids that were newly loaded — the hub uses this to\n * surface a per-agent diff in the upload response.\n */\n reload: {\n handler: async (): Promise<{ success: boolean; loaded: readonly string[] }> => {\n if (!deps.reloadDeployedAddons) {\n return { success: false, loaded: [] }\n }\n const loaded = await deps.reloadDeployedAddons()\n return { success: true, loaded }\n },\n },\n\n restart: {\n handler: async (ctx: Context<{ addonId: string }>) => {\n const { params } = ctx as unknown as CtxLike<{ addonId: string }>\n const { addonId } = params\n return { success: true, addonId, action: 'restart-queued' }\n },\n },\n\n /**\n * D3 subtree aggregation: a forked group-runner child calls this action\n * to deliver its complete capability manifest to the agent. The agent\n * then re-registers the UNION of its own in-process addons + all children\n * with the hub via `$hub.registerNode`.\n */\n registerNode: {\n handler(ctx: Context<RegisterNodeParams>): RegisterNodeResult {\n const { params } = ctx as unknown as CtxLike<RegisterNodeParams>\n // Cluster-secret gate: when the agent has a secret configured, a child\n // runner must present a matching hash or the registration is rejected.\n if (!clusterSecretMatches(deps.expectedClusterSecretHash, params.clusterSecretHash)) {\n throw new Errors.MoleculerError(\n `cluster secret mismatch — node \"${params.nodeId}\" rejected`,\n 403,\n CLUSTER_SECRET_MISMATCH_TYPE,\n )\n }\n deps.onChildRegistered?.(params)\n return { ok: true }\n },\n },\n },\n }\n}\n\nexport { createAgentService }\nexport type { AgentServiceDeps, LoadedAddonEntry }\n","/**\n * Agent HTTP server -- lightweight Fastify instance for agent status,\n * process management, config editing, and static UI serving.\n */\nimport Fastify from 'fastify'\nimport type { FastifyInstance } from 'fastify'\nimport type { ServiceBroker } from 'moleculer'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nexport interface AgentHttpConfig {\n readonly port: number\n readonly nodeId: string\n readonly dataDir: string\n readonly configPath: string\n /** Called when the UI requests a reconnect (hub/secret changed). */\n readonly onReconnect?: () => Promise<void>\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction resolveUiDistDir(dataDir: string): string | null {\n const candidates = [\n path.resolve(__dirname, '../../addon-agent-ui/dist'),\n path.join(dataDir, 'addons', '@camstack', 'addon-agent-ui', 'dist'),\n path.join(dataDir, 'agent-ui'),\n ]\n for (const dir of candidates) {\n if (fs.existsSync(path.join(dir, 'index.html'))) return dir\n }\n return null\n}\n\nfunction readConfigFile(configPath: string): Record<string, unknown> {\n if (!fs.existsSync(configPath)) return {}\n try {\n return JSON.parse(fs.readFileSync(configPath, 'utf-8')) as Record<string, unknown>\n } catch {\n return {}\n }\n}\n\nfunction writeConfigFile(configPath: string, data: Record<string, unknown>): void {\n fs.mkdirSync(path.dirname(configPath), { recursive: true })\n fs.writeFileSync(configPath, JSON.stringify(data, null, 2), 'utf-8')\n}\n\nfunction getRegistryNodes(broker: ServiceBroker): readonly { id: string }[] {\n try {\n const registry = (broker as unknown as Record<string, unknown>).registry as\n | { getNodeList?: (opts: { onlyAvailable: boolean }) => readonly { id: string }[] }\n | undefined\n return registry?.getNodeList?.({ onlyAvailable: true }) ?? []\n } catch {\n return []\n }\n}\n\n/**\n * Read the current effective config from the persisted file.\n * This is the single source of truth -- always reflects what the UI wrote.\n */\nfunction getEffectiveConfig(\n configPath: string,\n nodeId: string,\n): {\n name: string\n hubAddress: string | null\n hasSecret: boolean\n} {\n const raw = readConfigFile(configPath)\n return {\n name: typeof raw.name === 'string' ? raw.name : nodeId,\n hubAddress: typeof raw.hubAddress === 'string' ? raw.hubAddress : null,\n hasSecret: typeof raw.secret === 'string' && raw.secret.length > 0,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\nexport async function createAgentHttpServer(\n getBroker: () => ServiceBroker,\n config: AgentHttpConfig,\n): Promise<FastifyInstance> {\n const app = Fastify({ logger: false })\n\n const cors = await import('@fastify/cors')\n await app.register(cors.default)\n\n // -- Health ---------------------------------------------------------\n // Detailed shape lives on `$agent.health` (Moleculer action) so the\n // hub can call the same surface via `broker.call('$agent.health', {},\n // { nodeID })`. The HTTP route delegates so external monitors see the\n // identical payload.\n app.get('/health', async (_req, reply) => {\n try {\n const result = await getBroker().call('$agent.health')\n return result\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n return reply.status(503).send({\n ok: false,\n nodeId: config.nodeId,\n error: message,\n })\n }\n })\n\n // -- Agent status (enriched with connection state) ------------------\n\n app.get('/api/agent/status', async () => {\n const broker = getBroker()\n const eff = getEffectiveConfig(config.configPath, config.nodeId)\n const nodes = getRegistryNodes(broker)\n const hubConnected = nodes.some((n) => n.id === 'hub')\n const discoveryMode = !eff.hubAddress\n\n try {\n const status = (await broker.call('$agent.status')) as Record<string, unknown>\n return {\n ...status,\n name: eff.name,\n hubAddress: eff.hubAddress,\n hubConnected,\n discoveryMode,\n hasSecret: eff.hasSecret,\n discoveredNodes: nodes.filter((n) => n.id !== broker.nodeID).map((n) => n.id),\n }\n } catch {\n return {\n nodeId: config.nodeId,\n name: eff.name,\n hubAddress: eff.hubAddress,\n hubConnected,\n discoveryMode,\n hasSecret: eff.hasSecret,\n discoveredNodes: [],\n addons: [],\n localIps: [],\n }\n }\n })\n\n // -- Processes ------------------------------------------------------\n\n app.get('/api/agent/processes', async () => {\n try {\n return await getBroker().call('$process.list')\n } catch {\n return []\n }\n })\n\n // -- Addon restart --------------------------------------------------\n\n app.post<{ Body: { addonId: string } }>('/api/agent/addon/restart', async (req, reply) => {\n const addonId = req.body?.addonId\n if (!addonId) return reply.status(400).send({ error: 'addonId required' })\n return getBroker().call('$agent.restart', { addonId })\n })\n\n // -- Process restart ------------------------------------------------\n\n app.post<{ Body: { name: string } }>('/api/agent/process/restart', async (req, reply) => {\n const name = req.body?.name\n if (!name) return reply.status(400).send({ error: 'name required' })\n return getBroker().call('$process.restart', { name })\n })\n\n // -- Config read (always from file -- single source of truth) -------\n\n app.get('/api/agent/config', async () => {\n const persisted = readConfigFile(config.configPath)\n const eff = getEffectiveConfig(config.configPath, config.nodeId)\n return {\n nodeId: config.nodeId,\n name: eff.name,\n hubAddress: eff.hubAddress,\n hasSecret: eff.hasSecret,\n configPath: config.configPath,\n dataDir: config.dataDir,\n // Include all persisted fields except raw secret\n ...Object.fromEntries(Object.entries(persisted).filter(([k]) => k !== 'secret')),\n }\n })\n\n // -- Config write (merge-patch + persist) ---------------------------\n\n app.post<{ Body: Record<string, unknown> }>('/api/agent/config', async (req) => {\n const patch = req.body ?? {}\n const existing = readConfigFile(config.configPath)\n const merged = { ...existing, ...patch }\n writeConfigFile(config.configPath, merged)\n\n // Apply name change immediately (no reconnect needed)\n if (typeof patch.name === 'string' && patch.name.trim()) {\n try {\n await getBroker().call('$agent.rename', { name: patch.name.trim() })\n console.log(`[Agent] Name changed to \"${patch.name.trim()}\"`)\n } catch {\n /* best-effort */\n }\n }\n\n // Only flag reconnect when a connection-affecting field actually\n // CHANGED. The form posts every visible field on every Save, so\n // `patch.hubAddress !== undefined` was always true and produced\n // false-positive \"Restart required\" prompts on no-op saves.\n const reconnectRelevant: ReadonlyArray<keyof typeof patch> = ['hubAddress', 'secret']\n const needsReconnect = reconnectRelevant.some(\n (k) => Object.prototype.hasOwnProperty.call(patch, k) && patch[k] !== existing[k],\n )\n\n return {\n success: true,\n restartRequired: needsReconnect,\n }\n })\n\n // -- Agent reconnect (applies new hub/secret config) ----------------\n\n app.post('/api/agent/restart', async () => {\n if (!config.onReconnect) {\n return { success: false, message: 'Reconnect not available' }\n }\n console.log('[Agent] Reconnect requested from UI')\n void config.onReconnect().catch((err: unknown) => {\n console.error('[Agent] Reconnect failed:', err)\n })\n return { success: true, message: 'Agent reconnecting...' }\n })\n\n // -- Discovered nodes -----------------------------------------------\n\n app.get('/api/agent/discovered-nodes', async () => {\n const b = getBroker()\n const nodes = getRegistryNodes(b)\n return nodes.filter((n) => n.id !== b.nodeID).map((n) => ({ id: n.id, isHub: n.id === 'hub' }))\n })\n\n // -- Static file serving (agent-ui) ---------------------------------\n\n const uiDir = resolveUiDistDir(config.dataDir)\n if (uiDir) {\n const fastifyStatic = await import('@fastify/static')\n await app.register(fastifyStatic.default, {\n root: uiDir,\n prefix: '/',\n wildcard: false,\n decorateReply: false,\n })\n\n app.setNotFoundHandler(async (req, reply) => {\n if (req.url.startsWith('/api/') || req.url.startsWith('/health')) {\n return reply.status(404).send({ error: 'Not found' })\n }\n return reply.type('text/html').sendFile('index.html')\n })\n\n console.log(`[Agent] UI served from: ${uiDir}`)\n }\n\n return app\n}\n\n// ---------------------------------------------------------------------------\n// Start helper\n// ---------------------------------------------------------------------------\n\nexport async function startAgentHttpServer(\n getBroker: () => ServiceBroker,\n config: AgentHttpConfig,\n): Promise<void> {\n try {\n const app = await createAgentHttpServer(getBroker, config)\n await app.listen({ port: config.port, host: '0.0.0.0' })\n console.log(`[Agent] HTTP server: http://localhost:${config.port}`)\n } catch (err) {\n console.warn(`[Agent] HTTP server failed to start on port ${config.port}:`, err)\n }\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { startAgentHttpServer } from './agent-http.js'\nimport {\n createBroker,\n createAddonService,\n createAddonContext,\n createProcessService,\n deriveAgentListenPort,\n AddonLoader,\n AddonInstaller,\n detectWorkspacePackagesDir,\n CapabilityRegistry,\n INFRA_CAPABILITIES,\n registerEventBusService,\n createHwAccelService,\n createKernelHwAccel,\n HubNodeRegistry,\n callRegisterNodeWithRetry,\n buildNodeManifest,\n hashClusterSecret,\n isClusterSecretMismatchError,\n LocalChildRegistry,\n createLocalTransport,\n localEndpointPath,\n udsChildLogToWorkerEntry,\n createUdsEventBridge,\n getBrokerEventBus,\n getOrInitReadinessRegistry,\n createParentUnownedCallHandler,\n} from '@camstack/kernel'\nimport type {\n AddonContextOptions,\n RegisterNodeParams,\n RegisteredAddonManifest,\n ChildCapDescriptor,\n} from '@camstack/kernel'\nimport type {\n IStorageProvider,\n IScopedLogger,\n ILogDestination,\n IMetricsProvider,\n} from '@camstack/types'\nimport {\n normalizeAddonInitResult,\n isDeployableToAgent,\n resolveRunnerId,\n resolveAddonPlacement,\n} from '@camstack/types'\n// Capability definitions — must be declared before addon registerProvider() calls\nimport {\n storageCapability,\n storageProviderCapability,\n settingsStoreCapability,\n logDestinationCapability,\n metricsProviderCapability,\n decoderCapability,\n motionDetectionCapability,\n pipelineExecutorCapability,\n pipelineRunnerCapability,\n audioAnalyzerCapability,\n platformProbeCapability,\n errMsg,\n} from '@camstack/types'\nimport { loadAgentConfig } from './agent-config.js'\nimport { createAgentService } from './agent-service.js'\nimport type { LoadedAddonEntry } from './agent-service.js'\nimport { registerAgentCapDispatch } from './register-agent-cap-dispatch.js'\nimport type { ServiceBroker, ServiceSchema, Service } from 'moleculer'\n\n/**\n * Narrow interface for the Moleculer broker surface used in this file.\n * moleculer's index.d.ts chains through eventemitter2 whose package.json\n * has no `types` field; typescript-eslint's no-unsafe-* rules flag every\n * broker access. Cast once: `createBroker(...) as unknown as BrokerLike`.\n *\n * `createService` matches the real `ServiceBroker.createService` signature\n * so that `BrokerLike` is structurally compatible with `AgentServiceRegistrar`.\n */\ninterface BrokerLike {\n readonly nodeID: string\n start(): Promise<void>\n stop(): Promise<void>\n call<T = unknown>(action: string, params?: unknown, opts?: unknown): Promise<T>\n createService(schema: ServiceSchema, schemaMods?: Partial<ServiceSchema>): Service\n localBus: { on(event: string, handler: (data: unknown) => void): void }\n}\n\n// ---------------------------------------------------------------------------\n// Agent LogManager — shared log pipeline for all addon loggers.\n// The hub-forwarder addon registers as a destination, so all log\n// entries flow through it (console output + hub forwarding).\n// ---------------------------------------------------------------------------\nimport { LogManager } from '@camstack/core'\n\nconst agentLogManager = new LogManager(5000)\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\nasync function startAgent(configPath?: string): Promise<void> {\n const config = loadAgentConfig(configPath)\n\n if (config.hubAddress) {\n console.log(\n `[Agent] Starting node \"${config.nodeId}\" (name=\"${config.name}\") connecting to ${config.hubAddress}`,\n )\n } else {\n console.log(\n `[Agent] Starting node \"${config.nodeId}\" (name=\"${config.name}\") in discovery mode`,\n )\n }\n console.log(`[Agent] Config: ${config.configPath}`)\n console.log(`[Agent] Data dir: ${config.dataDir}`)\n\n // Ensure required addon packages are installed in the agent's addonsDir.\n // Resolution order:\n // 1. `CAMSTACK_BUNDLED_ADDONS_DIR` — Electron-packaged builds copy\n // from `<resourcesPath>/addons` (`'local'` mode).\n // 2. Otherwise npm from registry. Local dev pushes addons via\n // `camstack deploy` (CLI tarball upload), bypassing this path.\n const explicitSource = process.env['CAMSTACK_INSTALL_SOURCE'] as 'npm' | 'local' | undefined\n const bundledDir = process.env['CAMSTACK_BUNDLED_ADDONS_DIR']\n let workspaceDir: string | null = null\n let resolvedSource: typeof explicitSource = explicitSource\n if (bundledDir && fs.existsSync(bundledDir)) {\n workspaceDir = bundledDir\n resolvedSource = 'local'\n console.log(`[Agent] Using bundled addons from ${bundledDir}`)\n } else if (explicitSource === 'local') {\n workspaceDir = detectWorkspacePackagesDir(config.dataDir)\n }\n const installer = new AddonInstaller({\n addonsDir: config.addonsDir,\n workspacePackagesDir: workspaceDir ?? undefined,\n installSource: resolvedSource,\n })\n await installer.ensureRequiredPackages(AddonInstaller.AGENT_PACKAGES)\n console.log(`[Agent] Addon packages ready in ${config.addonsDir}`)\n\n let broker: BrokerLike = createBroker({\n nodeID: config.nodeId,\n mode: 'agent',\n hubAddress: config.hubAddress,\n logLevel: config.logLevel,\n secret: config.secret,\n }) as unknown as BrokerLike\n\n /**\n * Reconnect: stop the current broker, reload config from file,\n * create a new broker with the updated hub/secret, and restart.\n * The HTTP server stays alive throughout.\n */\n const reconnect = async (): Promise<void> => {\n console.log('[Agent] Reconnecting with updated config...')\n try {\n await broker.stop()\n } catch {\n /* already stopped */\n }\n\n // Reload config from file (UI may have written new hubAddress/secret)\n const fresh = loadAgentConfig(undefined, config.dataDir)\n console.log(\n `[Agent] New config: hub=${fresh.hubAddress ?? 'discovery'}, secret=${fresh.secret ? 'yes' : 'none'}`,\n )\n\n broker = createBroker({\n nodeID: config.nodeId,\n mode: 'agent',\n hubAddress: fresh.hubAddress,\n logLevel: fresh.logLevel,\n secret: fresh.secret,\n }) as unknown as BrokerLike\n await broker.start()\n console.log('[Agent] Reconnected successfully')\n }\n\n const loadedAddons = new Map<string, LoadedAddonEntry>()\n\n // D3 subtree registry: accumulates manifests from every group-runner child\n // that calls `$agent.registerNode`. The agent re-registers the UNION of its\n // own in-process addons + all children with the hub on every child update.\n const subtree = new HubNodeRegistry()\n\n // AbortController for the upward hub registration retry loop — cancelled on\n // agent shutdown so we don't leak the retry loop after stop().\n const registerAbortController = new AbortController()\n\n /**\n * Build the manifest for the agent's OWN in-process addons (those loaded by\n * bootCoreAddons and loadDeployedAddons that ended up in `loadedAddons` WITH\n * an `addon` instance). Group-spawned addons (loadClusterCapableAddons) have\n * no `addon` instance and register via `$agent.registerNode` from the child.\n *\n * We reconstruct per-addon capability lists by reverse-querying the\n * capabilityRegistry: for every declared capability, check which addonId\n * currently holds the provider and group the result by addonId.\n */\n function buildAgentOwnManifest(): readonly RegisteredAddonManifest[] {\n // Build addonId → capNames map from the registry.\n const addonCapMap = new Map<string, string[]>()\n\n for (const cap of agentCapabilities) {\n if (cap.mode === 'collection') {\n // Collection caps: multiple providers keyed by addonId.\n for (const [addonId] of capabilityRegistry.getCollectionEntries(cap.name)) {\n const list = addonCapMap.get(addonId) ?? []\n list.push(cap.name)\n addonCapMap.set(addonId, list)\n }\n } else {\n // Singleton caps: one active provider.\n const addonId = capabilityRegistry.getSingletonAddonId(cap.name)\n if (addonId) {\n const list = addonCapMap.get(addonId) ?? []\n list.push(cap.name)\n addonCapMap.set(addonId, list)\n }\n }\n }\n\n // Only emit in-process addons (those with an `addon` instance).\n const result: RegisteredAddonManifest[] = []\n for (const [addonId, entry] of loadedAddons) {\n if (!entry.addon) continue\n const caps = addonCapMap.get(addonId) ?? []\n result.push({ addonId, capabilities: caps })\n }\n return result\n }\n\n /**\n * Aggregate the agent's own manifest + every child's manifest into a single\n * RegisterNodeParams for the agent's nodeId. This is sent upward to the hub.\n */\n function aggregateManifest(): RegisterNodeParams {\n const ownAddons = buildAgentOwnManifest()\n const allAddons: RegisteredAddonManifest[] = [...ownAddons]\n const allNativeCaps = []\n\n for (const childNodeId of subtree.listNodeIds()) {\n const childAddons = subtree.getNodeManifest(childNodeId)\n if (childAddons) {\n allAddons.push(...childAddons)\n }\n const childNativeCaps = subtree.getNodeNativeCaps(childNodeId)\n if (childNativeCaps) {\n allNativeCaps.push(...childNativeCaps)\n }\n }\n\n return buildNodeManifest(\n config.nodeId,\n allAddons,\n allNativeCaps.length > 0 ? allNativeCaps : undefined,\n config.secret ? hashClusterSecret(config.secret) : undefined,\n )\n }\n\n /**\n * Fire (or re-fire) the upward `$hub.registerNode` registration carrying\n * the agent's complete subtree union. Called:\n * - after initial addon loading completes\n * - on every `$agent.registerNode` from a child\n * - on hub reconnect (via `$node.connected`)\n */\n function triggerUpwardRegistration(): void {\n callRegisterNodeWithRetry(\n broker as unknown as Parameters<typeof callRegisterNodeWithRetry>[0],\n aggregateManifest(),\n {\n target: 'hub',\n signal: registerAbortController.signal,\n log: (msg) => console.log(`[Agent] ${msg}`),\n },\n ).catch((err: unknown) => {\n if (isClusterSecretMismatchError(err)) {\n consoleLogger.error(\n 'hub registration rejected: cluster secret mismatch — correct CAMSTACK_CLUSTER_SECRET and restart the agent',\n )\n return\n }\n // abort (shutdown) — preserve prior void behaviour: ignore.\n })\n }\n\n const consoleLogger: IScopedLogger = {\n info: (msg) => console.log(`[Agent] ${msg}`),\n warn: (msg) => console.warn(`[Agent] ${msg}`),\n error: (msg) => console.error(`[Agent] ${msg}`),\n debug: (msg) => console.debug(`[Agent] ${msg}`),\n child: () => consoleLogger,\n withTags: () => consoleLogger,\n }\n const capabilityRegistry = new CapabilityRegistry(consoleLogger)\n\n // Declare all capabilities the agent uses — required before registerProvider()\n const agentCapabilities = [\n storageCapability,\n storageProviderCapability,\n settingsStoreCapability,\n logDestinationCapability,\n metricsProviderCapability,\n decoderCapability,\n motionDetectionCapability,\n pipelineExecutorCapability,\n pipelineRunnerCapability,\n audioAnalyzerCapability,\n platformProbeCapability,\n ]\n for (const cap of agentCapabilities) {\n capabilityRegistry.declareCapability(cap)\n }\n\n // Logger factory — creates scoped loggers from the shared LogManager.\n // All entries flow through registered ILogDestination providers (hub-forwarder).\n // No scope — the brand bracket `[agent/addonId]` already identifies the addon.\n // `addonId` tag is required for the brand bracket resolver.\n const loggerFactory = (addonId: string) => agentLogManager.createLogger().withTags({ addonId })\n\n const agentServiceSchema = createAgentService({\n addonsDir: config.addonsDir,\n dataDir: config.dataDir,\n agentName: config.name,\n configPath: config.configPath,\n loadedAddons,\n // Resolve the current metrics-provider cap lazily from the registry.\n // The cap is registered during bootCoreAddons but may not exist in test\n // scenarios — `null` is a documented fallback for both.\n getMetricsProvider: () => capabilityRegistry.getSingleton<IMetricsProvider>('metrics-provider'),\n agentVersion: readAgentVersion(),\n // Drives `$agent.reload` — re-runs the same discovery pass the bootstrap\n // does at startup, picking up tarballs just landed via `$agent.deploy`.\n // `storageProvider` is resolved lazily on each call because this closure\n // is captured here, BEFORE `bootCoreAddons` registers the storage cap.\n reloadDeployedAddons: async (): Promise<readonly string[]> => {\n const before = new Set(loadedAddons.keys())\n const storage = capabilityRegistry.getSingleton<IStorageProvider>('storage') ?? undefined\n await loadDeployedAddons(\n broker,\n config.addonsDir,\n config.dataDir,\n loadedAddons,\n storage,\n loggerFactory,\n capabilityRegistry,\n )\n const loaded: string[] = []\n for (const id of loadedAddons.keys()) {\n if (!before.has(id)) loaded.push(id)\n }\n return loaded\n },\n // D3 subtree aggregation: when a group-runner child delivers its manifest\n // via `$agent.registerNode`, merge it into the local subtree registry and\n // immediately re-register the complete union with the hub.\n onChildRegistered: (params: RegisterNodeParams): void => {\n subtree.registerNode(params)\n triggerUpwardRegistration()\n },\n expectedClusterSecretHash: config.secret ? hashClusterSecret(config.secret) : undefined,\n })\n broker.createService(agentServiceSchema)\n\n // $process service — manages forked child processes (same as hub).\n // Pass the agent's own TCP listen port so spawned addon-runners connect\n // back here instead of falling back to port 6000 (the hub). Bug-4:\n // without this, runners called `$agent.registerNode` through the hub\n // which has no `$agent` service → retry storm (attempt 80+).\n const agentTcpPort = deriveAgentListenPort(broker.nodeID)\n\n // UDS local transport — the agent hosts a LocalChildRegistry so its\n // forked addon-runners route cap calls over a Unix-domain socket. The\n // broker stays as the no-route fallback. On failure the children fall\n // back to broker-only (no parentUdsPath propagated). See moleculer.service.ts.\n let agentParentUdsPath: string | undefined\n let agentUdsRegistry: LocalChildRegistry | undefined\n try {\n const agentNodeId = broker.nodeID\n // F0 (slice-5 outbound): route a forked child's unowned `ctx.api.<cap>`\n // call from the agent (the parent) instead of throwing UDS_NO_ROUTE. The\n // agent has NO CapRouteResolver — it routes everything to the hub over its\n // own broker — so `getResolver` returns null and the handler uses the\n // broker fallback exclusively. The agent's `broker.call` reaches the hub\n // mesh (and any cluster node) via the same service-discovery + action-name\n // convention the child's brokerTransportLink used before F0. F1+F2 removes\n // the child broker, so the agent must own this outbound path.\n const onUnownedCall = createParentUnownedCallHandler({\n getResolver: () => null,\n broker: broker as unknown as ServiceBroker,\n // The agent's subtree registry — lets the broker fallback pin a\n // device-scoped child call to the owning node instead of load-balancing.\n nodeRegistry: subtree,\n // Hub-local UDS child dispatcher — routes a device-scoped native cap\n // owned by an agent-local child directly over UDS before any broker\n // fallback. Getter: `agentUdsRegistry` is assigned later in this scope,\n // after the handler is constructed.\n getLocalDispatcher: () => agentUdsRegistry ?? null,\n logger: { warn: (msg) => consoleLogger.warn(`[uds-fallback] ${msg}`) },\n })\n agentUdsRegistry = new LocalChildRegistry({\n server: createLocalTransport().createServer(agentNodeId),\n onUnownedCall,\n })\n await agentUdsRegistry.start()\n // E1: apply child manifest + cleanup from the UDS lifecycle (agent-local children).\n // When a runner connects over UDS, synthesise a RegisterNodeParams for the\n // agent's subtree and trigger upward hub registration — same effect as the\n // `$agent.registerNode` Moleculer RPC path. Idempotent: if the Moleculer path\n // fires first, the subtree's atomic-replace handles the re-registration safely.\n agentUdsRegistry.onChildRegistered((child) => {\n const childNodeId = `${agentNodeId}/${child.childId}`\n const childParams = buildAgentChildUdsManifest(childNodeId, child.childId, child.caps)\n subtree.registerNode(childParams)\n triggerUpwardRegistration()\n consoleLogger.info(`UDS child registered — subtree updated: ${childNodeId}`)\n })\n agentUdsRegistry.onChildGone((childId) => {\n const childNodeId = `${agentNodeId}/${childId}`\n subtree.removeNode(childNodeId)\n triggerUpwardRegistration()\n consoleLogger.info(`UDS child gone — subtree updated: ${childNodeId}`)\n })\n // B2: forward UDS child logs onward to the hub's log-receiver service so\n // they appear in the hub LogManager / admin-UI log stream.\n //\n // The agent has NO local LogManager readable by the admin-UI — all log\n // entries must reach the hub. We re-use the same `log-receiver.ingest`\n // Moleculer call that `HubForwarderDestination` uses for the agent's OWN\n // logs, but preserve the child's original `addonId`/`nodeId`/`tags` so\n // the admin-UI shows the originating addon (not the agent's identity).\n //\n // If the hub is not yet reachable, the call silently fails — the same\n // best-effort semantic as `HubForwarderDestination.forward`. Phase F will\n // retire the broker path once every log source emits over UDS end-to-end.\n agentUdsRegistry.onChildLog((childId, entry) => {\n const workerEntry = udsChildLogToWorkerEntry(childId, entry)\n broker.call('log-receiver.ingest', workerEntry).catch(() => {\n // Hub unreachable or not yet discovered — silently drop.\n // HubForwarderDestination handles the agent's own buffered logs;\n // child UDS logs emitted before hub connection are not buffered here.\n })\n })\n agentParentUdsPath = localEndpointPath(agentNodeId)\n consoleLogger.info(`UDS child registry listening on ${agentParentUdsPath}`)\n } catch (err) {\n consoleLogger.warn(\n `UDS child registry failed to start; children stay broker-only: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n\n const processServiceSchema = createProcessService(\n broker.nodeID,\n config.dataDir,\n undefined,\n agentTcpPort,\n agentParentUdsPath,\n )\n broker.createService(processServiceSchema)\n\n // Slice-5, Task 7 — register the agent-side cap-dispatch service so the hub\n // can route `agent-child-forward` cap calls to this agent's UDS children.\n // Registered only when the UDS child registry started successfully; if it\n // didn't start the service would have nothing to forward to.\n registerAgentCapDispatch(broker, agentUdsRegistry, consoleLogger)\n\n // $addonHost — REMOVED (Sprint 6). Three-level settings are now\n // exposed per-addon via `settings.*` actions in createAddonService.\n\n // Register $event-bus BEFORE start so the service subscription\n // is announced during discovery. See addon-context-factory.ts for\n // the rationale — post-start registration propagates via heartbeat\n // and is unreliable for the first ~10s.\n registerEventBusService(broker as unknown as ServiceBroker)\n\n // Per-node `$hwaccel` — exposes this agent's local hwaccel probe to\n // the rest of the cluster. Hub UI calls\n // `broker.call('$hwaccel.resolve', {}, { nodeID: '<agent>' })` to\n // render the pipeline-page \"hardware acceleration\" column.\n broker.createService(createHwAccelService(createKernelHwAccel()))\n\n // Start broker BEFORE bootCoreAddons. Core infra addons (storage,\n // sqlite-settings, ...) run their own BaseAddon.initialize() which\n // calls `ctx.settings.readAddonStore()` on every addon; that call\n // routes through `brokerTransportLink` and would deadline-free poll\n // for the hub's `settings-store.get` service if the broker weren't\n // connected to the mesh yet. Starting the broker first lets service\n // discovery resolve (or time out cleanly via the read-blob fallback)\n // so agent boot completes instead of hanging on the first infra addon.\n await broker.start()\n\n // C2: wire the UDS ↔ Moleculer event bridge so events emitted by UDS\n // children fan to siblings and reach the cluster, and cluster / parent-\n // local events propagate to every UDS child. Inert when agentUdsRegistry\n // was not started (no UDS children). Wired after broker.start() so\n // getBrokerEventBus returns the fully operational shared bus.\n let udsEventBridgeDispose: (() => void) | null = null\n if (agentUdsRegistry !== undefined) {\n const agentBrokerEventBus = getBrokerEventBus(broker as unknown as ServiceBroker)\n udsEventBridgeDispose = createUdsEventBridge({\n registry: agentUdsRegistry,\n parentBus: agentBrokerEventBus,\n parentNodeId: broker.nodeID,\n })\n // D1: answer readiness-snapshot requests from UDS children over the\n // agent's own readiness registry view. The agent hydrates its registry\n // from the hub's `$readiness.getSnapshot` Moleculer action (broker\n // path, intact until Phase F) — its snapshot reflects the hub's\n // authoritative view plus any agent-local readiness events. Children\n // request the snapshot over UDS without an additional Moleculer hop.\n // `getOrInitReadinessRegistry` is the same function addon-context-factory\n // uses, so the shared per-broker instance is returned on the first call\n // and reused on subsequent calls — no separate registry is created.\n // Wired after broker.start() so the broker event bus is operational.\n const agentReadinessRegistry = getOrInitReadinessRegistry(\n broker as unknown as ServiceBroker,\n agentBrokerEventBus,\n consoleLogger,\n )\n agentUdsRegistry.onReadinessSnapshotRequest(() =>\n agentReadinessRegistry.getSnapshotForTransport(),\n )\n }\n\n // ── HTTP server (Fastify) — status API + process management + UI ──\n // Started early (before addon boot) so the status page is reachable\n // even while addons are still loading.\n const getBrokerFn = (() => broker) as unknown as () => ServiceBroker\n void startAgentHttpServer(getBrokerFn, {\n port: config.statusPort ?? 4444,\n nodeId: config.nodeId,\n dataDir: config.dataDir,\n configPath: config.configPath,\n onReconnect: reconnect,\n })\n\n // ── Phase 1: Load core infrastructure addons (in-process) ──\n // storage + settings + metrics + hub-forwarder (log destination)\n await bootCoreAddons(broker, config, capabilityRegistry, loadedAddons, loggerFactory)\n\n // Plug every registered `log-destination` provider into the shared\n // LogManager. The LogManager replays its ring buffer to each destination\n // on registration, so boot-time log entries still reach destinations that\n // came up mid-boot (e.g. hub-forwarder arriving after the first logs).\n for (const dest of capabilityRegistry.getCollection<ILogDestination>('log-destination')) {\n agentLogManager.addDestination(dest)\n }\n\n // Everything downstream reads the resolved infra providers from the\n // capability registry — no hand-curated side-channel.\n const storageProvider = capabilityRegistry.getSingleton<IStorageProvider>('storage') ?? undefined\n\n // `ctx.api` for every addon is built inside `createAddonContext` from\n // `[localProviderLink, brokerTransportLink(broker)]`. Unresolved calls\n // route via Moleculer to the hub (or any other node hosting the cap).\n // No separate hub WSS client, no `CAMSTACK_HUB_API_URL` — all cross-node\n // traffic rides the broker mesh.\n\n // ── Phase 1.5: Load cluster-capable addon packages (forkable → child process) ──\n await loadClusterCapableAddons(broker, config, capabilityRegistry, loadedAddons)\n\n // ── Phase 2: Load deployed addons (from hub $agent.deploy) ──\n await loadDeployedAddons(\n broker,\n config.addonsDir,\n config.dataDir,\n loadedAddons,\n storageProvider,\n loggerFactory,\n capabilityRegistry,\n )\n\n // ── D3: Fire the initial upward hub registration carrying the agent's\n // complete in-process manifest (+ any children that already registered).\n // Group-runner children call `$agent.registerNode` and trigger a re-fire\n // via `onChildRegistered`; hub reconnects re-fire via `$node.connected`.\n triggerUpwardRegistration()\n\n // Fire `onHubReachable()` on every in-process addon as soon as the hub\n // node connects to our broker — this is the safe point for ctx.api.* calls\n // into hub-provided capabilities. Forked children get the same hook via\n // `process-runner.ts`.\n let hubReachableFired = false\n broker.localBus.on('$node.connected', (data: unknown) => {\n const node = (data as { node?: { id?: string } }).node\n if (node?.id !== 'hub') return\n // Re-register with the hub on reconnect — the hub may have restarted and\n // lost the agent's manifest. D3 idempotent-replace: safe to re-fire.\n triggerUpwardRegistration()\n if (hubReachableFired) return\n hubReachableFired = true\n for (const [, entry] of loadedAddons) {\n if (!entry.addon || typeof entry.addon.onHubReachable !== 'function') continue\n Promise.resolve(entry.addon.onHubReachable()).catch((err: unknown) => {\n console.error(`[Agent] ${entry.id} onHubReachable() threw:`, err)\n })\n }\n })\n\n console.log(\n `[Agent] Node \"${config.nodeId}\" (name=\"${config.name}\") ready — ${loadedAddons.size} addon(s) loaded`,\n )\n\n // Graceful shutdown\n const shutdown = async () => {\n console.log('[Agent] Shutting down...')\n // Abort any in-flight upward hub registration retry loop.\n registerAbortController.abort()\n // Dispose the UDS event bridge to remove the parent-bus subscriber and\n // clear the child-event handler, preventing subscriber leaks on shutdown.\n udsEventBridgeDispose?.()\n udsEventBridgeDispose = null\n for (const [, entry] of loadedAddons) {\n if (entry.addon?.shutdown) {\n try {\n await entry.addon.shutdown()\n } catch {\n /* */\n }\n }\n }\n await broker.stop()\n process.exit(0)\n }\n process.on('SIGTERM', shutdown)\n process.on('SIGINT', shutdown)\n}\n\n// ---------------------------------------------------------------------------\n// Phase 1: Boot core addons (storage, settings, metrics — NO winston)\n// ---------------------------------------------------------------------------\n\n// Core infra addons to load on agent — all infra including log-destination (hub-forwarder)\nconst AGENT_INFRA = INFRA_CAPABILITIES\n\nasync function bootCoreAddons(\n broker: BrokerLike,\n config: { dataDir: string; addonsDir: string },\n registry: CapabilityRegistry,\n loadedAddons: Map<string, LoadedAddonEntry>,\n loggerFactory: (addonId: string) => IScopedLogger,\n): Promise<void> {\n // Scan every installed addon package — infra providers may live outside\n // `@camstack/core` (e.g. `@camstack/addon-platform-probe-native`).\n const packageDirs = resolveAddonPackageDirs(config.addonsDir)\n if (packageDirs.length === 0) {\n console.warn('[Agent] No addon packages found — running without infrastructure addons')\n return\n }\n\n console.log(`[Agent] Scanning ${packageDirs.length} addon package(s) for infra providers`)\n const loader = new AddonLoader()\n for (const dir of packageDirs) {\n try {\n await loader.loadFromAddonDir(dir)\n } catch (err) {\n console.warn(`[Agent] Failed to scan ${dir}: ${errMsg(err)}`)\n }\n }\n\n for (const infra of AGENT_INFRA) {\n const candidates = loader.listAddons().filter((a) =>\n a.declaration.capabilities?.some((c) => {\n const capName = typeof c === 'string' ? c : c.name\n return capName === infra.name\n }),\n )\n // For log-destination, prefer hub-forwarder over winston-logging\n const addon =\n infra.name === 'log-destination'\n ? (candidates.find((a) => a.declaration.id === 'hub-forwarder') ?? candidates[0])\n : candidates[0]\n if (!addon) {\n if (infra.required) {\n console.error(`[Agent] Required infrastructure addon for \"${infra.name}\" not found`)\n }\n continue\n }\n\n const addonId = addon.declaration.id\n try {\n const instance = new addon.addonClass()\n // Seed ctx.kernel.storage from whatever storage provider is already\n // in the registry (the storage addon declares itself before the\n // addons that depend on it per AGENT_INFRA order).\n const storageProvider = registry.getSingleton<IStorageProvider>('storage') ?? undefined\n const context = await createAddonContext(\n broker as unknown as ServiceBroker,\n addon.declaration,\n config.dataDir,\n {\n storageProvider,\n addonConfig: { rootPath: config.dataDir },\n createLogger: loggerFactory,\n capabilityRegistry: registry,\n },\n )\n\n const initResult = normalizeAddonInitResult(await instance.initialize(context))\n\n for (const reg of initResult?.providers ?? []) {\n const capName = reg.capability.name\n registry.registerProvider(capName, addonId, reg.provider)\n // Also register in the per-broker context registry\n context.registerProvider(capName, reg.provider)\n }\n\n loadedAddons.set(addonId, {\n id: addonId,\n status: 'running',\n version: addon.declaration.version,\n packageName: addon.packageName,\n addon: instance,\n })\n\n console.log(`[Agent] Core addon \"${addonId}\" initialized`)\n } catch (err) {\n const msg = errMsg(err)\n console.error(`[Agent] Failed to initialize core addon \"${addonId}\": ${msg}`)\n if (infra.required) {\n throw new Error(`Required infrastructure addon \"${addonId}\" failed: ${msg}`, { cause: err })\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Phase 1.5: Load cluster-capable addon packages\n// ---------------------------------------------------------------------------\n\nasync function loadClusterCapableAddons(\n broker: BrokerLike,\n config: { dataDir: string; addonsDir: string },\n capabilityRegistry: CapabilityRegistry,\n loadedAddons: Map<string, LoadedAddonEntry>,\n): Promise<void> {\n const addonPackageDirs = resolveAddonPackageDirs(config.addonsDir)\n if (addonPackageDirs.length === 0) return\n\n // ── Phase 0 (cross-package): collect every group-eligible addon\n // across ALL package dirs FIRST so we issue exactly one\n // `$process.spawnGroup` per group. Doing this inside the per-dir\n // loop spawned the same group multiple times — Moleculer rejected\n // subsequent attempts with \"already running\" and the surviving\n // subprocess only had the first dir's subset of addons.\n type GroupCandidate = {\n readonly groupId: string\n readonly addonId: string\n readonly addonDir: string\n readonly version: string\n readonly packageName: string\n readonly capabilities: ReadonlyArray<string | { readonly name: string }>\n }\n const allGroupCandidates: GroupCandidate[] = []\n // Loaders are reused below for the per-addon legacy path; cache them\n // so each dir is parsed once.\n const dirToLoader = new Map<string, AddonLoader>()\n\n for (const dir of addonPackageDirs) {\n const loader = new AddonLoader()\n try {\n await loader.loadFromAddonDir(dir)\n } catch (err) {\n console.warn(`[Agent] Skipping ${dir}: ${errMsg(err)}`)\n continue\n }\n dirToLoader.set(dir, loader)\n\n for (const registered of loader.listAddons()) {\n if (loadedAddons.has(registered.declaration.id)) continue\n if (registered.declaration.execution === undefined) continue\n const placement = resolveAddonPlacement(registered.declaration)\n if (placement === 'hub-only') continue\n allGroupCandidates.push({\n groupId: resolveRunnerId(registered.declaration, registered.declaration.id),\n addonId: registered.declaration.id,\n addonDir: dir,\n version: registered.declaration.version ?? '0.0.0',\n packageName: registered.packageName,\n capabilities: registered.declaration.capabilities ?? [],\n })\n }\n }\n\n if (allGroupCandidates.length > 0) {\n const grouped = new Map<string, GroupCandidate[]>()\n for (const c of allGroupCandidates) {\n const arr = grouped.get(c.groupId) ?? []\n arr.push(c)\n grouped.set(c.groupId, arr)\n }\n for (const [groupId, addons] of grouped) {\n try {\n await broker.call('$process.spawnRunner', {\n runnerId: groupId,\n addons: addons.map((a) => ({ addonId: a.addonId, addonDir: a.addonDir })),\n })\n for (const a of addons) {\n for (const cap of a.capabilities) {\n const capName = typeof cap === 'string' ? cap : cap.name\n const proxy = new Proxy<Record<string, unknown>>(\n {},\n {\n get(_target, prop: string) {\n if (prop === 'then' || typeof prop === 'symbol') return undefined\n return (params: unknown) => broker.call(`${a.addonId}.${capName}.${prop}`, params)\n },\n },\n )\n capabilityRegistry.registerProvider(capName, a.addonId, proxy)\n }\n loadedAddons.set(a.addonId, {\n id: a.addonId,\n status: 'running',\n version: a.version,\n packageName: a.packageName,\n })\n }\n console.log(\n `[Agent] Group \"${groupId}\" spawned with ${addons.length} addon(s): ${addons.map((a) => a.addonId).join(', ')}`,\n )\n } catch (err) {\n const msg = err instanceof Error ? (err.stack ?? err.message) : String(err)\n console.error(`[Agent] Failed to spawn group \"${groupId}\": ${msg}`)\n for (const a of addons) {\n loadedAddons.set(a.addonId, { id: a.addonId, status: 'error' })\n }\n }\n }\n }\n\n // Diagnostic — list addons that exist in the agent's package dir but\n // were filtered out by Phase 0 (placement=hub-only or no execution).\n for (const dir of addonPackageDirs) {\n const loader = dirToLoader.get(dir)\n if (!loader) continue\n for (const registered of loader.listAddons()) {\n const addonId = registered.declaration.id\n if (loadedAddons.has(addonId)) continue\n if (!isDeployableToAgent(registered.declaration)) continue\n console.warn(\n `[Agent] Addon \"${addonId}\" is deployable but missing from any spawned group — verify package.json execution field`,\n )\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Phase 2: Load deployed addons (pushed from hub via $agent.deploy)\n// ---------------------------------------------------------------------------\n\nasync function loadDeployedAddons(\n broker: BrokerLike,\n addonsDir: string,\n dataDir: string,\n loadedAddons: Map<string, LoadedAddonEntry>,\n storageProvider: IStorageProvider | undefined,\n loggerFactory: (addonId: string) => IScopedLogger,\n capabilityRegistry?: CapabilityRegistry,\n): Promise<void> {\n if (!fs.existsSync(addonsDir)) return\n\n const loader = new AddonLoader()\n await loader.loadFromDirectory(addonsDir)\n\n const modelsDir = storageProvider\n ? await storageProvider.resolve({ location: 'models', relativePath: '' }).catch(() => undefined)\n : undefined\n const contextOptions: AddonContextOptions = {\n storageProvider,\n addonConfig: modelsDir ? { modelsDir } : {},\n createLogger: loggerFactory,\n capabilityRegistry,\n }\n\n for (const registered of loader.listAddons()) {\n const addonId = registered.declaration.id\n if (loadedAddons.has(addonId)) continue\n if (!isDeployableToAgent(registered.declaration)) continue\n\n try {\n const instance = new registered.addonClass()\n const context = await createAddonContext(\n broker as unknown as ServiceBroker,\n registered.declaration,\n dataDir,\n contextOptions,\n )\n await instance.initialize(context)\n\n const serviceSchema = createAddonService(instance, registered.declaration)\n broker.createService(serviceSchema)\n\n loadedAddons.set(addonId, {\n id: addonId,\n status: 'running',\n version: registered.declaration.version,\n packageName: registered.packageName,\n addon: instance,\n })\n\n console.log(`[Agent] Deployed addon \"${addonId}\" loaded`)\n } catch (err) {\n const msg = errMsg(err)\n console.error(`[Agent] Failed to load deployed addon \"${addonId}\": ${msg}`)\n loadedAddons.set(addonId, { id: addonId, status: 'error' })\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Read the agent's own package.json version (best-effort). */\nfunction readAgentVersion(): string {\n // package.json sits two dirs up from `dist/`. Walk up until we hit it.\n const candidates = [\n path.resolve(__dirname, '..', 'package.json'),\n path.resolve(__dirname, '..', '..', 'package.json'),\n ]\n for (const candidate of candidates) {\n try {\n if (!fs.existsSync(candidate)) continue\n const raw = JSON.parse(fs.readFileSync(candidate, 'utf-8')) as {\n name?: string\n version?: string\n }\n if (raw.name === '@camstack/agent' && typeof raw.version === 'string') return raw.version\n } catch {\n /* keep searching */\n }\n }\n return 'unknown'\n}\n\n/** Check if path is a directory (follows symlinks) */\nfunction isDir(p: string): boolean {\n try {\n return fs.statSync(p).isDirectory()\n } catch {\n return false\n }\n}\n\n/** Scan addonsDir for addon packages (scoped and unscoped, follows symlinks) */\nfunction resolveAddonPackageDirs(addonsDir: string): string[] {\n const dirs: string[] = []\n if (!fs.existsSync(addonsDir)) return dirs\n\n for (const name of fs.readdirSync(addonsDir)) {\n const full = path.join(addonsDir, name)\n if (name.startsWith('@') && isDir(full)) {\n // Scoped packages: @camstack/addon-xyz\n for (const sub of fs.readdirSync(full)) {\n const subFull = path.join(full, sub)\n if (isDir(subFull) && fs.existsSync(path.join(subFull, 'package.json'))) {\n dirs.push(subFull)\n }\n }\n } else if (isDir(full) && fs.existsSync(path.join(full, 'package.json'))) {\n dirs.push(full)\n }\n }\n\n return dirs\n}\n\n// ---------------------------------------------------------------------------\n// E1 helper — agent-local UDS child manifest adaptation\n// ---------------------------------------------------------------------------\n\n/**\n * Adapt a child's UDS `ChildCapDescriptor[]` (no addonId) into a\n * `RegisterNodeParams` for the agent's subtree HubNodeRegistry.\n * Uses `childId` as the synthetic `addonId` — matches the Moleculer path\n * where `runnerId = addonId` for single-addon runners (the current default).\n * Only system (non-device-scoped) caps go into `addons`; device-scoped native\n * caps arrive on a later re-handshake via the Moleculer path.\n *\n * TODO(co-location): This function synthesises ONE manifest entry with `addonId = childId`\n * (the runner id). This is correct under the current one-addon-one-process invariant\n * where `childId = runnerId = addonId`. If `execution.group` co-location is ever\n * activated (multiple addons sharing one runner), a single runner would host multiple\n * addonIds but this function would register all their caps under one synthetic addonId —\n * collapsing distinct provider registryKeys into one and breaking per-addon routing.\n * Multi-addon manifest support (splitting the `ChildCapDescriptor[]` by addonId once the\n * protocol carries addonId) would be needed here before enabling co-location post-Phase-F.\n */\nfunction buildAgentChildUdsManifest(\n nodeId: string,\n childId: string,\n caps: readonly ChildCapDescriptor[],\n): RegisterNodeParams {\n const systemCapNames = new Set<string>()\n for (const cap of caps) {\n if (cap.deviceId === undefined) {\n systemCapNames.add(cap.capName)\n }\n }\n const addons: readonly RegisteredAddonManifest[] = [\n { addonId: childId, capabilities: [...systemCapNames] },\n ]\n return { nodeId, addons }\n}\n\n// Run if executed directly\nconst scriptName = process.argv[1] ?? ''\nif (scriptName.endsWith('agent-bootstrap.js') || scriptName.endsWith('agent-bootstrap.ts')) {\n startAgent().catch((err: unknown) => {\n console.error('[Agent] Fatal error:', err)\n process.exit(1)\n })\n}\n\nexport { startAgent }\n","/**\n * Slice-5, Task 6 — agent-side Moleculer service for forwarding hub-dispatched\n * cap calls to the agent's forked UDS children.\n *\n * The hub's `CapRouteResolver` dispatches an `agent-child-forward` route by\n * calling `callWithServiceDiscovery(broker, AGENT_CAP_FWD_SERVICE,\n * AGENT_CAP_FWD_ACTION, params, { nodeID: agentNodeId })`. This service\n * receives those calls, resolves the child locally via the agent's\n * `LocalChildRegistry`, and forwards the call over UDS.\n */\n\nimport type { ServiceSchema, Context } from 'moleculer'\nimport type {\n HubLocalChildDispatcher,\n AgentCapForwardParams,\n CapCallInput,\n LocalChildRegistryLogger,\n} from '@camstack/kernel'\nimport { AGENT_CAP_FWD_SERVICE } from '@camstack/kernel'\n\n// ---------------------------------------------------------------------------\n// ctx.params narrowing helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Moleculer types `ctx.params` loosely; narrow it to `AgentCapForwardParams`\n * by checking the required string fields without unsafe casts.\n */\nfunction narrowParams(raw: unknown): AgentCapForwardParams {\n if (raw === null || typeof raw !== 'object') {\n throw new Error(\n '$agent-cap-fwd.forward: invalid params — capName and method are required strings',\n )\n }\n const capName: unknown = Reflect.get(raw, 'capName')\n const method: unknown = Reflect.get(raw, 'method')\n if (typeof capName !== 'string' || typeof method !== 'string') {\n throw new Error(\n '$agent-cap-fwd.forward: invalid params — capName and method are required strings',\n )\n }\n const childId: unknown = Reflect.get(raw, 'childId')\n const deviceId: unknown = Reflect.get(raw, 'deviceId')\n return {\n capName,\n method,\n args: Reflect.get(raw, 'args'),\n childId: typeof childId === 'string' ? childId : undefined,\n deviceId: typeof deviceId === 'number' ? deviceId : undefined,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Creates the Moleculer `ServiceSchema` for the agent-side cap-dispatch service.\n *\n * @param agentNodeId The Moleculer nodeId of this agent (used in error messages).\n * @param agentUdsRegistry The agent's `LocalChildRegistry` (or a compatible spy in tests).\n * `LocalChildRegistry` structurally satisfies `HubLocalChildDispatcher`.\n * @param logger Optional scoped logger; logs the no-child path at INFO level.\n */\nfunction createAgentCapDispatchService(\n agentNodeId: string,\n agentUdsRegistry: HubLocalChildDispatcher,\n logger?: LocalChildRegistryLogger,\n): ServiceSchema {\n return {\n name: AGENT_CAP_FWD_SERVICE,\n actions: {\n forward: {\n handler: async (ctx: Context): Promise<unknown> => {\n const params = narrowParams(ctx.params)\n const { capName, method, args, deviceId } = params\n\n // Resolve the child: prefer the explicitly-provided childId (hub may\n // know it from its HubNodeRegistry), otherwise resolve locally.\n const childId: string | null | undefined =\n params.childId !== undefined\n ? params.childId\n : agentUdsRegistry.resolveChildId(capName, deviceId)\n\n if (childId == null) {\n logger?.info(\n `agent ${agentNodeId}: no UDS child for cap \"${capName}\"${deviceId !== undefined ? ` (deviceId ${deviceId})` : ''}`,\n )\n throw new Error(\n `agent ${agentNodeId} has no UDS child for cap \"${capName}\"${deviceId !== undefined ? ` (deviceId ${deviceId})` : ''}`,\n )\n }\n\n const input: CapCallInput = {\n capName,\n method,\n args,\n ...(deviceId !== undefined ? { deviceId } : {}),\n }\n\n return agentUdsRegistry.callCapOnChild(childId, input)\n },\n },\n },\n }\n}\n\nexport { createAgentCapDispatchService }\n","/**\n * Extracted, testable helper for registering the agent-side cap-dispatch\n * service with a Moleculer broker.\n *\n * The helper is separated from `agent-bootstrap.ts` so the wiring decision\n * (undefined → no-op; defined → create service) can be exercised in a unit\n * test independently of the full bootstrap lifecycle.\n */\n\nimport type { ServiceSchema, Service } from 'moleculer'\nimport type { HubLocalChildDispatcher, LocalChildRegistryLogger } from '@camstack/kernel'\nimport { createAgentCapDispatchService } from './agent-cap-dispatch-service.js'\n\n// ---------------------------------------------------------------------------\n// AgentServiceRegistrar — narrow interface satisfied by real ServiceBroker\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal surface of `ServiceBroker` required to register the cap-dispatch\n * service. Keeping a narrow interface here avoids coupling the helper to the\n * full broker type (which chains through eventemitter2 without exported types)\n * and prevents `as unknown as ServiceBroker` casts at every call-site.\n *\n * The real `ServiceBroker` structurally satisfies this interface:\n * `ServiceBroker.nodeID: string` ✓\n * `ServiceBroker.createService(schema: ServiceSchema, ...): Service` ✓\n */\nexport interface AgentServiceRegistrar {\n readonly nodeID: string\n createService(schema: ServiceSchema, schemaMods?: Partial<ServiceSchema>): Service\n}\n\n// ---------------------------------------------------------------------------\n// Exported helper\n// ---------------------------------------------------------------------------\n\n/**\n * Registers the `$agent-cap-fwd` Moleculer service with `registrar` when\n * `agentUdsRegistry` is defined; otherwise this is a no-op.\n *\n * Called from `agent-bootstrap.ts` after the UDS child registry starts\n * successfully so the hub can route `agent-child-forward` cap calls to this\n * agent's forked children.\n *\n * @param registrar Moleculer broker (or compatible double in tests).\n * @param agentUdsRegistry The agent's `LocalChildRegistry`; pass `undefined`\n * to skip registration (UDS failed to start).\n * @param logger Optional scoped logger forwarded to the service.\n */\nexport function registerAgentCapDispatch(\n registrar: AgentServiceRegistrar,\n agentUdsRegistry: HubLocalChildDispatcher | undefined,\n logger?: LocalChildRegistryLogger,\n): void {\n if (agentUdsRegistry === undefined) return\n registrar.createService(createAgentCapDispatchService(registrar.nodeID, agentUdsRegistry, logger))\n}\n"],"mappings":"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","names":["os","fs","path","cpus","fs","path","fs","path"]}
|