@camstack/agent 1.1.53 → 1.1.54
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-Y3WG77PU.mjs → chunk-52R7SQAV.mjs} +6 -1
- package/dist/chunk-52R7SQAV.mjs.map +1 -0
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +8 -8
- package/dist/chunk-Y3WG77PU.mjs.map +0 -1
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/agent",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.54",
|
|
4
4
|
"description": "CamStack remote agent — standalone Moleculer node for distributed addon execution",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
"start": "node dist/cli.js"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@camstack/system": "1.1.
|
|
37
|
-
"@camstack/types": "1.1.
|
|
38
|
-
"@camstack/sdk": "1.1.
|
|
39
|
-
"@camstack/shm-ring": "1.0.
|
|
40
|
-
"@camstack/addon-pipeline": "1.1.
|
|
41
|
-
"@camstack/addon-decoder-nodeav": "1.1.
|
|
42
|
-
"@camstack/addon-agent-ui": "1.1.
|
|
36
|
+
"@camstack/system": "1.1.53",
|
|
37
|
+
"@camstack/types": "1.1.48",
|
|
38
|
+
"@camstack/sdk": "1.1.27",
|
|
39
|
+
"@camstack/shm-ring": "1.0.26",
|
|
40
|
+
"@camstack/addon-pipeline": "1.1.60",
|
|
41
|
+
"@camstack/addon-decoder-nodeav": "1.1.15",
|
|
42
|
+
"@camstack/addon-agent-ui": "1.1.26",
|
|
43
43
|
"fastify": "^5",
|
|
44
44
|
"@fastify/static": "^9.1.3",
|
|
45
45
|
"@fastify/cors": "^11.2.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/agent-config.ts","../src/agent-service.ts","../src/agent-deploy-swap.ts","../src/apply-model-distribution.ts","../src/fetch-bundle-from-hub.ts","../src/agent-http.ts","../src/derive-hub-url.ts","../src/agent-http-auth.ts","../src/agent-bootstrap.ts","../src/agent-update-service.ts","../src/agent-group-runner.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 fs from 'node:fs'\nimport * as os from 'node:os'\nimport * as path from 'node:path'\nimport type { AddonDeploySource, RegisterNodeParams, RegisterNodeResult } from '@camstack/system'\nimport {\n CLUSTER_SECRET_MISMATCH_TYPE,\n clusterSecretMatches,\n isAddonDeploySource,\n} from '@camstack/system'\nimport type { AgentHealth, IMetricsProvider, IScopedLogger } from '@camstack/types'\nimport type { Context, ServiceSchema } from 'moleculer'\nimport { Errors } from 'moleculer'\nimport { applyDeployedBundle } from './agent-deploy-swap.js'\nimport { applyModelDistribution, type ModelDistributionSeam } from './apply-model-distribution.js'\nimport { fetchBundleFromHub } from './fetch-bundle-from-hub.js'\n\n/**\n * Console-backed scoped logger for the deploy swap (the only consumer in this\n * file). `applyDeployedBundle` logs only on the rare restore-after-failed-swap\n * path, so a thin console adapter is sufficient.\n */\nconst deploySwapLogger: 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: () => deploySwapLogger,\n withTags: () => deploySwapLogger,\n}\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 call<T>(\n action: string,\n params?: unknown,\n opts?: { nodeID?: string; timeout?: number },\n ): Promise<T>\n}\n\n/**\n * Shape of the `$process.restart` reply (see\n * `packages/system/src/kernel/moleculer/process-service.ts`). `pid` is present\n * on success, `reason` on failure (e.g. the runner could not be resolved).\n */\ninterface ProcessRestartResult {\n readonly success: boolean\n readonly reason?: string\n readonly pid?: number\n}\n\n/** Timeout for the agent-local `$process.restart` delegation. */\nconst AGENT_PROCESS_RESTART_TIMEOUT_MS = 30_000\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\n/**\n * Resolve a promise but never block longer than `ms` — on timeout, resolve to\n * `fallback`. `$agent.status`/`$agent.health` MUST always answer promptly: the\n * hub drops a node from `nodes.topology` whenever `$agent.status` times out, so\n * a status RPC that hangs on a transiently-unavailable metrics provider (e.g.\n * mid-reload) makes the agent silently vanish from the cluster. Bounding the\n * metrics fetch keeps the node visible with a degraded (zeroed) metrics block\n * instead of disappearing.\n */\nasync function withTimeout<T>(p: Promise<T>, ms: number, fallback: T): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<T>((resolve) => {\n timer = setTimeout(() => resolve(fallback), ms)\n })\n try {\n return await Promise.race([p, timeout])\n } finally {\n if (timer) clearTimeout(timer)\n }\n}\n\n/** Bound for the metrics snapshot inside status/health — see `withTimeout`. */\nconst METRICS_SNAPSHOT_TIMEOUT_MS = 2_000\n\ninterface LoadedAddonEntry {\n readonly id: string\n readonly status: string\n /** Addon DECLARATION version (from the addon manifest). */\n readonly version?: string\n readonly packageName?: string\n /**\n * npm PACKAGE version (from the package's package.json) — distinct from the\n * addon declaration `version`. The hub's per-node update check compares the\n * installed PACKAGE version against the registry, so it must be reported\n * here; reporting the declaration version (often `0.1.0` while the package\n * is `1.0.4`) makes `listUpdates` show a permanent phantom update.\n */\n readonly packageVersion?: 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 /** Pull-install a published addon from npm/GHCR (wired by agent-bootstrap). */\n readonly installFromNpm?: (packageName: string, version: string) => Promise<void>\n /**\n * Install a deployed `.tgz` bundle through the agent's `AddonInstaller`\n * (`installFromTgz`): staged extract → `@camstack/*` manifest strip →\n * runtime-deps `npm install` (FATAL on failure) → manifest-driven native\n * deps → atomic rename swap. Wired by agent-bootstrap. When absent the\n * deploy handler falls back to the plain tar swap (`applyDeployedBundle`) —\n * the pre-#17 path that silently LOSES native deps (sharp on the Mac\n * Electron agent) because it never runs the installer's post-install.\n */\n readonly installBundleTgz?: (tgzPath: string) => Promise<{ name: string; version: string }>\n}\n\ninterface AgentConfigFileShape {\n readonly hubAddress?: string\n}\n\n/**\n * Pure seam for the deploy handler routing logic — injected in production from\n * real deps, stubbed in tests. Allows unit-testing source routing without\n * Moleculer.\n */\nexport interface DeployActionSeam {\n installFromNpm: (pkg: string, version: string) => Promise<void>\n applyBundle: (bundle: Buffer) => Promise<{ addonDir: string }>\n fetchBundle: (s: Extract<AddonDeploySource, { kind: 'hub-http' }>) => Promise<Buffer>\n onApplied: (addonDir: string) => void\n}\n\n/**\n * Pure factory that builds the deploy routing logic from a seam. Exported so\n * the routing can be unit-tested without Moleculer or a real filesystem.\n */\nexport function resolveDeployAction(seam: DeployActionSeam) {\n return async (params: {\n addonId: string\n source?: AddonDeploySource\n bundle?: Buffer | string\n }): Promise<{ success: true; addonId: string; path?: string }> => {\n const { addonId, source, bundle } = params\n if (source && isAddonDeploySource(source)) {\n if (source.kind === 'npm') {\n await seam.installFromNpm(addonId, source.version)\n // npm path installs to disk via AddonInstaller and returns no addonDir; the\n // loadedAddons eviction (onApplied) only applies to bundle-extracted installs.\n return { success: true, addonId }\n }\n const buf = await seam.fetchBundle(source)\n const { addonDir } = await seam.applyBundle(buf)\n seam.onApplied(addonDir)\n return { success: true, addonId, path: addonDir }\n }\n // Legacy inline-bundle path (back-compat, removed next release).\n if (bundle === undefined) {\n throw new Error('$agent.deploy: no source descriptor and no legacy bundle provided')\n }\n const buf = typeof bundle === 'string' ? Buffer.from(bundle, 'base64') : bundle\n const { addonDir } = await seam.applyBundle(buf)\n seam.onApplied(addonDir)\n return { success: true, addonId, path: addonDir }\n }\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 // Bounded so $agent.status never hangs (and drops the node from\n // topology) when the metrics provider is mid-reload/unavailable.\n const snapshot = await withTimeout(\n metrics.getCached(),\n METRICS_SNAPSHOT_TIMEOUT_MS,\n null,\n )\n if (snapshot) {\n cpuPercent = snapshot.cpu.total\n memoryPercent = snapshot.memory.percent\n }\n }\n const mem = process.memoryUsage()\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 // The agent *process* itself (distinct from the host `uptime` /\n // memory above) — surfaced so the UI can show how long THIS agent\n // has been running and how much RAM its own runtime holds.\n agentProcess: {\n pid: process.pid,\n uptimeSeconds: Math.round(process.uptime()),\n rssMB: Math.round(mem.rss / 1024 / 1024),\n heapUsedMB: Math.round(mem.heapUsed / 1024 / 1024),\n heapTotalMB: Math.round(mem.heapTotal / 1024 / 1024),\n },\n localIps: getLocalIps(),\n addons: [...deps.loadedAddons.values()].map((a) => ({\n id: a.id,\n status: a.status,\n // Report the PACKAGE version (falls back to the declaration\n // version) so the hub's per-node update check is accurate.\n version: a.packageVersion ?? 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 // Bounded — see $agent.status. Health must answer promptly too.\n const snapshot = await withTimeout(\n metrics.getCached(),\n METRICS_SNAPSHOT_TIMEOUT_MS,\n null,\n )\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 source?: AddonDeploySource\n bundle?: Buffer | string\n config?: Record<string, unknown>\n }>,\n ) => {\n const { params } = ctx as unknown as CtxLike<{\n addonId: string\n source?: AddonDeploySource\n bundle?: Buffer | string\n config?: Record<string, unknown>\n }>\n const { addonId, source, bundle } = params\n\n // execFile (no shell) instead of execSync — addonId comes from RPC\n // params and was previously interpolated into a shell command, which\n // is a command-injection vector. argv form doesn't go through a shell\n // so any payload in addonId stays a literal path arg. Use the ASYNC\n // form (not execFileSync): a synchronous extract blocks the agent\n // event loop for the whole untar, which on a large package starves\n // concurrent hub RPCs ($agent.status heartbeat) long enough for the\n // hub to time them out and drop the node from topology mid-deploy.\n const { execFile } = await import('node:child_process')\n const { promisify } = await import('node:util')\n const execFileAsync = promisify(execFile)\n\n // Atomic install: extract into a temp dir then swap into place. The\n // old body `rm`'d the live dir THEN untarred — a killed/timed-out\n // untar (e.g. a redeploy whose RPC was cut) left the addon dir\n // MISSING. `applyDeployedBundle` never leaves the live dir absent\n // (the non-atomic-deploy fix).\n const extract = async (tgz: Buffer, destDir: string): Promise<void> => {\n const tgzPath = path.join(destDir, '..', `.${path.basename(destDir)}.tgz`)\n fs.writeFileSync(tgzPath, tgz)\n try {\n await execFileAsync('tar', ['-xzf', tgzPath, '-C', destDir, '--strip-components=1'], {\n timeout: 60000,\n })\n } finally {\n try {\n fs.unlinkSync(tgzPath)\n } catch {\n /* best-effort temp cleanup */\n }\n }\n }\n\n const seam: DeployActionSeam = {\n installFromNpm: (pkg, v) => {\n if (!deps.installFromNpm) {\n throw new Error('npm deploy source unsupported on this agent')\n }\n return deps.installFromNpm(pkg, v)\n },\n // #17: route the bundle through the agent's AddonInstaller when\n // wired — installFromTgz runs the FULL post-install (manifest\n // strip, runtime-deps npm install, manifest-driven NATIVE deps)\n // with its own staged extract + atomic swap. The plain tar swap\n // below never installed deps, so every pipeline/post-analysis\n // deploy on the Mac Electron agent silently shipped without\n // `sharp` et al. Fallback kept for agents without an installer\n // (test harnesses).\n applyBundle: async (buf) => {\n if (deps.installBundleTgz) {\n const tgzPath = path.join(\n os.tmpdir(),\n `camstack-deploy-${addonId.replace(/[^\\w.-]/g, '_')}-${Date.now()}.tgz`,\n )\n fs.writeFileSync(tgzPath, buf)\n try {\n const { name } = await deps.installBundleTgz(tgzPath)\n return { addonDir: path.join(deps.addonsDir, name) }\n } finally {\n try {\n fs.unlinkSync(tgzPath)\n } catch {\n /* best-effort temp cleanup */\n }\n }\n }\n return applyDeployedBundle({\n addonsDir: deps.addonsDir,\n addonId,\n bundle: buf,\n extract,\n logger: deploySwapLogger,\n })\n },\n fetchBundle: (s) => fetchBundleFromHub(s),\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 onApplied: (addonDir) => {\n for (const declId of readDeployedAddonIds(addonDir)) {\n deps.loadedAddons.delete(declId)\n }\n },\n }\n\n return resolveDeployAction(seam)({ addonId, source, bundle })\n },\n },\n\n /**\n * Pull a staged model tarball from the hub and untar it into this node's\n * `<dataDir>/models` (Model Studio P2). Reuses the agent-pull machinery:\n * `fetchBundleFromHub` verifies bytes + sha256 before extraction. The\n * existing `isModelDownloaded` then reports the model present, so the\n * detection-pipeline can load it locally.\n */\n distributeModel: {\n handler: async (\n ctx: Context<{\n modelId: string\n format: string\n source: Extract<AddonDeploySource, { kind: 'hub-http' }>\n }>,\n ) => {\n const { params } = ctx as unknown as CtxLike<{\n modelId: string\n format: string\n source: Extract<AddonDeploySource, { kind: 'hub-http' }>\n }>\n const { execFile } = await import('node:child_process')\n const { promisify } = await import('node:util')\n const execFileAsync = promisify(execFile)\n const modelsDir = path.join(deps.dataDir, 'models')\n const seam: ModelDistributionSeam = {\n modelsDir,\n fetchBundle: (s) => fetchBundleFromHub(s),\n extract: async (tgz, destDir) => {\n const tmp = path.join(\n destDir,\n `.dist-${Date.now()}-${Math.random().toString(36).slice(2)}.tgz`,\n )\n fs.writeFileSync(tmp, tgz)\n try {\n await execFileAsync('tar', ['-xzf', tmp, '-C', destDir], { timeout: 60000 })\n } finally {\n try {\n fs.unlinkSync(tmp)\n } catch {\n /* best-effort temp cleanup */\n }\n }\n },\n mkdirp: (dir) => fs.mkdirSync(dir, { recursive: true }),\n }\n return applyModelDistribution(seam, params)\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 // Defense-in-depth: one package dir can host MANY addons (e.g.\n // `@camstack/addon-pipeline` ships decoder-ffmpeg, recorder,\n // motion-wasm, …). Removing the whole bundle because ONE member was\n // undeployed is destructive — every sibling runner would crash-loop\n // on the missing package.json. Only remove the package dir when no\n // OTHER loaded addon still lives in it (this addon's own entry was\n // already deleted from `loadedAddons` above, so the remaining\n // values are exactly the siblings).\n const pkgName = entry?.packageName\n if (pkgName && pkgName !== addonId) {\n const pkgShared = [...deps.loadedAddons.values()].some((e) => e.packageName === pkgName)\n if (!pkgShared) {\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\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, broker } = ctx as unknown as CtxLike<{ addonId: string }>\n const { addonId } = params\n // Delegate to THIS agent's own `$process` service — the SAME primitive\n // the hub uses for a local forked addon (AddonRegistryService.restartAddon\n // → `$process.restart`). `resolveRunnerName` maps `addonId` to its hosting\n // runner (direct runner key OR `runnerAddons` membership), so the child is\n // respawned, the crash streak reset (`crashSupervisor.reset`), the stale\n // Moleculer node evicted, and the addon's caps re-registered — identical\n // semantics to a hub-local restart. Pin to the agent's own node so the\n // call always hits THIS agent's `$process`, never the hub's.\n const result = await broker.call<ProcessRestartResult>(\n '$process.restart',\n { name: addonId },\n { nodeID: broker.nodeID, timeout: AGENT_PROCESS_RESTART_TIMEOUT_MS },\n )\n if (!result.success) {\n // Fail loudly so the hub's `nodes.restartAddon` surfaces the real\n // error instead of the old fire-and-forget phantom success.\n throw new Errors.MoleculerError(\n `$agent.restart: process restart failed for \"${addonId}\": ${result.reason ?? 'unknown'}`,\n 500,\n 'AGENT_RESTART_FAILED',\n )\n }\n return { success: true, addonId, pid: result.pid }\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 type { AgentServiceDeps, LoadedAddonEntry }\nexport { createAgentService }\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { randomBytes } from 'node:crypto'\nimport type { IScopedLogger } from '@camstack/types'\n\n/**\n * Inputs for {@link applyDeployedBundle}.\n *\n * `extract` is injected (rather than hard-wiring `tar`) so the swap logic is\n * unit-testable without a real tarball and so the caller controls the extractor\n * (the `$agent.deploy` handler wires the async `execFile tar --strip-components=1`\n * it already uses).\n */\nexport interface ApplyDeployedBundleInput {\n /** Directory holding installed addon package dirs (`<addonsDir>/<addonId>`). */\n readonly addonsDir: string\n /** On-disk dir name for this package (the `$agent.deploy` `addonId` param). */\n readonly addonId: string\n /** The `.tgz` bytes to extract. */\n readonly bundle: Buffer\n /** Extract `tgz` into `destDir` (stripping the npm `package/` prefix). */\n readonly extract: (tgz: Buffer, destDir: string) => Promise<void>\n readonly logger: IScopedLogger\n}\n\n/** Recursively remove a path, ignoring a missing target. */\nfunction rmrf(target: string): void {\n fs.rmSync(target, { recursive: true, force: true })\n}\n\n/**\n * Move `from` → `to` atomically when on the same filesystem; fall back to a\n * recursive copy + remove on a cross-device boundary (`EXDEV`). The agent's\n * `/data` addonsDir is frequently a different filesystem from the OS temp dir,\n * so a bare `renameSync` would throw `EXDEV` — the same fallback\n * `AddonInstaller.applyUpdateFromStaged` uses.\n */\nfunction moveDir(from: string, to: string): void {\n try {\n fs.renameSync(from, to)\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code === 'EXDEV') {\n fs.cpSync(from, to, { recursive: true })\n rmrf(from)\n } else {\n throw err\n }\n }\n}\n\n/**\n * Atomically install a deployed addon bundle into `<addonsDir>/<addonId>`.\n *\n * Unlike the old `$agent.deploy` body (`rm` the live dir, THEN untar into it),\n * this never leaves the live dir missing: extraction happens in a sibling temp\n * dir first, the live dir is swapped in by rename, and any failure restores the\n * previous copy. A killed/timed-out extract (the non-atomic-deploy hazard that\n * a contaminated propagation test hit) leaves the old version fully intact.\n *\n * No `AddonInstaller` manifest dependency — works for any deployed addon,\n * including ones never tracked in the agent's install manifest.\n *\n * @returns the live addon directory path (`<addonsDir>/<addonId>`).\n */\nexport async function applyDeployedBundle(\n input: ApplyDeployedBundleInput,\n): Promise<{ addonDir: string }> {\n const { addonsDir, addonId, bundle, extract, logger } = input\n\n fs.mkdirSync(addonsDir, { recursive: true })\n const liveDir = path.join(addonsDir, addonId)\n // Place the temp + backup dirs as SIBLINGS of the live dir (same parent →\n // same filesystem → atomic rename). Deriving them from the live dir's parent\n // and basename keeps a scoped `addonId` like \"@camstack/addon-pipeline\" — whose\n // slash would otherwise turn `.<addonId>.next` into a nested path — correct.\n const liveParent = path.dirname(liveDir)\n const liveBase = path.basename(liveDir)\n fs.mkdirSync(liveParent, { recursive: true })\n const token = randomBytes(6).toString('hex')\n const nextDir = path.join(liveParent, `.${liveBase}.next.${token}`)\n const backupDir = path.join(liveParent, `.${liveBase}.backup.${token}`)\n\n // 1. Extract into a temp dir. If extraction throws (e.g. a killed untar),\n // drop the temp dir and rethrow — the live dir is never touched.\n fs.mkdirSync(nextDir, { recursive: true })\n try {\n await extract(bundle, nextDir)\n } catch (err) {\n rmrf(nextDir)\n throw err\n }\n\n // 2. Swap. Back up the live dir (if present), then move next → live. On any\n // failure, restore the backup so the addon stays installed at the old\n // version.\n const hadLive = fs.existsSync(liveDir)\n if (hadLive) {\n fs.renameSync(liveDir, backupDir)\n }\n try {\n moveDir(nextDir, liveDir)\n } catch (swapErr) {\n if (hadLive) {\n try {\n if (fs.existsSync(liveDir)) rmrf(liveDir)\n fs.renameSync(backupDir, liveDir)\n } catch (restoreErr) {\n logger.error(\n `agent deploy swap: restore of \"${addonId}\" failed after a failed swap — manual recovery may be needed`,\n {\n meta: {\n backupDir,\n error: restoreErr instanceof Error ? restoreErr.message : String(restoreErr),\n },\n },\n )\n }\n }\n rmrf(nextDir)\n throw swapErr\n }\n\n // 3. Success — drop the backup.\n if (hadLive) rmrf(backupDir)\n return { addonDir: liveDir }\n}\n","/**\n * Agent-side model distribution (P2): pull a staged model tarball from the hub\n * and untar it into this node's modelsDir. Factored behind a seam so the\n * fetch/extract/fs effects are injectable in tests.\n */\n\nexport interface DistributeModelParams {\n readonly modelId: string\n readonly format: string\n readonly source: {\n readonly url: string\n readonly token: string\n readonly sha256: string\n readonly bytes: number\n }\n}\n\nexport interface ModelDistributionSeam {\n readonly modelsDir: string\n /** Pull + verify (bytes + sha256) the staged tgz. */\n fetchBundle(source: DistributeModelParams['source']): Promise<Buffer>\n /** Untar the gzip buffer into destDir. */\n extract(tgz: Buffer, destDir: string): Promise<void>\n mkdirp(dir: string): void\n}\n\nexport interface DistributeModelResult {\n readonly success: true\n readonly modelId: string\n readonly format: string\n readonly path: string\n}\n\nexport async function applyModelDistribution(\n seam: ModelDistributionSeam,\n params: DistributeModelParams,\n): Promise<DistributeModelResult> {\n seam.mkdirp(seam.modelsDir)\n const buffer = await seam.fetchBundle(params.source)\n await seam.extract(buffer, seam.modelsDir)\n return { success: true, modelId: params.modelId, format: params.format, path: seam.modelsDir }\n}\n","import { createHash } from 'node:crypto'\nimport { Agent, fetch as undicicFetch } from 'undici'\n\nexport interface FetchBundleOpts {\n readonly url: string\n readonly token: string\n readonly sha256: string\n readonly bytes: number\n /** Injectable for tests; defaults to global fetch. */\n readonly fetchImpl?: typeof fetch\n}\n\n/** Minimal response surface used by fetchBundleFromHub. */\ninterface ResponseLike {\n readonly ok: boolean\n readonly status: number\n arrayBuffer(): Promise<ArrayBuffer>\n}\n\n/**\n * Stream a staged addon tgz from the hub's one-time-token bundle route and\n * verify integrity (byte count + sha256) before handing it to the installer.\n */\nexport async function fetchBundleFromHub(opts: FetchBundleOpts): Promise<Buffer> {\n // When a fetchImpl is injected (tests), call it as-is — the stub ignores dispatcher.\n // For production (global fetch against the real hub's self-signed TLS cert), use\n // undici.fetch with an Agent that disables CA verification. The hub serves HTTPS with a\n // self-signed cert; bare fetch rejects it with UNABLE_TO_VERIFY_LEAF_SIGNATURE.\n // Integrity is independently guaranteed by the sha256+bytes checks below; trust is\n // established by the cluster relationship — consistent with the CLI's approach.\n //\n // We call undici.fetch (not the global fetch) so that `Agent` and `RequestInit` share\n // the same undici type declarations; the global fetch augmentation uses `undici-types`\n // which ships a structurally incompatible `Dispatcher` type. Both return a structurally\n // compatible Response; we narrow via the shared `ResponseLike` interface to avoid\n // cross-package type unification.\n // `accept-encoding: identity` — never let the hub gzip/brotli this binary\n // pull. A Brotli-encoded response made undici's BrotliDecompress abort with\n // \"TypeError: terminated\", silently breaking the pull. The hub also opts the\n // route out of compression; this is the client-side belt-and-suspenders.\n const authHeader = { Authorization: `Bearer ${opts.token}`, 'accept-encoding': 'identity' }\n const res: ResponseLike = opts.fetchImpl\n ? await opts.fetchImpl(opts.url, { headers: authHeader })\n : await undicicFetch(opts.url, {\n headers: authHeader,\n dispatcher: new Agent({ connect: { rejectUnauthorized: false } }),\n })\n if (!res.ok) {\n throw new Error(`bundle fetch failed: HTTP ${res.status}`)\n }\n const buffer = Buffer.from(await res.arrayBuffer())\n if (buffer.length !== opts.bytes) {\n throw new Error(`bundle bytes mismatch: expected ${opts.bytes}, got ${buffer.length}`)\n }\n const sha = createHash('sha256').update(buffer).digest('hex')\n if (sha !== opts.sha256) {\n throw new Error(`bundle sha256 mismatch: expected ${opts.sha256}, got ${sha}`)\n }\n return buffer\n}\n","/**\n * Agent HTTP server -- lightweight Fastify instance for agent status,\n * process management, config editing, and static UI serving.\n */\n\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport type { FastifyInstance } from 'fastify'\nimport Fastify from 'fastify'\nimport type { ServiceBroker } from 'moleculer'\nimport type { HubRegistryNode } from './derive-hub-url.js'\nimport { pickIpv4 } from './derive-hub-url.js'\nimport { isAuthorizedAgentRequest, isPairingRequest } from './agent-http-auth.js'\n\n// ---------------------------------------------------------------------------\n// Connection state\n// ---------------------------------------------------------------------------\n\n/**\n * Discriminated hub connection state surfaced by the agent status API.\n *\n * - `connected` — hub node is in the Moleculer registry (available).\n * - `searching` — discovery mode active, no hub found yet.\n * - `disconnected` — direct address configured but hub not in registry.\n * - `secret-mismatch`— `$hub.registerNode` was rejected with a cluster-secret error.\n *\n * `registering` (transport up but registerNode not yet acked) is not\n * currently deterministic from the broker registry alone — it would require\n * a tighter integration with the retry loop. Omitted until a clean signal\n * exists; the brief gap is invisible at 3 s poll rate.\n */\nexport type HubConnectionState = 'connected' | 'searching' | 'disconnected' | 'secret-mismatch'\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 * Optional getter for the current hub connection state. When provided,\n * the status endpoint uses this to surface a richer discriminated state\n * instead of the legacy boolean `hubConnected`. The bootstrap wires this\n * to expose `secret-mismatch` which is not derivable from the broker\n * registry alone.\n */\n readonly getHubConnectionState?: () => HubConnectionState\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** A candidate agent-ui `dist/` dir plus the owning package's version. */\nexport interface UiDistCandidate {\n readonly dir: string\n readonly version: string | null\n}\n\n/** Dotted-numeric version compare; `null` sorts lowest. */\nexport function compareVersions(a: string | null, b: string | null): number {\n if (a === null && b === null) return 0\n if (a === null) return -1\n if (b === null) return 1\n const pa = a.split('.').map((s) => Number.parseInt(s, 10) || 0)\n const pb = b.split('.').map((s) => Number.parseInt(s, 10) || 0)\n const len = Math.max(pa.length, pb.length)\n for (let i = 0; i < len; i++) {\n const diff = (pa[i] ?? 0) - (pb[i] ?? 0)\n if (diff !== 0) return diff\n }\n return 0\n}\n\n/**\n * Read an agent-ui package dir (`<base>/package.json` + `<base>/dist/index.html`)\n * into a candidate. Returns `null` when the dist is absent/unusable.\n */\nexport function readUiDistCandidate(baseDir: string): UiDistCandidate | null {\n const dir = path.join(baseDir, 'dist')\n if (!fs.existsSync(path.join(dir, 'index.html'))) return null\n let version: string | null = null\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(baseDir, 'package.json'), 'utf-8')) as {\n readonly version?: unknown\n }\n version = typeof parsed.version === 'string' ? parsed.version : null\n } catch {\n version = null\n }\n return { dir, version }\n}\n\n/**\n * Pick between the dataDir-installed copy and the app-bundled copy: the\n * NEWER version wins; on a tie the installed copy wins (it is the one\n * `camstack deploy` updates).\n *\n * Rationale: bootstrap addon install is seed-only (first boot), so a\n * packaged desktop app that ships a newer agent-ui in its resources would\n * otherwise keep serving the UI frozen at whatever version was first\n * installed (observed live: an app at v1.1.27 serving the v1.1.1 UI).\n */\nexport function pickUiDist(\n installed: UiDistCandidate | null,\n bundled: UiDistCandidate | null,\n): string | null {\n if (installed && bundled) {\n return compareVersions(bundled.version, installed.version) > 0 ? bundled.dir : installed.dir\n }\n return installed?.dir ?? bundled?.dir ?? null\n}\n\nexport function resolveUiDistDir(\n dataDir: string,\n bundledAddonsDir?: string,\n // Workspace / npm-package sibling — dev checkouts and Docker images where\n // @camstack/addon-agent-ui sits next to @camstack/agent in node_modules.\n // Always version-matched to the agent, so it keeps top priority.\n // Injectable so tests are independent of the workspace checkout.\n siblingDistDir: string = path.resolve(__dirname, '../../addon-agent-ui/dist'),\n): string | null {\n if (fs.existsSync(path.join(siblingDistDir, 'index.html'))) return siblingDistDir\n\n const installed = readUiDistCandidate(path.join(dataDir, 'addons', '@camstack', 'addon-agent-ui'))\n const bundled = bundledAddonsDir\n ? readUiDistCandidate(path.join(bundledAddonsDir, 'addon-agent-ui'))\n : null\n const picked = pickUiDist(installed, bundled)\n if (picked) return picked\n\n const legacy = path.join(dataDir, 'agent-ui')\n if (fs.existsSync(path.join(legacy, 'index.html'))) return legacy\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\n// Registry-node shape shared with the CAMSTACK_HUB_URL derivation\n// (`deriveHubUrlFromRegistry`) so the UI's `resolvedHubAddress` and the\n// exported env var read the exact same registry view.\ntype RegistryNode = HubRegistryNode\n\n/** Live Moleculer registry snapshot — reused by agent-bootstrap's Option A. */\nexport function getRegistryNodes(broker: ServiceBroker): readonly RegistryNode[] {\n try {\n const registry = (broker as unknown as Record<string, unknown>).registry as\n | { getNodeList?: (opts: { onlyAvailable: boolean }) => readonly RegistryNode[] }\n | undefined\n return registry?.getNodeList?.({ onlyAvailable: true }) ?? []\n } catch {\n return []\n }\n}\n\n// `pickIpv4` moved to derive-hub-url.ts (single home shared with\n// `deriveHubUrlFromRegistry`); re-exported here to preserve this module's API.\nexport { pickIpv4 }\n\n/**\n * A human-readable, reachable address for the hub node as the agent actually\n * sees it in the Moleculer registry. Used to fill in the effective address\n * even in UDP-discovery mode where no address was manually configured.\n */\nexport function resolveHubEndpoint(nodes: readonly RegistryNode[]): string | null {\n const hub = nodes.find((n) => n.id === 'hub')\n if (!hub) return null\n return pickIpv4(hub.ipList) ?? hub.hostname ?? null\n}\n\nexport interface DiscoveredNode {\n readonly id: string\n readonly hostname: string\n readonly isHub: boolean\n}\n\n/**\n * Map raw registry nodes (minus self) to the UI-facing shape: the hub carries\n * its real hostname so the UI can render \"hostname (hub)\" instead of the bare\n * `hub` node id.\n */\nexport function mapDiscoveredNodes(\n nodes: readonly RegistryNode[],\n selfId: string,\n): readonly DiscoveredNode[] {\n return nodes\n .filter((n) => n.id !== selfId)\n .map((n) => ({ id: n.id, hostname: n.hostname ?? n.id, isHub: n.id === 'hub' }))\n}\n\n/**\n * Derive the hub connection state from broker registry + config when no\n * external getter is wired. Does NOT detect `secret-mismatch` — that\n * requires the bootstrap to inject `getHubConnectionState`.\n */\nfunction deriveHubConnectionState(\n nodes: readonly { id: string }[],\n discoveryMode: boolean,\n): HubConnectionState {\n const hubInRegistry = nodes.some((n) => n.id === 'hub')\n if (hubInRegistry) return 'connected'\n if (discoveryMode) return 'searching'\n return 'disconnected'\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 // -- Auth gate (port-4444 hardening, mirrors the hub's /health work) --\n // Loopback (Electron renderer, in-container probes) always passes;\n // remote callers need `Authorization: Bearer <cluster secret>`. While\n // the agent is UNPAIRED (no secret configured) only the pairing surface\n // is reachable remotely. Registered as an onRequest hook over the /api\n // + /health/details prefixes so any FUTURE route is gated fail-closed.\n const currentSecret = (): string | null => {\n const raw = readConfigFile(config.configPath)\n return typeof raw.secret === 'string' && raw.secret.length > 0 ? raw.secret : null\n }\n app.addHook('onRequest', async (req, reply) => {\n const pathOnly = req.url.split('?')[0] ?? req.url\n const isProtected = pathOnly.startsWith('/api/') || pathOnly.startsWith('/health/')\n if (!isProtected) return\n const secret = currentSecret()\n const authInput = {\n remoteAddress: req.socket.remoteAddress ?? undefined,\n ...(typeof req.headers.authorization === 'string'\n ? { authorization: req.headers.authorization }\n : {}),\n }\n if (isAuthorizedAgentRequest(authInput, secret)) return\n if (secret === null && isPairingRequest(req.method, pathOnly)) return\n return reply.status(401).send({ ok: false, error: 'unauthorized' })\n })\n\n // -- Health ---------------------------------------------------------\n // PUBLIC probe: liveness only — `{ok}`, no nodeId/version/topology (a\n // public endpoint must not enumerate the deployment; same policy as the\n // hub's /health). The detailed shape lives on `$agent.health` (Moleculer\n // action) for the hub, and on the AUTHENTICATED /health/details below\n // for external monitors.\n app.get('/health', async (_req, reply) => {\n try {\n await getBroker().call('$agent.health')\n return { ok: true }\n } catch {\n return reply.status(503).send({ ok: false })\n }\n })\n\n // AUTHENTICATED detailed health — the payload /health used to return.\n app.get('/health/details', 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 const hubConnectionState: HubConnectionState = config.getHubConnectionState\n ? config.getHubConnectionState()\n : deriveHubConnectionState(nodes, discoveryMode)\n // The address the agent actually reaches the hub at (from the Moleculer\n // registry) — lets the UI show the real endpoint even under UDP discovery\n // where `eff.hubAddress` is null.\n const resolvedHubAddress = resolveHubEndpoint(nodes)\n // Version of the Electron wrapper (injected by the desktop app); absent\n // when the agent runs headless / in Docker.\n const appVersion = process.env['CAMSTACK_AGENT_APP_VERSION'] ?? null\n const discoveredNodes = mapDiscoveredNodes(nodes, broker.nodeID)\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 resolvedHubAddress,\n appVersion,\n hubConnected,\n hubConnectionState,\n discoveryMode,\n hasSecret: eff.hasSecret,\n discoveredNodes,\n }\n } catch {\n return {\n nodeId: config.nodeId,\n name: eff.name,\n hubAddress: eff.hubAddress,\n resolvedHubAddress,\n appVersion,\n hubConnected,\n hubConnectionState,\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 // Fastify (not Express) natively awaits async route handlers and serializes\n // the returned value / catches rejections, so an async handler is correct here.\n // eslint-disable-next-line oxc/no-async-endpoint-handlers\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 mapDiscoveredNodes(nodes, b.nodeID)\n })\n\n // -- Static file serving (agent-ui) ---------------------------------\n\n const uiDir = resolveUiDistDir(config.dataDir, process.env['CAMSTACK_BUNDLED_ADDONS_DIR'])\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 // MUST stay true: the SPA-fallback below uses `reply.sendFile`, which\n // only exists when the plugin decorates Reply. With `false` every\n // fallback request 500'd with \"reply.sendFile is not a function\".\n decorateReply: true,\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 // Never serve index.html to an asset request: a stale index.html\n // referencing missing hashed assets must fail VISIBLY (404 in the\n // renderer console) instead of feeding HTML to a module script,\n // which renders as a silent blank page.\n if (req.url.startsWith('/assets/')) {\n console.warn(`[Agent] UI asset not found (stale index.html?): ${req.url}`)\n return reply.status(404).send({ error: 'Asset 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","/**\n * Derive the agent's `CAMSTACK_HUB_URL` from its friendly `hubAddress`.\n *\n * `hubAddress` is the agent's single source of truth for \"where is the hub\"\n * (the Moleculer TCP dial target). The cross-node consumers of\n * `CAMSTACK_HUB_URL` — the pipeline-runner remote-source leg and the\n * cross-node recorder pull — only ever extract the HOSTNAME from it\n * (`resolveHubHostname` / `extractHost` in\n * `packages/addon-pipeline/src/shared/hub-hostname.ts`). Deriving the value\n * here removes the historical requirement to set a second, redundant env var\n * by hand on every remote agent (the cross-node-source trap).\n *\n * Accepts every friendly form `normalizeHubUrl`\n * (`packages/system/src/kernel/moleculer/broker-factory.ts`) accepts:\n * `host`, `host:port`, `host:port/nodeID`, `nodeID@host`,\n * `nodeID@host:port`, and bracketed IPv6 (`[::1]:6000`).\n * The optional `nodeID@` prefix and `/nodeID` suffix are stripped and the\n * Moleculer `:port` (6000-family) is dropped — only the host survives.\n *\n * Port note: the emitted `:4443` is the DEFAULT hub API port (matching the\n * hub-side default in `server/backend/src/api/addon-upload.ts` and the\n * manually-proven value used on live remote agents), NOT a probed value.\n * Because every current consumer keeps only the host, the port is convention;\n * a future full-URL consumer must treat it as the default, not authoritative.\n */\n\nconst HUB_API_PORT = 4443\n\nexport function deriveHubUrlFromHubAddress(hubAddress: string): string | undefined {\n const trimmed = hubAddress.trim()\n if (trimmed.length === 0) return undefined\n\n // Strip an optional `nodeID@` prefix.\n const afterAt = trimmed.includes('@') ? (trimmed.split('@')[1] ?? '') : trimmed\n // Strip an optional `/nodeID` suffix — keep only the `host[:port]` authority.\n const authority = afterAt.split('/')[0] ?? ''\n\n const host = extractHostFromAuthority(authority)\n if (host === undefined) return undefined\n return `https://${host}:${HUB_API_PORT}`\n}\n\n/**\n * Compute the `CAMSTACK_HUB_URL` value the agent should export, or `undefined`\n * when it must be left untouched. An explicit operator-provided env value\n * (`hubUrlWasExplicit`) always wins and is never clobbered — this preserves\n * today's working deployments. Kept pure (env is passed in) so both the\n * boot-time derivation and the reconnect path can be unit-tested.\n */\nexport function deriveHubUrlForExport(\n hubUrlWasExplicit: boolean,\n hubAddress: string | undefined,\n): string | undefined {\n if (hubUrlWasExplicit) return undefined\n if (hubAddress === undefined) return undefined\n return deriveHubUrlFromHubAddress(hubAddress)\n}\n\n/** Take the bare host from a `host[:port]` authority, keeping bracketed IPv6. */\nfunction extractHostFromAuthority(authority: string): string | undefined {\n const value = authority.trim()\n if (value.length === 0) return undefined\n if (value.startsWith('[')) {\n const end = value.indexOf(']')\n if (end > 0) return value.slice(0, end + 1)\n return undefined\n }\n const host = value.split(':')[0] ?? ''\n return host.length > 0 ? host : undefined\n}\n\n// ---------------------------------------------------------------------------\n// Registry-backed derivation (Option A — discovery-mode fallback)\n// ---------------------------------------------------------------------------\n// In UDP-discovery mode there is no configured `hubAddress` to derive from,\n// but the agent PARENT (the only agent-side process with a Moleculer\n// registry — addon-runner children are broker-less) can read the hub node\n// straight out of the live cluster connection. `udpAddress` is wire truth:\n// Moleculer's TCP transporter stamps it from the UDP announce source address\n// or the inbound GOSSIP_HELLO `socket.remoteAddress`, and the hub's\n// self-advertised INFO never overwrites it — Moleculer's own peer dialing\n// trusts the same ordering (udpAddress → hostname → ipList[0]).\n\n/** The agent-side Moleculer registry view of a node (`registry.getNodeList`). */\nexport interface HubRegistryNode {\n readonly id: string\n readonly hostname?: string\n readonly ipList?: readonly string[]\n readonly udpAddress?: string | null\n}\n\n/** Node id the hub broker always registers under. */\nconst HUB_NODE_ID = 'hub'\n\nconst IPV4_MAPPED_PREFIX = '::ffff:'\nconst IPV4_DOTTED_QUAD = /^\\d{1,3}(?:\\.\\d{1,3}){3}$/\n\n/**\n * First non-internal IPv4 in a Moleculer node's advertised IP list.\n * Canonical home of this helper — `resolveHubEndpoint` in `agent-http.ts`\n * (UI-facing `resolvedHubAddress`) re-exports and reuses it so the UI display\n * and the exported `CAMSTACK_HUB_URL` always agree.\n */\nexport function pickIpv4(ipList: readonly string[] | undefined): string | null {\n if (!ipList) return null\n for (const ip of ipList) {\n if (ip.includes(':')) continue // skip IPv6\n if (ip.startsWith('127.')) continue // skip loopback\n return ip\n }\n return null\n}\n\n/**\n * Normalize a socket-observed address so it survives a WHATWG-`URL` hostname\n * write (`substituteRtspHost`): strip the `::ffff:` IPv4-mapped prefix,\n * bracket bare IPv6 (an unbracketed IPv6 assigned to `url.hostname` is\n * silently IGNORED — the URL would keep 127.0.0.1), pass plain IPv4 /\n * hostnames / already-bracketed IPv6 through untouched.\n */\nexport function normalizeObservedHost(raw: string | undefined | null): string | undefined {\n if (raw === undefined || raw === null) return undefined\n const trimmed = raw.trim()\n if (trimmed.length === 0) return undefined\n if (trimmed.startsWith('[')) return trimmed // already-bracketed IPv6\n if (trimmed.toLowerCase().startsWith(IPV4_MAPPED_PREFIX)) {\n const mapped = trimmed.slice(IPV4_MAPPED_PREFIX.length)\n if (IPV4_DOTTED_QUAD.test(mapped)) return mapped\n }\n // Any remaining colon means bare IPv6 — bracket it.\n if (trimmed.includes(':')) return `[${trimmed}]`\n return trimmed\n}\n\n/**\n * Best hub host from the agent-side Moleculer registry view:\n * `udpAddress` (wire truth) → first non-loopback IPv4 in `ipList`\n * (hub self-advertised; container-internal in bridge-mode Docker) →\n * `hostname` (hub's `os.hostname()`; often unresolvable — last resort).\n * Returns `undefined` when the hub node is not (yet) known. Emits the SAME\n * `https://host:4443` shape as `deriveHubUrlFromHubAddress` — see the port\n * note at the top of this file.\n */\nexport function deriveHubUrlFromRegistry(nodes: readonly HubRegistryNode[]): string | undefined {\n const hub = nodes.find((node) => node.id === HUB_NODE_ID)\n if (hub === undefined) return undefined\n const host = normalizeObservedHost(hub.udpAddress) ?? pickIpv4(hub.ipList) ?? hub.hostname\n if (host === undefined || host.length === 0) return undefined\n return `https://${host}:${HUB_API_PORT}`\n}\n","/**\n * Agent HTTP auth — pure request-authorization helpers for the agent's\n * port-4444 API (mirrors the hub's /health hardening).\n *\n * The surface was fully unauthenticated on 0.0.0.0 — including\n * `POST /api/agent/config`, which can rewrite `hubAddress` and the\n * cluster secret itself. Model:\n * - loopback callers (the Electron renderer, in-container probes) are\n * always allowed — the desktop app keeps working with zero changes,\n * - remote callers must present `Authorization: Bearer <cluster secret>`\n * — the secret is the agent↔hub trust anchor and the only credential\n * an agent can verify without a user database,\n * - an UNPAIRED agent (no secret configured yet) can be set up remotely\n * through the pairing surface only; everything else stays denied.\n */\n\nimport { timingSafeEqual } from 'node:crypto'\n\n/** The header/socket facts needed to authorize one request. */\nexport interface AgentAuthInput {\n readonly remoteAddress?: string | undefined\n readonly authorization?: string | undefined\n}\n\n/** IPv4/IPv6 loopback (incl. the IPv4-mapped IPv6 form Node reports). */\nexport function isLoopbackAddress(addr: string | undefined): boolean {\n if (!addr) return false\n if (addr === '::1') return true\n const v4 = addr.startsWith('::ffff:') ? addr.slice('::ffff:'.length) : addr\n // 127.0.0.0/8 — every octet must be numeric so '127.evil.host' fails.\n const parts = v4.split('.')\n if (parts.length !== 4 || parts[0] !== '127') return false\n return parts.every((p) => /^\\d{1,3}$/.test(p))\n}\n\n/** Timing-safe `Authorization: Bearer <secret>` compare. Empty secrets never match. */\nexport function bearerMatchesSecret(authorization: string | undefined, secret: string): boolean {\n if (secret.length === 0) return false\n if (!authorization?.startsWith('Bearer ')) return false\n const token = Buffer.from(authorization.slice('Bearer '.length))\n const expected = Buffer.from(secret)\n return token.length === expected.length && timingSafeEqual(token, expected)\n}\n\n/**\n * True when the request may access the protected agent API. `clusterSecret`\n * is the CURRENTLY configured secret (`null`/empty = unpaired agent — no\n * remote credential can be verified, so remote access is denied except for\n * the pairing surface, which the route layer allows via {@link isPairingRequest}).\n */\nexport function isAuthorizedAgentRequest(\n input: AgentAuthInput,\n clusterSecret: string | null,\n): boolean {\n if (isLoopbackAddress(input.remoteAddress)) return true\n if (clusterSecret === null || clusterSecret.length === 0) return false\n return bearerMatchesSecret(input.authorization, clusterSecret)\n}\n\n/**\n * The first-boot pairing surface: what a remote browser needs to configure\n * a factory-fresh agent (read status/config, write hub address + secret,\n * trigger the reconnect). Process control and health details are NEVER\n * part of it. Only consulted while the agent has no secret configured.\n */\nexport function isPairingRequest(method: string, urlPath: string): boolean {\n const pathOnly = urlPath.split('?')[0] ?? urlPath\n if (method === 'GET') {\n return (\n pathOnly === '/api/agent/status' ||\n pathOnly === '/api/agent/config' ||\n pathOnly === '/api/agent/discovered-nodes'\n )\n }\n if (method === 'POST') {\n return pathOnly === '/api/agent/config' || pathOnly === '/api/agent/restart'\n }\n return false\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { getRegistryNodes, startAgentHttpServer } from './agent-http.js'\nimport type { HubConnectionState } from './agent-http.js'\nimport { deriveHubUrlForExport, deriveHubUrlFromRegistry } from './derive-hub-url.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 HubNodeRegistry,\n callRegisterNodeWithRetry,\n buildNodeManifest,\n hashClusterSecret,\n isClusterSecretMismatchError,\n LocalChildRegistry,\n createLocalTransport,\n localEndpointPath,\n udsChildLogToWorkerEntry,\n createUdsEventBridge,\n getBrokerEventBus,\n setNodeEventInterest,\n subscribePassthrough,\n getOrInitReadinessRegistry,\n createParentUnownedCallHandler,\n HUB_CAP_FWD_ACTION,\n} from '@camstack/system'\nimport type {\n AddonContextOptions,\n RegisterNodeParams,\n RegisteredAddonManifest,\n ChildCapDescriptor,\n InProcessProviderLookup,\n InProcessProviderRef,\n} from '@camstack/system'\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 serverManagementCapability,\n errMsg,\n ALL_CAPABILITY_DEFINITIONS,\n} from '@camstack/types'\nimport { loadAgentConfig } from './agent-config.js'\nimport {\n AGENT_RUNTIME_ADDON_ID,\n AgentUpdateService,\n agentRuntimeManifestEntry,\n armLocalConfirmFallback,\n buildAgentServerManagementProvider,\n} from './agent-update-service.js'\nimport { createAgentService } from './agent-service.js'\nimport type { LoadedAddonEntry } from './agent-service.js'\nimport {\n isGroupRunnerAddon,\n ensureGroupRunner,\n registerGroupRunnerProviders,\n readPackageManifestAddons,\n type GroupCandidate,\n} from './agent-group-runner.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/system'\n\nconst agentLogManager = new LogManager(5000)\n\n/**\n * Caps whose `nodeId` arg is provider DATA (`nodeIdMode: 'data'` —\n * `addon-settings`, `addons`, `nodes`, `pipeline-orchestrator`), never a\n * routing pin. The agent's own capabilityRegistry only declares the agent\n * caps, so derive the set from the static cap definitions shipped in\n * `@camstack/types`. Fed to `createParentUnownedCallHandler` so a forked\n * addon on this agent calling e.g.\n * `addon-settings.getGlobalSettings({addonId, nodeId})` forwards UNPINNED to\n * the hub singleton (which reads `nodeId` as data) instead of being pinned to\n * a node with no provider (dead broker fallback → ServiceNotFoundError).\n */\nconst DATA_NODEID_CAPS: ReadonlySet<string> = new Set(\n ALL_CAPABILITY_DEFINITIONS.filter((c) => c.nodeIdMode === 'data').map((c) => c.name),\n)\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\nasync function startAgent(configPath?: string): Promise<void> {\n const config = loadAgentConfig(configPath)\n\n // Derive CAMSTACK_HUB_URL from the configured hub address so remote agents\n // no longer need a second, redundant env var set by hand (the cross-node\n // source trap). An explicit operator value ALWAYS wins — capture whether it\n // was set BEFORE deriving so reconnect() can still update a derived value.\n // This runs before every runner spawn (loadClusterCapableAddons /\n // loadDeployedAddons below), and process-service spreads `process.env` into\n // child env, so pipeline-runner + recorder children inherit it for free.\n const hubUrlWasExplicit = process.env['CAMSTACK_HUB_URL'] !== undefined\n const derivedHubUrl = deriveHubUrlForExport(hubUrlWasExplicit, config.hubAddress)\n if (derivedHubUrl !== undefined) {\n process.env['CAMSTACK_HUB_URL'] = derivedHubUrl\n console.log(\n `[Agent] Derived CAMSTACK_HUB_URL=${derivedHubUrl} from hub address \"${config.hubAddress}\"`,\n )\n }\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 // Re-derive CAMSTACK_HUB_URL for the discovery-mode → dashboard-configured\n // flow (config file freshly written a hubAddress). Explicit operator values\n // still win via the boot-captured `hubUrlWasExplicit` flag.\n // KNOWN LIMITATION (Stage-0): runners already spawned before this reconnect\n // keep the env they inherited; picking up a changed hub address in live\n // children would need a runner respawn. Boot-time — the actual trap — is\n // fully covered.\n const freshHubUrl = deriveHubUrlForExport(hubUrlWasExplicit, fresh.hubAddress)\n if (freshHubUrl !== undefined && process.env['CAMSTACK_HUB_URL'] !== freshHubUrl) {\n process.env['CAMSTACK_HUB_URL'] = freshHubUrl\n console.log(\n `[Agent] Derived CAMSTACK_HUB_URL=${freshHubUrl} from hub address \"${fresh.hubAddress}\"`,\n )\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 // ---------------------------------------------------------------------------\n // Hub connection state tracking (for richer UI status)\n // ---------------------------------------------------------------------------\n // Tracks the last known discriminated state so the HTTP status endpoint\n // can surface `secret-mismatch` which is not observable from the broker\n // registry alone. All mutations are synchronous/single-threaded (Node.js\n // event loop) so no locking is needed.\n let hubConnectionStateOverride: HubConnectionState | null = null\n\n function getHubConnectionState(): HubConnectionState {\n // If a definitive override is set (e.g. secret-mismatch), return it\n // regardless of registry state.\n if (hubConnectionStateOverride !== null) return hubConnectionStateOverride\n // Fall through to live registry-derived state (connected/searching/disconnected).\n // getRegistryNodes-equivalent inline: broker.registry.getNodeList\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 if (nodes.some((n) => n.id === 'hub')) return 'connected'\n } catch {\n /* fall through */\n }\n const configNow = readConfigFile(config.configPath)\n const discoveryMode =\n typeof configNow.hubAddress !== 'string' || configNow.hubAddress.length === 0\n return discoveryMode ? 'searching' : 'disconnected'\n }\n\n function readConfigFile(p: string): Record<string, unknown> {\n if (!fs.existsSync(p)) return {}\n try {\n return JSON.parse(fs.readFileSync(p, 'utf-8')) as Record<string, unknown>\n } catch {\n return {}\n }\n }\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 // The agent runtime's OWN providers (server-management) are registered by\n // the bootstrap, not an addon — append their synthetic manifest entry so\n // the hub's HubNodeRegistry (and nodeId-pinned cap routing) sees them.\n result.push(agentRuntimeManifestEntry())\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 // Version visibility (runtime-updatable node packages): the manifest\n // carries the agent's root-package identity so the hub's HubNodeRegistry\n // (and the Server management UI) sees every node's exact version.\n const rootPackage = agentUpdateService.getRunningRootPackage()\n return buildNodeManifest(\n config.nodeId,\n allAddons,\n allNativeCaps.length > 0 ? allNativeCaps : undefined,\n config.secret ? hashClusterSecret(config.secret) : undefined,\n rootPackage.version === null\n ? undefined\n : { name: rootPackage.name, version: rootPackage.version },\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 )\n .then(() => {\n // The hub acked the manifest — the agent reached its registered state.\n // This is the FAST boot-health signal for the runtime-updatable root\n // package probation protocol (first ack only; no-op after).\n confirmAgentBootHealthy('hub-ack')\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 // Surface the mismatch in the UI status.\n hubConnectionStateOverride = 'secret-mismatch'\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 serverManagementCapability,\n ]\n for (const cap of agentCapabilities) {\n capabilityRegistry.declareCapability(cap)\n }\n\n // ── Runtime-updatable root package (phase 2) ─────────────────────────\n // The agent hosts its own `server-management` provider, backed by the\n // update service that stages `@camstack/agent` closures into\n // `<dataDir>/server-root/`. Registered under the synthetic\n // `agent-runtime` addonId (no addon owns it); `buildAgentOwnManifest`\n // appends the matching manifest entry so the hub can route\n // nodeId-pinned `server-management` calls here.\n const agentUpdateService = new AgentUpdateService({\n logger: consoleLogger,\n dataDir: config.dataDir,\n })\n capabilityRegistry.registerProvider(\n 'server-management',\n AGENT_RUNTIME_ADDON_ID,\n buildAgentServerManagementProvider(agentUpdateService),\n )\n\n // Boot-health confirmation: the agent's FAST ready signal is its FIRST acked\n // `$hub.registerNode`. When this boot is the probation boot of a freshly\n // staged root version, promote it (N-1 retained for rollback) and disarm\n // the starter's crash-loop auto-rollback. Idempotent no-op otherwise.\n //\n // Security review #1 — the hub-ack signal must NOT be the ONLY confirm: a\n // restart during a hub outage would leave a genuinely-healthy update\n // eternally unconfirmed, so an unrelated later restart would auto-roll-back\n // a working version. A LOCAL grace-timer fallback (armed AFTER addon-load\n // reaches ready, below) confirms locally when no hub ack arrives within\n // AGENT_LOCAL_CONFIRM_GRACE_MS. Whichever fires first wins (idempotent).\n let agentBootConfirmed = false\n let cancelLocalConfirmFallback: (() => void) | null = null\n const confirmAgentBootHealthy = (source: 'hub-ack' | 'local-grace'): void => {\n if (agentBootConfirmed) return\n agentBootConfirmed = true\n // Whoever confirms first cancels the other path.\n cancelLocalConfirmFallback?.()\n cancelLocalConfirmFallback = null\n try {\n const confirm = agentUpdateService.confirmBootHealthy()\n if (confirm.promoted !== null) {\n consoleLogger.info(\n `agent root package update confirmed healthy (${source}): @camstack/agent@${confirm.promoted}`,\n )\n }\n } catch (err) {\n consoleLogger.warn(\n `agent root boot confirmation failed: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\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 installFromNpm: async (pkg, version) => {\n await installer.install(pkg, version)\n },\n // #17: deployed bundles go through the installer's full post-install\n // (manifest strip + runtime deps + native deps + atomic swap) instead of\n // the bare tar swap that shipped addons without their node_modules.\n installBundleTgz: (tgzPath) => installer.installFromTgz(tgzPath),\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 // `nodeIdMode: 'data'` signal: these caps carry `nodeId` as provider\n // DATA (the hub singleton dispatches internally), never a routing pin —\n // suppressing the pin lets the call forward unpinned to the hub's\n // singleton resolution, with `args.nodeId` intact for the provider.\n isDataNodeIdCap: (capName) => DATA_NODEID_CAPS.has(capName),\n // AGENT → HUB forward: a cap no agent-local child owns is routed to the\n // hub's `$hub-cap-fwd.forward`, which runs it through the hub's own\n // routing. Only the hub registers `$hub-cap-fwd`, so the undirected\n // `broker.call` discovers it there (no nodeID pin needed). This is what\n // makes an agent's forked addon reach a hub-hosted cap (`stream-broker`,\n // `settings-store`, …) instead of 30s-deadlining on raw `${cap}.*`\n // discovery (hub-local addon runners expose no Moleculer service).\n forwardToHub: (input) =>\n (broker as unknown as ServiceBroker).call(\n HUB_CAP_FWD_ACTION,\n {\n capName: input.capName,\n method: input.method,\n args: input.args,\n ...(input.deviceId !== undefined ? { deviceId: input.deviceId } : {}),\n ...(input.nodeId !== undefined ? { nodeId: input.nodeId } : {}),\n },\n { timeout: 60_000 },\n ),\n logger: { warn: (msg) => consoleLogger.warn(`[uds-fallback] ${msg}`) },\n })\n agentUdsRegistry = new LocalChildRegistry({\n server: createLocalTransport().createServer(agentNodeId),\n // The agent's own id — a cap call pinned to THIS agent (e.g. the\n // benchmark running `pipeline-executor.runPipeline` on the very node it\n // is on) is served by the co-resident sibling instead of being forwarded\n // to the hub (the agent has no CapRouteResolver, and the hub would reject\n // it with \"no provider registered\" unless it knew the agent hosts the\n // cap). A pin to ANOTHER node still bypasses the sibling → onUnownedCall.\n ownNodeId: 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 //\n // Caps hosted in the agent's OWN main process — core addons such as\n // `platform-probe` and `metrics-provider` register singleton providers in\n // `capabilityRegistry`, NOT as forked UDS children — are exposed via this\n // in-process lookup so hub-dispatched `agent-child-forward` calls resolve to\n // them instead of failing with \"no provider\". Mirrors the hub's\n // `createInProcessProviderLookup`.\n const agentInProcessLookup: InProcessProviderLookup = (\n capName: string,\n ): InProcessProviderRef | null => {\n const provider = capabilityRegistry.getSingleton<Record<string, unknown>>(capName)\n if (provider === null || provider === undefined) return null\n return {\n invoke: (method: string, args: unknown): Promise<unknown> => {\n const fn = provider[method]\n if (typeof fn !== 'function') {\n return Promise.reject(new Error(`method \"${method}\" not found on cap \"${capName}\"`))\n }\n const result: unknown = fn.call(provider, args)\n return Promise.resolve(result)\n },\n }\n }\n registerAgentCapDispatch(broker, agentUdsRegistry, agentInProcessLookup, 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 // 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 // ── Registry-derived CAMSTACK_HUB_URL (Option A — discovery-mode fallback) ──\n // In UDP-discovery mode `config.hubAddress` is null, so the boot-time\n // derivation above (Option B) produced nothing. The agent parent is the only\n // agent-side process with a Moleculer registry (addon-runner children are\n // broker-less), so derive the hub host from the LIVE hub connection —\n // `udpAddress` is wire truth — and export it through the same env contract\n // before the runner spawns in loadClusterCapableAddons below.\n // Precedence: explicit operator env > config-derived (B) > registry (A).\n // A fills only the empty case; on later hub (re)connects it refreshes only\n // its OWN previous value (hub IP change), never an explicit or B-derived one.\n // BOOT-ORDER LIMITATION (Stage-0): a runner spawned before the first hub\n // connect in discovery mode inherits an empty env until it is respawned —\n // same class as Option B's reconnect limitation documented above. The first\n // hub connect normally precedes any orchestrator attach dispatch, but the\n // child env snapshot is fixed at spawn time.\n let hubUrlFromRegistry: string | undefined\n const reconcileHubUrlFromRegistry = (): void => {\n if (hubUrlWasExplicit) return\n const current = process.env['CAMSTACK_HUB_URL']\n if (current !== undefined && current !== hubUrlFromRegistry) return\n const fromRegistry = deriveHubUrlFromRegistry(\n getRegistryNodes(broker as unknown as ServiceBroker),\n )\n if (fromRegistry === undefined || fromRegistry === current) return\n process.env['CAMSTACK_HUB_URL'] = fromRegistry\n hubUrlFromRegistry = fromRegistry\n console.log(`[Agent] Derived CAMSTACK_HUB_URL=${fromRegistry} from live hub connection`)\n }\n // Immediate attempt (the hub may already be in the registry), then reconcile\n // on every hub (re)connect via the `$node.connected` localBus idiom — NO\n // timers, NO registry polling (CLAUDE.md invariant).\n reconcileHubUrlFromRegistry()\n broker.localBus.on('$node.connected', (data: unknown) => {\n const node = (data as { node?: { id?: string } }).node\n if (node?.id !== 'hub') return\n reconcileHubUrlFromRegistry()\n })\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 // D2: relay to children via the pass-through subscription so the bridge's\n // wildcard does NOT count toward this node's local interest (otherwise the\n // gate could never filter anything).\n subscribePassthrough: (handler) =>\n subscribePassthrough(broker as unknown as ServiceBroker, handler),\n })\n // D2: teach the agent's `$event-bus` inbound handler which cross-node\n // (Moleculer) event categories any of its UDS children actually want, so the\n // agent drops hub-origin categories no local subscriber cares about instead\n // of fanning every category into its bus. Read per-event, so a child\n // (un)subscribing takes effect immediately. Fails OPEN when a child is\n // undeclared (aggregateEventInterest → null). Gated by\n // CAMSTACK_MOLECULER_EVENT_FANOUT (filter|shadow|broadcast).\n const interestRegistry = agentUdsRegistry\n setNodeEventInterest(broker as unknown as ServiceBroker, () =>\n interestRegistry.aggregateEventInterest(),\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 getHubConnectionState,\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 // Security review #1 — addon-load has now reached ready (a purely LOCAL\n // signal, mirroring the hub's local-only confirm). Arm the local boot-health\n // fallback: if no hub ack confirms the probation version within the grace\n // window (hub offline), confirm it locally so a healthy update can't be\n // eternally-unconfirmed and auto-rolled-back by an unrelated later restart.\n // Cancelled on graceful shutdown; unref'd so a crash before the grace does\n // NOT confirm.\n cancelLocalConfirmFallback = armLocalConfirmFallback(() => confirmAgentBootHealthy('local-grace'))\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 // Hub connected — clear any stale secret-mismatch override so the\n // status endpoint reflects the live state again.\n if (hubConnectionStateOverride === 'secret-mismatch') {\n hubConnectionStateOverride = null\n }\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 // Cancel the local boot-health grace so a shutdown BEFORE the grace window\n // elapses does NOT confirm a probation version (security review #1).\n cancelLocalConfirmFallback?.()\n cancelLocalConfirmFallback = null\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/system` (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 // Feed the storage-orchestrator its first-boot seed declarations\n // (addons-data:default, models:default, …). Without this the agent's\n // sqlite-settings aborts boot: \"No default storage location\n // configured for type addons-data\" → fatal crash-loop.\n listStorageLocationDeclarations: () => loader.listStorageLocationDeclarations(),\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 packageVersion: addon.packageVersion,\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 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 packageVersion: registered.packageVersion,\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 // Shared with the reload path (`ensureGroupRunner`) so the boot and\n // reload registrations can never drift — the drift was the in-process\n // reload-wedge's root cause.\n registerGroupRunnerProviders({ broker, capabilityRegistry, loadedAddons }, addons)\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 // Discover deployable addons by reading each package's MANIFEST\n // (package.json `camstack.addons[]`) — NOT by importing the entry module.\n // Importing heavy native addon entries (node-av / onnxruntime bridge / …) on\n // the agent MAIN thread is what wedged `$agent.reload` and dropped the node\n // from topology. A group-runner addon only needs its declaration to be\n // (re)dispatched to a subprocess, which loads the real module itself.\n const packageDirs = resolveAddonPackageDirs(addonsDir)\n const groupCandidates: GroupCandidate[] = []\n // Dirs holding a deployable NON-group addon — and ONLY these — need the\n // importing loader. None exist under the default `hub-only` placement\n // (deployable ⟹ cluster-capable), so the heavy import path is never taken.\n const inProcessDirs = new Set<string>()\n\n for (const dir of packageDirs) {\n const manifest = readPackageManifestAddons(dir)\n if (!manifest) continue\n for (const decl of manifest.declarations) {\n const addonId = decl.id\n if (loadedAddons.has(addonId)) continue\n if (!isDeployableToAgent(decl)) continue\n\n if (isGroupRunnerAddon(decl)) {\n groupCandidates.push({\n groupId: resolveRunnerId(decl, addonId),\n addonId,\n addonDir: dir,\n version: decl.version ?? '0.0.0',\n packageName: manifest.packageName,\n packageVersion: manifest.packageVersion,\n capabilities: decl.capabilities ?? [],\n })\n } else {\n inProcessDirs.add(dir)\n }\n }\n }\n\n // Rare in-process fallback (imports lazily, per-dir, only when such an addon\n // actually exists — never under the current placement model).\n if (inProcessDirs.size > 0) {\n await loadInProcessDeployedAddons(\n broker,\n [...inProcessDirs],\n dataDir,\n loadedAddons,\n storageProvider,\n loggerFactory,\n capabilityRegistry,\n )\n }\n\n // (Re)spawn each cluster-capable group runner with the current on-disk code —\n // no module import on the main thread.\n if (groupCandidates.length === 0) return\n if (!capabilityRegistry) {\n console.warn(\n `[Agent] ${groupCandidates.length} deployed group addon(s) skipped — no capabilityRegistry passed`,\n )\n return\n }\n const grouped = new Map<string, GroupCandidate[]>()\n for (const c of groupCandidates) {\n const arr = grouped.get(c.groupId) ?? []\n arr.push(c)\n grouped.set(c.groupId, arr)\n }\n const deployLogger = loggerFactory('agent-group-runner')\n for (const [groupId, addons] of grouped) {\n await ensureGroupRunner(\n { broker, capabilityRegistry, loadedAddons, logger: deployLogger },\n groupId,\n addons,\n )\n }\n}\n\n/**\n * In-process loader path for deployable NON-group addons (rare). Uses the\n * importing `AddonLoader` — invoked lazily, per-dir, ONLY when such an addon\n * exists. Group-runner addons never reach here, so heavy native modules are\n * never imported on the agent main thread during a reload.\n */\nasync function loadInProcessDeployedAddons(\n broker: BrokerLike,\n dirs: readonly 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 // Node-local models dir — anchored at this node's data root (CAMSTACK_DATA ??\n // the agent --data dir), never the hub-singleton `storage` cap. Mirrors\n // BaseAddon.resolveModelsDir so addons that read the hint and those that\n // self-resolve agree on one path.\n const modelsDir = path.join(process.env['CAMSTACK_DATA'] ?? dataDir, 'models')\n const contextOptions: AddonContextOptions = {\n storageProvider,\n addonConfig: { modelsDir },\n createLogger: loggerFactory,\n capabilityRegistry,\n }\n\n for (const dir of dirs) {\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 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 // Group-runner addons are dispatched to subprocesses by the caller.\n if (isGroupRunnerAddon(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 packageVersion: registered.packageVersion,\n addon: instance,\n })\n\n console.log(`[Agent] Deployed addon \"${addonId}\" loaded in-process`)\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// ---------------------------------------------------------------------------\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[]` into a `RegisterNodeParams`\n * for the agent's subtree HubNodeRegistry.\n *\n * Multi-addon manifest support (co-location): descriptors are grouped by the\n * `addonId` each one carries, producing one manifest entry per hosted addon —\n * a GROUPED runner (`execution.group`) registers each addon's caps under its\n * REAL addon id instead of collapsing them under a synthetic\n * `addonId = childId` (the group name). A legacy child that omits `addonId`\n * falls back to `childId` — identical to the historical single-addon\n * behaviour (childId = runnerId = addonId when no group is declared).\n * Mirrors the hub's `buildChildUdsManifest`.\n *\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 */\nfunction buildAgentChildUdsManifest(\n nodeId: string,\n childId: string,\n caps: readonly ChildCapDescriptor[],\n): RegisterNodeParams {\n const capsByAddon = new Map<string, Set<string>>()\n for (const cap of caps) {\n if (cap.deviceId !== undefined) continue\n const addonId = cap.addonId ?? childId\n const set = capsByAddon.get(addonId) ?? new Set<string>()\n set.add(cap.capName)\n capsByAddon.set(addonId, set)\n }\n if (capsByAddon.size === 0) {\n capsByAddon.set(childId, new Set<string>())\n }\n const addons: readonly RegisteredAddonManifest[] = [...capsByAddon.entries()].map(\n ([addonId, capNames]) => ({ addonId, capabilities: [...capNames] }),\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 * AgentUpdateService — agent-side adapter over the SHARED `RootUpdateService`\n * (`@camstack/node-root`, bundled into the agent dist by tsup). Phase 2 of\n * the runtime-updatable node packages design: stage/apply/rollback for\n * `@camstack/agent` closures in `<agentDataDir>/server-root/`, applied on\n * restart by the agent STARTER (probation boot + auto-rollback).\n *\n * The engine — including the security-review fixes (rollback reentrancy\n * guards, fresh-transient prune protection, `stateFileCorrupt` visibility) —\n * lives in the shared core; this module binds the agent's parameters:\n * - spec `@camstack/agent` + `dist/cli.js`\n * - env markers `CAMSTACK_AGENT_{BOOT_MODE,ACTIVE_VERSION}` +\n * `CAMSTACK_SEED_AGENT_DIR` (written by the agent starter / image)\n * - restart = graceful self-SIGTERM → the supervisor (docker restart\n * policy / Electron main in phase 3) relaunches → starter probation\n * - confirmBootHealthy = the agent's `$hub.registerNode` reaching its\n * acked state (wired in agent-bootstrap.ts)\n *\n * Spec: docs/superpowers/specs/2026-07-12-runtime-updatable-node-packages-design.md\n */\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport {\n AGENT_ROOT_SPEC,\n RootUpdateService,\n serverRootDir,\n writeRestartIntentMarker,\n type NpmExecFn,\n type RootUpdateLogger,\n} from '@camstack/node-root'\nimport type { RegisteredAddonManifest } from '@camstack/system'\nimport type { IServerManagementProvider } from '@camstack/types'\n\n/**\n * The synthetic addonId the agent's OWN runtime registers infra providers\n * under (no addon owns them — the bootstrap does). Appears in the agent's\n * `$hub.registerNode` manifest so the hub's cap routing can reach the\n * agent-hosted `server-management` provider via `nodeId` pinning.\n */\nexport const AGENT_RUNTIME_ADDON_ID = 'agent-runtime'\n\n/** Grace before the self-SIGTERM so the tRPC reply can flush to the hub. */\nexport const AGENT_RESTART_GRACE_MS = 2_000\n\n/**\n * How long the agent runs PAST its own boot/addon-load-ready before the local\n * boot-health fallback confirms a probation version WITHOUT a hub ack. The\n * hub-ack path is the fast confirm; this bound covers a genuinely-offline-hub\n * window so a healthy update can't stay eternally unconfirmed and vulnerable\n * to an unrelated restart's auto-rollback (security review #1).\n */\nexport const AGENT_LOCAL_CONFIRM_GRACE_MS = 90_000\n\n/**\n * Arm the LOCAL boot-health confirmation fallback. Fires `onGrace` after\n * `graceMs`, UNLESS cancelled first (graceful shutdown). Returns the canceller.\n *\n * The timer is `unref()`ed so a genuinely-crashing process (which never runs\n * the returned canceller) exits before it fires — a crash before the grace\n * does NOT confirm. On a healthy-but-hub-offline node the timer fires and\n * confirms locally, so a later unrelated restart can't false-positive-roll-\n * back the update.\n *\n * The caller MUST arm this only AFTER its own boot reaches ready (a purely\n * local signal), mirroring the hub's local-only confirm.\n */\nexport function armLocalConfirmFallback(\n onGrace: () => void,\n graceMs: number = AGENT_LOCAL_CONFIRM_GRACE_MS,\n): () => void {\n const timer = setTimeout(() => onGrace(), graceMs)\n // `unref` is absent on some fake-timer shims — guard it (no cast to `any`).\n const maybeUnref = (timer as { unref?: () => void }).unref\n if (typeof maybeUnref === 'function') maybeUnref.call(timer)\n return () => clearTimeout(timer)\n}\n\n/**\n * Best-effort container detection for the restart-policy startup warning\n * (security review #3). `/.dockerenv` is present in Docker/Podman containers;\n * this is diagnostic only — never gates any logic.\n */\nexport function isRunningInContainer(existsSyncFn: (p: string) => boolean): boolean {\n return existsSyncFn('/.dockerenv')\n}\n\n/**\n * Resolve the RUNNING @camstack/agent package.json. The bundled dist lives at\n * `<pkg>/dist/*.js`, so `../package.json` is the normal hit; the second\n * candidate covers a nested layout. Mirrors `readAgentVersion` in\n * agent-bootstrap.ts.\n */\nexport function resolveAgentPackageJsonPath(fromDir: string): string {\n const candidates = [\n path.resolve(fromDir, '..', 'package.json'),\n path.resolve(fromDir, '..', '..', 'package.json'),\n ]\n for (const candidate of candidates) {\n try {\n const raw = JSON.parse(fs.readFileSync(candidate, 'utf-8')) as { name?: string }\n if (raw.name === AGENT_ROOT_SPEC.packageName) return candidate\n } catch {\n /* keep searching */\n }\n }\n return candidates[0] ?? path.resolve(fromDir, '..', 'package.json')\n}\n\n/**\n * Default restart seam: log, then after a short grace self-deliver SIGTERM so\n * the bootstrap's graceful shutdown runs (addons stopped, broker stopped,\n * exit 0) and the container restart policy relaunches into the starter. A\n * hard exit fallback guards against a wedged shutdown.\n *\n * When `dataDir` is given, drop a restart-intent MARKER in the server-root dir\n * BEFORE the SIGTERM so an external process supervisor (phase 3: the Electron\n * shell) can tell this INTENTIONAL update/rollback restart apart from a crash\n * and not penalise its crash-backoff. The docker restart policy ignores the\n * marker (it relaunches regardless), so this is a no-op there. Best-effort: a\n * marker-write failure must never block the restart.\n */\nexport function scheduleAgentRestart(\n logger: RootUpdateLogger,\n requestedBy: string,\n dataDir?: string,\n): void {\n if (dataDir !== undefined && dataDir.length > 0) {\n try {\n writeRestartIntentMarker(serverRootDir(dataDir), {\n requestedAtMs: Date.now(),\n reason: requestedBy,\n })\n } catch (err) {\n logger.warn('failed to write restart-intent marker', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n logger.warn('agent restart requested — exiting for supervisor relaunch', {\n meta: { requestedBy, graceMs: AGENT_RESTART_GRACE_MS },\n })\n const grace = setTimeout(() => {\n process.kill(process.pid, 'SIGTERM')\n const hard = setTimeout(() => process.exit(0), 15_000)\n hard.unref()\n }, AGENT_RESTART_GRACE_MS)\n grace.unref()\n}\n\nexport interface AgentUpdateServiceOptions {\n readonly logger: RootUpdateLogger\n /** The agent's resolved data dir (config.dataDir). */\n readonly dataDir: string\n /** Overrides for tests. */\n readonly restartAgent?: (requestedBy: string) => void\n readonly execNpm?: NpmExecFn\n readonly env?: NodeJS.ProcessEnv\n readonly now?: () => number\n readonly runningPackageJsonPath?: string\n}\n\nexport class AgentUpdateService extends RootUpdateService {\n constructor(options: AgentUpdateServiceOptions) {\n super({\n spec: AGENT_ROOT_SPEC,\n envNames: {\n bootMode: 'CAMSTACK_AGENT_BOOT_MODE',\n activeVersion: 'CAMSTACK_AGENT_ACTIVE_VERSION',\n seedDir: 'CAMSTACK_SEED_AGENT_DIR',\n },\n logger: options.logger,\n restartServer:\n options.restartAgent ??\n ((requestedBy): void => scheduleAgentRestart(options.logger, requestedBy, options.dataDir)),\n dataDir: options.dataDir,\n runningPackageJsonPath:\n options.runningPackageJsonPath ?? resolveAgentPackageJsonPath(__dirname),\n workspaceProbeDir: __dirname,\n execNpm: options.execNpm,\n env: options.env,\n now: options.now,\n })\n }\n}\n\n/**\n * `server-management` cap provider — thin trampoline over the agent's update\n * service. Registered by agent-bootstrap under {@link AGENT_RUNTIME_ADDON_ID};\n * the hub reaches it through the standard singleton `nodeId` routing\n * (`input.nodeId` → remote proxy → `$agent-cap-fwd` → the agent's in-process\n * provider lookup).\n */\nexport function buildAgentServerManagementProvider(\n service: AgentUpdateService,\n): IServerManagementProvider {\n return {\n getServerPackageStatus: () => service.getServerPackageStatus(),\n checkServerUpdate: () => service.checkServerUpdate(),\n applyServerUpdate: (input) => service.applyServerUpdate(input),\n rollbackServerUpdate: () => service.rollbackServerUpdate(),\n restartServer: () => Promise.resolve(service.restartNode()),\n }\n}\n\n/**\n * The manifest entry for the agent runtime's own providers. Appended to\n * `buildAgentOwnManifest`'s output so `server-management` reaches the hub's\n * `HubNodeRegistry` (and therefore `createCapabilityProxy` node routing) even\n * though no `loadedAddons` entry owns it.\n */\nexport function agentRuntimeManifestEntry(): RegisteredAddonManifest {\n return { addonId: AGENT_RUNTIME_ADDON_ID, capabilities: ['server-management'] }\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { resolveAddonPlacement } from '@camstack/types'\nimport type { AddonDeclaration, IScopedLogger } from '@camstack/types'\nimport type { LoadedAddonEntry } from './agent-service.js'\n\n/** A package's addon declarations read straight from package.json (NO module import). */\nexport interface PackageManifestAddons {\n readonly packageName: string\n readonly packageVersion: string\n readonly declarations: readonly AddonDeclaration[]\n}\n\n/**\n * Read a package's `camstack.addons[]` declarations directly from its\n * package.json — WITHOUT importing the addon entry module.\n *\n * The reload path must NOT `import()` heavy native addon entries (node-av, the\n * onnxruntime bridge, …) on the agent's MAIN thread: doing so blocks the event\n * loop long enough that `$agent.status` stops answering and the hub drops the\n * node from topology — and a hung import wedges `$agent.reload` outright (the\n * reload-wedge). A group-runner addon only needs its declaration (id,\n * capabilities, execution) to be (re)dispatched to a runner SUBPROCESS, which\n * loads the real module itself. Returns null on a missing/corrupt manifest.\n */\nexport function readPackageManifestAddons(dir: string): PackageManifestAddons | null {\n try {\n const pkgPath = path.join(dir, 'package.json')\n if (!fs.existsSync(pkgPath)) return null\n // Documented JSON boundary: the package.json shape is validated field-by-field below.\n const raw = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as {\n name?: unknown\n version?: unknown\n camstack?: { addons?: unknown }\n }\n const packageName = typeof raw.name === 'string' ? raw.name : ''\n const packageVersion = typeof raw.version === 'string' ? raw.version : '0.0.0'\n const entries = Array.isArray(raw.camstack?.addons) ? raw.camstack.addons : []\n const declarations: AddonDeclaration[] = []\n for (const entry of entries) {\n if (\n entry !== null &&\n typeof entry === 'object' &&\n typeof (entry as { id?: unknown }).id === 'string'\n ) {\n // Boundary cast: validated to have a string `id`; the runtime helpers\n // (isDeployableToAgent / resolveRunnerId) read only `execution` / `id`.\n declarations.push(entry as AddonDeclaration)\n }\n }\n if (!packageName || declarations.length === 0) return null\n return { packageName, packageVersion, declarations }\n } catch {\n return null\n }\n}\n\n/**\n * A single addon to be hosted by a forked group runner on the agent.\n *\n * `groupId` is the runner id (`resolveRunnerId` — `execution.group ?? addonId`);\n * addons sharing a `groupId` collapse into one runner subprocess.\n */\nexport interface GroupCandidate {\n readonly groupId: string\n readonly addonId: string\n readonly addonDir: string\n readonly version: string\n readonly packageName: string\n readonly packageVersion: string\n readonly capabilities: ReadonlyArray<string | { readonly name: string }>\n}\n\n/** Minimal broker surface (`call`) used by the group-runner helpers. */\ninterface BrokerCall {\n call<T = unknown>(action: string, params?: unknown, opts?: unknown): Promise<T>\n}\n\n/** Minimal capability-registry surface used by the group-runner helpers. */\ninterface ProviderRegistrar {\n registerProvider(capabilityName: string, addonId: string, provider: unknown): void\n hasProvider(capabilityName: string, addonId: string): boolean\n unregisterProvider(capabilityName: string, addonId: string): void\n}\n\n/** Deps for the provider-registration step (no logging needed). */\nexport interface ProviderDeps {\n readonly broker: BrokerCall\n readonly capabilityRegistry: ProviderRegistrar\n readonly loadedAddons: Map<string, LoadedAddonEntry>\n}\n\n/** Deps for {@link ensureGroupRunner} — adds a logger for the restart/spawn path. */\nexport interface GroupRunnerDeps extends ProviderDeps {\n readonly logger: IScopedLogger\n}\n\n/** Shape of the `$process.restart` action result we branch on. */\ninterface RestartResult {\n readonly success: boolean\n readonly reason?: string\n}\n\n/**\n * Whether a deployed addon must run in a forked group runner (one addon = one\n * process) rather than in-process. True when the addon declares an `execution`\n * block with a placement reachable on an agent (`any-node`/`agent-only`).\n *\n * The default placement is `hub-only`, so a declaration without `execution`\n * is hub-only and not a group-runner addon — and, being hub-only, is never\n * deployable to an agent in the first place.\n */\nexport function isGroupRunnerAddon(decl: Pick<AddonDeclaration, 'execution'>): boolean {\n return decl.execution !== undefined && resolveAddonPlacement(decl) !== 'hub-only'\n}\n\n/**\n * Register the broker-call cap proxies + `loadedAddons` bookkeeping for a group\n * of addons whose runner is (now) live. Extracted so the boot path\n * (`loadClusterCapableAddons`) and the reload path (`ensureGroupRunner`) share\n * ONE implementation — preventing the two from drifting (the original cause of\n * the in-process-reload wedge).\n */\nexport function registerGroupRunnerProviders(\n deps: ProviderDeps,\n addons: readonly GroupCandidate[],\n): void {\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) => deps.broker.call(`${a.addonId}.${capName}.${prop}`, params)\n },\n },\n )\n // Idempotent (re)registration. The reload path (`ensureGroupRunner`)\n // re-runs this after a `$process.restart` of the SUBPROCESS, but the\n // agent's central registry still holds the proxy from the previous\n // registration — the subprocess restart never touched it. Since\n // `registerProvider` throws on a duplicate `(cap, addonId)` pair (a\n // deliberate guard against a double `registerProvider` in one init\n // path), drop the stale proxy first so the fresh one installs cleanly.\n // On the boot path nothing is registered yet, so this is a no-op.\n // Without this, an in-place addon update/redeploy leaves the whole group\n // stuck in `error` until a full agent (main-process) restart clears the\n // registry.\n if (deps.capabilityRegistry.hasProvider(capName, a.addonId)) {\n deps.capabilityRegistry.unregisterProvider(capName, a.addonId)\n }\n deps.capabilityRegistry.registerProvider(capName, a.addonId, proxy)\n }\n deps.loadedAddons.set(a.addonId, {\n id: a.addonId,\n status: 'running',\n version: a.version,\n packageName: a.packageName,\n packageVersion: a.packageVersion,\n })\n }\n}\n\n/**\n * Ensure a group runner is live with the CURRENT on-disk addon code, then wire\n * its providers.\n *\n * A redeploy's runner is already running (the deploy step swapped its on-disk\n * code but never killed the process), and `$process.spawnRunner` throws\n * \"already running\" for a live runnerId. So restart first — `$process.restart`\n * stops the old child, evicts it from the Moleculer registry, and re-spawns it\n * from the same dir (now holding the new code) in a subprocess, off the agent's\n * main event loop. Only when no runner exists yet (first deploy →\n * `{success:false, reason:'not found'}`) do we `$process.spawnRunner`.\n *\n * On a hard failure of both paths the addons are marked `error` (no providers\n * registered) so `$agent.status` reports the degraded state.\n */\nexport async function ensureGroupRunner(\n deps: GroupRunnerDeps,\n groupId: string,\n addons: readonly GroupCandidate[],\n): Promise<void> {\n try {\n const restart = await deps.broker.call<RestartResult>('$process.restart', { name: groupId })\n if (!restart.success) {\n if (restart.reason !== undefined && restart.reason !== 'not found') {\n deps.logger.warn(\n `group \"${groupId}\" restart returned \"${restart.reason}\" — spawning a fresh runner`,\n )\n }\n await deps.broker.call('$process.spawnRunner', {\n runnerId: groupId,\n addons: addons.map((a) => ({ addonId: a.addonId, addonDir: a.addonDir })),\n })\n }\n registerGroupRunnerProviders(deps, addons)\n deps.logger.info(\n `group \"${groupId}\" live 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 deps.logger.error(`failed to ensure group \"${groupId}\": ${msg}`)\n for (const a of addons) {\n deps.loadedAddons.set(a.addonId, { id: a.addonId, status: 'error' })\n }\n }\n}\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 InProcessProviderLookup,\n LocalChildRegistryLogger,\n} from '@camstack/system'\nimport { AGENT_CAP_FWD_SERVICE } from '@camstack/system'\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 inProcessLookup Optional resolver for caps hosted in the agent's OWN main process\n * (core addons such as `platform-probe`, `metrics-provider` register\n * their singleton providers in the agent's `CapabilityRegistry`, NOT\n * as forked UDS children). Consulted ONLY when no UDS child provides\n * the cap. Without it, agent in-process core caps are unroutable from\n * the hub.\n * @param logger Optional scoped logger; logs the no-provider path at INFO level.\n */\nfunction createAgentCapDispatchService(\n agentNodeId: string,\n agentUdsRegistry: HubLocalChildDispatcher,\n inProcessLookup?: InProcessProviderLookup,\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 // No forked UDS child owns this cap. It may instead be hosted by a\n // core addon running in the agent's OWN main process (platform-probe,\n // metrics-provider, …) — resolve it against the in-process registry.\n const ref = inProcessLookup?.(capName) ?? null\n if (ref !== null) {\n return ref.invoke(method, args)\n }\n logger?.info(\n `agent ${agentNodeId}: no provider for cap \"${capName}\"${deviceId !== undefined ? ` (deviceId ${deviceId})` : ''}`,\n )\n throw new Error(\n `agent ${agentNodeId} has no provider 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 {\n HubLocalChildDispatcher,\n InProcessProviderLookup,\n LocalChildRegistryLogger,\n} from '@camstack/system'\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 inProcessLookup Optional resolver for caps hosted in the agent's own\n * main process (core addons); forwarded to the service\n * so hub-dispatched calls to those caps resolve instead\n * of failing with \"no provider\".\n * @param logger Optional scoped logger forwarded to the service.\n */\nexport function registerAgentCapDispatch(\n registrar: AgentServiceRegistrar,\n agentUdsRegistry: HubLocalChildDispatcher | undefined,\n inProcessLookup?: InProcessProviderLookup,\n logger?: LocalChildRegistryLogger,\n): void {\n if (agentUdsRegistry === undefined) return\n registrar.createService(\n createAgentCapDispatchService(registrar.nodeID, agentUdsRegistry, inProcessLookup, logger),\n )\n}\n"],"mappings":"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","names":["fs","os","path","fs","path","randomBytes","resolve","buf","addonDir","cpus","fs","path","fs","path","resolveAddonPlacement","fs","path","fs","path","readConfigFile","resolveAddonPlacement"]}
|