@camstack/agent 1.1.50 → 1.1.52
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-P6AJ6S7H.mjs → chunk-67UOSAWB.mjs} +2 -2
- package/dist/{chunk-Y6BUFOWG.mjs → chunk-KPUXW5YX.mjs} +84 -55
- package/dist/chunk-KPUXW5YX.mjs.map +1 -0
- package/dist/cli.js +113 -72
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +30 -18
- package/dist/cli.mjs.map +1 -1
- package/dist/index.js +83 -54
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/starter.js +83 -54
- package/dist/starter.js.map +1 -1
- package/dist/starter.mjs +1 -1
- package/package.json +8 -8
- package/dist/chunk-Y6BUFOWG.mjs.map +0 -1
- /package/dist/{chunk-P6AJ6S7H.mjs.map → chunk-67UOSAWB.mjs.map} +0 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../node-root/src/index.ts","../../node-root/src/dev-uploads.ts","../../node-root/src/root-package-spec.ts","../../node-root/src/root-state.ts","../../node-root/src/boot-plan.ts","../../node-root/src/semver-compare.ts","../../node-root/src/workspace-detect.ts","../../node-root/src/starter-core.ts","../../node-root/src/root-update-service.ts","../src/index.ts","../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-bootstrap.ts","../src/agent-http.ts","../src/derive-hub-url.ts","../src/agent-http-auth.ts","../src/agent-update-service.ts","../src/agent-group-runner.ts","../src/agent-cap-dispatch-service.ts","../src/register-agent-cap-dispatch.ts"],"sourcesContent":["/**\n * @camstack/node-root — the shared, zero-runtime-dep node-root boot core.\n *\n * PRIVATE workspace package: it is BUNDLED at build time into the hub's\n * `dist/server-root/index.js` (tsup) and into the agent's tsup output —\n * never published, never resolved from node_modules at runtime. Both node\n * starters and both update services are thin per-node adapters over these\n * modules, so the boot-plan / probation / rollback semantics can never\n * drift between the hub and the agent.\n *\n * Spec: docs/superpowers/specs/2026-07-12-runtime-updatable-node-packages-design.md\n */\nexport * from './dev-uploads.js'\nexport * from './root-package-spec.js'\nexport * from './root-state.js'\nexport * from './boot-plan.js'\nexport * from './semver-compare.js'\nexport * from './workspace-detect.js'\nexport * from './starter-core.js'\nexport * from './root-update-service.js'\n","/**\n * Dev server-deploy channel — layout + manifest for locally-uploaded root\n * package closures (ZERO-DEP: node:fs/node:path only, same constraint as\n * root-state.ts — the baked starter bundles this module).\n *\n * `camstack deploy-server` packs every workspace package in the server's\n * `@camstack/*` closure at one uniform dev version\n * (`<base>-dev.<epochSeconds>`) and uploads the tarballs to\n * `POST /api/server-upload`. The hub stores them under:\n *\n * <dataDir>/server-root/dev-uploads/<version>/\n * <flattened-pkg-name>.tgz one per uploaded package\n * manifest.json { version, packages: { name → filename } }\n *\n * `RootUpdateService.stageAndActivate` detects the dir and stages from these\n * tarballs (synthetic package.json with `file:` refs + `overrides`) instead\n * of the npm registry. Everything downstream — probation boot, promote,\n * rollback — is version-string driven and unchanged; the `-dev.` suffix is\n * self-describing.\n *\n * Spec: docs/superpowers/specs — dev server-deploy channel (2026-07-13).\n */\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\n\nexport const DEV_UPLOADS_DIRNAME = 'dev-uploads'\nexport const DEV_UPLOAD_MANIFEST_FILE = 'manifest.json'\n\n/** Number of dev-uploads entries retained by the promote-time sweep. */\nexport const DEV_UPLOADS_KEEP_COUNT = 3\n\n/**\n * Dev-channel version shape: `<semver-base>-dev.<epochSeconds>`. The suffix\n * is sortable, self-describing, and can never collide with a published\n * registry version. The upload route and the staging branch BOTH gate on it.\n */\nconst DEV_CHANNEL_VERSION_RE = /-dev\\.\\d+$/\n\nexport function isDevChannelVersion(version: string): boolean {\n return DEV_CHANNEL_VERSION_RE.test(version)\n}\n\n/**\n * The `<epochSeconds>` ordering key of a dev-channel version (or dir name).\n * Returns null for anything that is not dev-channel shaped — the sweep\n * treats those as oldest so malformed residue is collected first.\n */\nexport function devChannelEpoch(version: string): number | null {\n const match = /-dev\\.(\\d+)$/.exec(version)\n if (match === null) return null\n const epoch = Number.parseInt(match[1] ?? '', 10)\n return Number.isNaN(epoch) ? null : epoch\n}\n\n/** `<dataDir>/server-root/dev-uploads` */\nexport function devUploadsDir(rootDir: string): string {\n return path.join(rootDir, DEV_UPLOADS_DIRNAME)\n}\n\n/** `<dataDir>/server-root/dev-uploads/<version>` */\nexport function devUploadVersionDir(rootDir: string, version: string): string {\n return path.join(devUploadsDir(rootDir), version)\n}\n\n// ---------------------------------------------------------------------------\n// Manifest IO\n// ---------------------------------------------------------------------------\n\n/**\n * Written by the hub's `/api/server-upload` route after validating every\n * tarball; read by the staging branch. Maps each uploaded package NAME to\n * its tgz FILENAME inside the version dir — staging never has to re-derive\n * package names from flattened filenames.\n */\nexport interface DevUploadManifest {\n readonly version: string\n readonly packages: Readonly<Record<string, string>>\n}\n\nexport function devUploadManifestPath(versionDirPath: string): string {\n return path.join(versionDirPath, DEV_UPLOAD_MANIFEST_FILE)\n}\n\nfunction isDevUploadManifest(v: unknown): v is DevUploadManifest {\n if (typeof v !== 'object' || v === null) return false\n const m = v as Record<string, unknown>\n if (typeof m['version'] !== 'string') return false\n const packages = m['packages']\n if (typeof packages !== 'object' || packages === null || Array.isArray(packages)) return false\n return Object.values(packages).every((filename) => typeof filename === 'string')\n}\n\n/** Read + validate the manifest. Returns null when missing/corrupt. */\nexport function readDevUploadManifest(versionDirPath: string): DevUploadManifest | null {\n try {\n const raw: unknown = JSON.parse(fs.readFileSync(devUploadManifestPath(versionDirPath), 'utf-8'))\n return isDevUploadManifest(raw) ? raw : null\n } catch {\n return null\n }\n}\n\n/** Write the manifest atomically (tmp sibling + rename). Creates the dir. */\nexport function writeDevUploadManifest(versionDirPath: string, manifest: DevUploadManifest): void {\n fs.mkdirSync(versionDirPath, { recursive: true })\n const target = devUploadManifestPath(versionDirPath)\n const tmp = `${target}.tmp`\n fs.writeFileSync(tmp, JSON.stringify(manifest, null, 2), 'utf-8')\n fs.renameSync(tmp, target)\n}\n","/**\n * Root-package spec — the per-node-type parameters of the shared node-root\n * boot machinery. Everything else (the `server-root/` data-dir layout, the\n * `state.json` schema, the probation/rollback boot plan, the update service)\n * is IDENTICAL between the hub and the agent; only the root package's name\n * and its loadable entry differ.\n *\n * Spec: docs/superpowers/specs/2026-07-12-runtime-updatable-node-packages-design.md\n */\n\nexport interface RootPackageSpec {\n /** The npm name of the node's root package. */\n readonly packageName: string\n /**\n * Path segments of the loadable entry INSIDE the root package dir\n * (`node_modules/<packageName>/<...entryRelPath>`).\n */\n readonly entryRelPath: readonly string[]\n}\n\n/** The hub's root package — the starter loads its launcher. */\nexport const HUB_ROOT_SPEC: RootPackageSpec = {\n packageName: '@camstack/server',\n entryRelPath: ['dist', 'launcher.js'],\n}\n\n/** The agent's root package — the starter loads its CLI entry. */\nexport const AGENT_ROOT_SPEC: RootPackageSpec = {\n packageName: '@camstack/agent',\n entryRelPath: ['dist', 'cli.js'],\n}\n","/**\n * Data-dir server-root layout + state file — ZERO-DEP (node:fs/node:path only).\n *\n * Loaded by each node's baked STARTER before any node_modules resolution is\n * trusted, so this module must never import from `@camstack/*` (runtime) or\n * any npm package. The hub's `ServerUpdateService` and the agent's update\n * service consume the same module — one source of truth for the layout and\n * the state shape. The layout is IDENTICAL on both node types (the dir is\n * named `server-root/` everywhere); only the root package name + entry differ\n * and arrive via {@link RootPackageSpec}.\n *\n * Layout (`<dataDir>/server-root/`):\n * versions/<semver>/ npm-installed closure for one version\n * node_modules/<pkg>/<entry> ← the loadable entry (spec-defined)\n * state.json pointer file (ServerRootState, atomic)\n *\n * A POINTER FILE (state.json) is used instead of `current`/`previous`\n * symlinks: atomic via tmp+rename, works on every fs, and carries the\n * probation/rollback bookkeeping in the same durable record.\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 type { RootPackageSpec } from './root-package-spec.js'\n\nexport const SERVER_ROOT_DIRNAME = 'server-root'\nexport const SERVER_ROOT_STATE_FILE = 'state.json'\n/**\n * Restart-intent marker filename. Written by the update service moments before\n * a graceful self-restart (apply/rollback), then read + consumed by an\n * external process supervisor so it can tell an INTENTIONAL restart-for-update\n * apart from a crash — a crash-backoff policy must never penalise an update\n * restart. Lives in the server-root dir next to `state.json`.\n *\n * (Phase 3 — the Electron shell supervisor reads it; the docker restart policy\n * relaunches unconditionally so the marker is a harmless no-op there.)\n */\nexport const RESTART_INTENT_FILE = '.restart-intent'\n\n// ---------------------------------------------------------------------------\n// State shape\n// ---------------------------------------------------------------------------\n\nexport interface ServerRootPendingBoot {\n /** The freshly-staged version awaiting its probation boot. */\n readonly version: string\n /** The version that was current when the update was requested (null = baked). */\n readonly fromVersion: string | null\n readonly requestedAtMs: number\n /** 0 = not yet booted; >=1 = a probation boot started and never confirmed. */\n readonly bootAttempts: number\n}\n\nexport interface ServerRootRollbackInfo {\n readonly fromVersion: string\n /** null = rolled back all the way to the baked seed. */\n readonly toVersion: string | null\n readonly atMs: number\n readonly reason: string\n}\n\nexport interface ServerRootState {\n readonly schemaVersion: 1\n /** The confirmed-healthy active version (null = boot from the baked seed). */\n readonly currentVersion: string | null\n /** N-1 kept for rollback (its versions/ dir is retained). */\n readonly previousVersion: string | null\n readonly pendingBoot: ServerRootPendingBoot | null\n /** Set when the most recent update attempt was rolled back. */\n readonly rolledBack: ServerRootRollbackInfo | null\n}\n\nexport function emptyServerRootState(): ServerRootState {\n return {\n schemaVersion: 1,\n currentVersion: null,\n previousVersion: null,\n pendingBoot: null,\n rolledBack: null,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Layout helpers\n// ---------------------------------------------------------------------------\n\nexport function serverRootDir(dataDir: string): string {\n return path.join(dataDir, SERVER_ROOT_DIRNAME)\n}\n\nexport function versionsDir(rootDir: string): string {\n return path.join(rootDir, 'versions')\n}\n\nexport function versionDir(rootDir: string, version: string): string {\n return path.join(versionsDir(rootDir), version)\n}\n\n/** `versions/<v>/node_modules/<packageName>` — the installed root package dir. */\nexport function rootPackageDir(versionDirPath: string, spec: RootPackageSpec): string {\n return path.join(versionDirPath, 'node_modules', ...spec.packageName.split('/'))\n}\n\n/** The loadable entry inside the installed root package dir. */\nexport function rootEntryPath(versionDirPath: string, spec: RootPackageSpec): string {\n return path.join(rootPackageDir(versionDirPath, spec), ...spec.entryRelPath)\n}\n\nexport function stateFilePath(rootDir: string): string {\n return path.join(rootDir, SERVER_ROOT_STATE_FILE)\n}\n\n// ---------------------------------------------------------------------------\n// State IO (atomic, corruption-tolerant)\n// ---------------------------------------------------------------------------\n\nfunction isNullableString(v: unknown): v is string | null {\n return v === null || typeof v === 'string'\n}\n\nfunction isPendingBoot(v: unknown): v is ServerRootPendingBoot {\n if (typeof v !== 'object' || v === null) return false\n const p = v as Record<string, unknown>\n return (\n typeof p['version'] === 'string' &&\n isNullableString(p['fromVersion']) &&\n typeof p['requestedAtMs'] === 'number' &&\n typeof p['bootAttempts'] === 'number'\n )\n}\n\nfunction isRollbackInfo(v: unknown): v is ServerRootRollbackInfo {\n if (typeof v !== 'object' || v === null) return false\n const r = v as Record<string, unknown>\n return (\n typeof r['fromVersion'] === 'string' &&\n isNullableString(r['toVersion']) &&\n typeof r['atMs'] === 'number' &&\n typeof r['reason'] === 'string'\n )\n}\n\nexport function isServerRootState(v: unknown): v is ServerRootState {\n if (typeof v !== 'object' || v === null) return false\n const s = v as Record<string, unknown>\n if (s['schemaVersion'] !== 1) return false\n if (!isNullableString(s['currentVersion'])) return false\n if (!isNullableString(s['previousVersion'])) return false\n if (s['pendingBoot'] !== null && !isPendingBoot(s['pendingBoot'])) return false\n if (s['rolledBack'] !== null && !isRollbackInfo(s['rolledBack'])) return false\n return true\n}\n\n/** Read + validate the state file. Returns null when missing/corrupt/mismatched. */\nexport function readServerRootState(rootDir: string): ServerRootState | null {\n try {\n const raw: unknown = JSON.parse(fs.readFileSync(stateFilePath(rootDir), 'utf-8'))\n return isServerRootState(raw) ? raw : null\n } catch {\n return null\n }\n}\n\n/** Write the state file atomically (tmp sibling + rename). Creates rootDir. */\nexport function writeServerRootState(rootDir: string, state: ServerRootState): void {\n fs.mkdirSync(rootDir, { recursive: true })\n const target = stateFilePath(rootDir)\n const tmp = `${target}.tmp`\n fs.writeFileSync(tmp, JSON.stringify(state, null, 2), 'utf-8')\n fs.renameSync(tmp, target)\n}\n\n// ---------------------------------------------------------------------------\n// Restart-intent marker (supervisor: update-restart vs crash discrimination)\n// ---------------------------------------------------------------------------\n\n/**\n * Payload of the restart-intent marker. `requestedAtMs` lets a reader ignore a\n * STALE marker (e.g. one left behind by a hard kill between write and\n * relaunch) via a freshness window instead of mis-classifying a much-later\n * crash as an update restart. `reason` carries the human `requestedBy` string.\n */\nexport interface RestartIntentMarker {\n readonly requestedAtMs: number\n readonly reason: string\n}\n\nexport function restartIntentMarkerPath(rootDir: string): string {\n return path.join(rootDir, RESTART_INTENT_FILE)\n}\n\nfunction isRestartIntentMarker(v: unknown): v is RestartIntentMarker {\n if (typeof v !== 'object' || v === null) return false\n const m = v as Record<string, unknown>\n return typeof m['requestedAtMs'] === 'number' && typeof m['reason'] === 'string'\n}\n\n/** Write the marker atomically (tmp sibling + rename). Creates rootDir. */\nexport function writeRestartIntentMarker(rootDir: string, marker: RestartIntentMarker): void {\n fs.mkdirSync(rootDir, { recursive: true })\n const target = restartIntentMarkerPath(rootDir)\n const tmp = `${target}.tmp`\n fs.writeFileSync(tmp, JSON.stringify(marker, null, 2), 'utf-8')\n fs.renameSync(tmp, target)\n}\n\n/** Read + validate the marker. Returns null when missing/corrupt. */\nexport function readRestartIntentMarker(rootDir: string): RestartIntentMarker | null {\n try {\n const raw: unknown = JSON.parse(fs.readFileSync(restartIntentMarkerPath(rootDir), 'utf-8'))\n return isRestartIntentMarker(raw) ? raw : null\n } catch {\n return null\n }\n}\n\n/** Delete the marker if present (idempotent — a missing file is not an error). */\nexport function clearRestartIntentMarker(rootDir: string): void {\n try {\n fs.rmSync(restartIntentMarkerPath(rootDir), { force: true })\n } catch {\n /* best-effort */\n }\n}\n\n// ---------------------------------------------------------------------------\n// Version-dir validation\n// ---------------------------------------------------------------------------\n\n/**\n * Extract the minimum required Node major from an `engines.node` range like\n * `>=22`, `>= 22.12`, `^24.0.0`, `22.x`. Returns null when unparseable\n * (validation is then permissive — a bogus range must not brick boot).\n */\nexport function minNodeMajorOf(enginesNode: string): number | null {\n const match = /(\\d+)/.exec(enginesNode)\n if (match === null) return null\n const major = Number.parseInt(match[1] ?? '', 10)\n return Number.isNaN(major) ? null : major\n}\n\n/**\n * Validate that `versions/<version>` holds a loadable root-package closure\n * for `spec`. Returns null when valid, else a human-readable reason. Checks:\n * 1. the entry file exists,\n * 2. the package.json parses with the right `name` + `version`,\n * 3. `engines.node` (when parseable) is satisfied by the running runtime —\n * the starter refuses a native-ABI mismatch with a clear error instead\n * of segfaulting (design risk: node bump vs shm-ring prebuilds).\n */\nexport function validateVersionDir(\n rootDir: string,\n version: string,\n nodeMajor: number,\n spec: RootPackageSpec,\n): string | null {\n const vDir = versionDir(rootDir, version)\n const entry = rootEntryPath(vDir, spec)\n if (!fs.existsSync(entry)) {\n return `root entry missing: ${entry}`\n }\n\n const pkgJsonPath = path.join(rootPackageDir(vDir, spec), 'package.json')\n let pkg: Record<string, unknown>\n try {\n const raw: unknown = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'))\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n return `package.json malformed: ${pkgJsonPath}`\n }\n pkg = raw as Record<string, unknown>\n } catch {\n return `package.json unreadable: ${pkgJsonPath}`\n }\n\n if (pkg['name'] !== spec.packageName) {\n return `package name mismatch: expected ${spec.packageName}, got ${String(pkg['name'])}`\n }\n if (pkg['version'] !== version) {\n return `package version mismatch: dir says ${version}, package.json says ${String(pkg['version'])}`\n }\n\n const engines = pkg['engines']\n if (typeof engines === 'object' && engines !== null) {\n const enginesNode = (engines as Record<string, unknown>)['node']\n if (typeof enginesNode === 'string') {\n const minMajor = minNodeMajorOf(enginesNode)\n if (minMajor !== null && nodeMajor < minMajor) {\n return `engines.node \"${enginesNode}\" requires Node >= ${minMajor}, runtime is ${nodeMajor}`\n }\n }\n }\n\n return null\n}\n","/**\n * Pure boot-plan decision for the server-root starter — ZERO-DEP.\n *\n * Given the durable `ServerRootState` and a validity predicate for staged\n * version dirs, decide WHICH closure this boot should load and what the\n * state file must be updated to. All filesystem access is behind the\n * injected `isValidVersion` callback so the decision itself is unit-testable.\n *\n * Probation / rollback protocol (mirrors `launcher-framework-swap.ts`):\n * - `pendingBoot.bootAttempts === 0` → this is the probation boot: bump the\n * counter durably BEFORE loading the new version. The hub clears the\n * pending record (promoting the version to `currentVersion`) only once it\n * reaches ready (`ServerUpdateService.confirmBootHealthy`).\n * - `pendingBoot.bootAttempts >= 1` → the previous probation boot died\n * before confirming: auto-rollback to `currentVersion` (or baked) and\n * record `rolledBack` so the status cap can surface it.\n */\nimport type { ServerRootState } from './root-state.js'\n\nexport type BootPlan =\n | {\n readonly kind: 'baked'\n readonly reason: string\n readonly stateToWrite: ServerRootState | null\n }\n | {\n readonly kind: 'data-root'\n readonly version: string\n /** True when this boot must be confirmed healthy (freshly-staged version). */\n readonly probation: boolean\n readonly stateToWrite: ServerRootState | null\n }\n\ntype IsValidVersion = (version: string) => boolean\ntype NowFn = () => number\n\n/**\n * Decide the boot target. Never throws; never mutates `state`.\n * `stateToWrite` is non-null whenever the durable state must change\n * (probation counter bump, rollback, invalid-version eviction).\n */\nexport function planBoot(\n state: ServerRootState | null,\n isValidVersion: IsValidVersion,\n now: NowFn,\n): BootPlan {\n if (state === null) {\n return { kind: 'baked', reason: 'no server-root state', stateToWrite: null }\n }\n\n let next: ServerRootState = state\n let changed = false\n\n // ── Pending (freshly-staged) version ──────────────────────────────────\n if (next.pendingBoot !== null) {\n const pending = next.pendingBoot\n if (pending.bootAttempts >= 1) {\n // Previous probation boot never confirmed healthy — roll back.\n next = {\n ...next,\n pendingBoot: null,\n rolledBack: {\n fromVersion: pending.version,\n toVersion: next.currentVersion,\n atMs: now(),\n reason: 'probation boot did not reach ready — rolled back',\n },\n }\n changed = true\n } else if (!isValidVersion(pending.version)) {\n next = {\n ...next,\n pendingBoot: null,\n rolledBack: {\n fromVersion: pending.version,\n toVersion: next.currentVersion,\n atMs: now(),\n reason: 'staged version failed validation',\n },\n }\n changed = true\n } else {\n // Probation boot: bump the attempt counter durably, keep currentVersion\n // untouched until the hub confirms the boot healthy.\n return {\n kind: 'data-root',\n version: pending.version,\n probation: true,\n stateToWrite: {\n ...next,\n pendingBoot: { ...pending, bootAttempts: pending.bootAttempts + 1 },\n },\n }\n }\n }\n\n // ── Confirmed current version ──────────────────────────────────────────\n if (next.currentVersion !== null) {\n if (isValidVersion(next.currentVersion)) {\n return {\n kind: 'data-root',\n version: next.currentVersion,\n probation: false,\n stateToWrite: changed ? next : null,\n }\n }\n\n // Current version dir is broken — try N-1, else baked.\n const broken = next.currentVersion\n if (next.previousVersion !== null && isValidVersion(next.previousVersion)) {\n const fallback = next.previousVersion\n return {\n kind: 'data-root',\n version: fallback,\n probation: false,\n stateToWrite: {\n ...next,\n currentVersion: fallback,\n previousVersion: null,\n rolledBack: {\n fromVersion: broken,\n toVersion: fallback,\n atMs: now(),\n reason: 'active version failed validation',\n },\n },\n }\n }\n\n return {\n kind: 'baked',\n reason: `active version ${broken} failed validation and no valid previous version exists`,\n stateToWrite: {\n ...next,\n currentVersion: null,\n rolledBack: {\n fromVersion: broken,\n toVersion: null,\n atMs: now(),\n reason: 'active version failed validation',\n },\n },\n }\n }\n\n return {\n kind: 'baked',\n reason: 'no active data-root version',\n stateToWrite: changed ? next : null,\n }\n}\n","/**\n * Minimal semver comparison — zero-dep (used by the starter, which must never\n * import from node_modules). Handles `MAJOR.MINOR.PATCH[-prerelease]`:\n * numeric segment compare, missing segments = 0, prerelease < its release,\n * prereleases of the same base compare lexically. Malformed segments compare\n * as 0 (best effort — never throws).\n */\n\ntype SemverParts = {\n readonly nums: readonly number[]\n readonly prerelease: string | null\n}\n\nfunction parse(version: string): SemverParts {\n const dashIdx = version.indexOf('-')\n const base = dashIdx === -1 ? version : version.slice(0, dashIdx)\n const prerelease = dashIdx === -1 ? null : version.slice(dashIdx + 1)\n const nums = base.split('.').map((seg) => {\n const n = Number.parseInt(seg, 10)\n return Number.isNaN(n) ? 0 : n\n })\n return { nums, prerelease }\n}\n\n/** Returns -1 when a < b, 0 when equal, 1 when a > b. */\nexport function compareSemver(a: string, b: string): -1 | 0 | 1 {\n const pa = parse(a)\n const pb = parse(b)\n const len = Math.max(pa.nums.length, pb.nums.length)\n for (let i = 0; i < len; i++) {\n const na = pa.nums[i] ?? 0\n const nb = pb.nums[i] ?? 0\n if (na < nb) return -1\n if (na > nb) return 1\n }\n if (pa.prerelease === null && pb.prerelease === null) return 0\n if (pa.prerelease === null) return 1\n if (pb.prerelease === null) return -1\n if (pa.prerelease < pb.prerelease) return -1\n if (pa.prerelease > pb.prerelease) return 1\n return 0\n}\n","/**\n * Workspace-checkout detection for the starter — ZERO-DEP.\n *\n * The starter must defer to plain resolution in a dev checkout (`npm run\n * dev:full` / `npm start` from the monorepo): the data-dir server root is a\n * production mechanism and must never shadow freshly-built workspace code.\n * Detection = any ancestor package.json declaring `workspaces` (the monorepo\n * root has one; the baked image run-dir's `npm init -y` package.json and the\n * data-dir version closures never do).\n */\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\n\n/**\n * Walk up from `fromDir` looking for a package.json with a `workspaces`\n * field. Returns the directory containing it, or null when none exists.\n */\nexport function detectWorkspaceRoot(fromDir: string): string | null {\n let dir = path.resolve(fromDir)\n for (;;) {\n const pkgPath = path.join(dir, 'package.json')\n try {\n const raw: unknown = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))\n if (\n typeof raw === 'object' &&\n raw !== null &&\n (raw as Record<string, unknown>)['workspaces'] !== undefined\n ) {\n return dir\n }\n } catch {\n // missing / unreadable package.json — keep walking\n }\n const parent = path.dirname(dir)\n if (parent === dir) return null\n dir = parent\n }\n}\n","/**\n * Shared STARTER core — the boot flow both node starters (hub + agent) run.\n * ZERO-DEP: bundled into each starter at build time; must never resolve\n * anything from node_modules at runtime.\n *\n * Boot sequence (identical on both node types; per-node names arrive via\n * {@link NodeStarterOptions}):\n * 1. Workspace checkout? → defer to plain resolution (load the sibling\n * seed entry; the dev loop never runs the built starter at all).\n * 2. Read `<dataDir>/server-root/state.json`, run the pure boot plan\n * (probation counter / auto-rollback / validity fallback) and persist\n * any state change BEFORE loading code.\n * 3. Data-root chosen → register in-process resolver hooks so the node's\n * MAIN process resolves the host-external packages (@camstack/system, …)\n * from the ACTIVE root's node_modules, then load that root's entry.\n * 4. Otherwise → load the baked seed entry next to the starter.\n *\n * Console usage is intentional: the starter runs before any logging exists.\n * Every line is prefixed `[starter]`.\n */\nimport * as path from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { planBoot } from './boot-plan.js'\nimport type { RootPackageSpec } from './root-package-spec.js'\nimport {\n readServerRootState,\n rootEntryPath,\n serverRootDir,\n validateVersionDir,\n versionDir,\n writeServerRootState,\n} from './root-state.js'\nimport { detectWorkspaceRoot } from './workspace-detect.js'\n\n/**\n * Mirror of the host-external list in\n * `packages/system/src/kernel/moleculer/framework-resolver-hook.mjs` — kept\n * inline because the starter must stay zero-dep. Only these specifiers are\n * redirected to the active root; everything else resolves normally.\n */\nexport const HOST_EXTERNAL_SPECIFIERS: readonly string[] = [\n '@camstack/system',\n '@camstack/shm-ring',\n '@camstack/types',\n '@camstack/sdk',\n 'zod',\n '@trpc/server',\n '@trpc/client',\n 'sharp',\n 'node-av',\n]\n\nexport function isHostExternal(specifier: string): boolean {\n return HOST_EXTERNAL_SPECIFIERS.some(\n (name) => specifier === name || specifier.startsWith(`${name}/`),\n )\n}\n\ntype ResolveContext = { readonly parentURL?: string }\ntype NextResolve = (specifier: string, context?: ResolveContext) => unknown\ntype RegisterHooksFn = (hooks: {\n resolve: (specifier: string, context: ResolveContext, nextResolve: NextResolve) => unknown\n}) => void\n\n/**\n * Register in-process resolver hooks (Node >= 22.15 `module.registerHooks`,\n * covers BOTH require and import) that anchor host-external resolution inside\n * the active root's node_modules. Failure-tolerant: if the anchored resolve\n * fails (package absent from the closure, e.g. sharp/node-av), fall back to\n * the default walk (NODE_PATH /data/framework still covers those).\n */\nexport function registerActiveRootResolver(activeRootDir: string): void {\n // Documented type boundary: `module.registerHooks` is not yet in the\n // bundled @types/node — feature-detect it off the untyped module surface.\n const moduleApi: Record<string, unknown> = require('node:module')\n const registerHooks = moduleApi['registerHooks']\n if (typeof registerHooks !== 'function') {\n console.warn(\n '[starter] module.registerHooks unavailable (Node < 22.15) — host-external redirect skipped',\n )\n return\n }\n const anchorURL = pathToFileURL(\n path.join(activeRootDir, 'node_modules', '__camstack_starter_anchor__.js'),\n ).href\n const hooks: Parameters<RegisterHooksFn>[0] = {\n resolve: (specifier, context, nextResolve) => {\n if (isHostExternal(specifier)) {\n try {\n return nextResolve(specifier, { ...context, parentURL: anchorURL })\n } catch {\n // Not in the active root's closure — fall through to default.\n }\n }\n return nextResolve(specifier, context)\n },\n }\n ;(registerHooks as RegisterHooksFn)(hooks)\n}\n\n/** Per-node env-var names the starter writes / reads. */\nexport interface StarterEnvNames {\n /** Boot-mode marker read by the node's update service (`workspace`/`baked`/`data-root`). */\n readonly bootMode: string\n /** Absolute path of the active data-root version dir (data-root boots only). */\n readonly activeRoot: string\n /** Active data-root version (data-root boots only). */\n readonly activeVersion: string\n /** Kill switch: `<killSwitch>=off` forces the baked seed closure. */\n readonly killSwitch: string\n}\n\nexport interface NodeStarterOptions {\n readonly spec: RootPackageSpec\n readonly envNames: StarterEnvNames\n /** The node's resolved data dir (owner-specific env/argv resolution). */\n readonly dataDir: string\n /** Absolute path of the baked seed entry sibling to the starter. */\n readonly seedEntry: string\n /** The starter's own dir (`__dirname`) — anchors workspace detection. */\n readonly starterDir: string\n /** Load an entry file (a plain CJS `require` in the real starters). */\n readonly loadEntry: (entryPath: string) => void\n /** Test seams. Defaults: `process.env`, `Date.now`, running node major. */\n readonly env?: NodeJS.ProcessEnv\n readonly now?: () => number\n readonly nodeMajor?: number\n readonly registerResolver?: (activeRootDir: string) => void\n}\n\n/**\n * Run the shared starter flow. Never throws for boot-plan reasons — every\n * failure path degrades to the baked seed. (A `loadEntry` throw propagates:\n * that is the caller's fatal handler's job.)\n */\nexport function runNodeStarter(options: NodeStarterOptions): void {\n const env = options.env ?? process.env\n const now = options.now ?? Date.now\n const registerResolver = options.registerResolver ?? registerActiveRootResolver\n\n // ── 1. Workspace checkout → plain resolution, dev loop untouched ────────\n const disabled = env[options.envNames.killSwitch] === 'off'\n const workspaceRoot = detectWorkspaceRoot(options.starterDir)\n if (workspaceRoot !== null) {\n console.log(\n `[starter] workspace checkout detected at ${workspaceRoot} — using plain resolution`,\n )\n env[options.envNames.bootMode] = 'workspace'\n options.loadEntry(options.seedEntry)\n return\n }\n if (disabled) {\n console.log(`[starter] ${options.envNames.killSwitch}=off — booting the baked seed closure`)\n env[options.envNames.bootMode] = 'baked'\n options.loadEntry(options.seedEntry)\n return\n }\n\n // ── 2. Boot plan from the durable server-root state ─────────────────────\n const rootDir = serverRootDir(options.dataDir)\n const state = readServerRootState(rootDir)\n const nodeMajor =\n options.nodeMajor ?? Number.parseInt(process.versions.node.split('.')[0] ?? '0', 10)\n const isValidVersion = (version: string): boolean => {\n const reason = validateVersionDir(rootDir, version, nodeMajor, options.spec)\n if (reason !== null) console.warn(`[starter] version ${version} invalid: ${reason}`)\n return reason === null\n }\n\n const plan = planBoot(state, isValidVersion, now)\n if (plan.stateToWrite !== null) {\n try {\n writeServerRootState(rootDir, plan.stateToWrite)\n } catch (err) {\n // A state write failure must not brick boot — fall back to the seed\n // (booting a probation version WITHOUT the durable attempt counter\n // would disarm the crash-loop rollback).\n console.error('[starter] FAILED to persist server-root state — booting baked seed:', err)\n env[options.envNames.bootMode] = 'baked'\n options.loadEntry(options.seedEntry)\n return\n }\n if (\n plan.stateToWrite.rolledBack !== null &&\n (state?.rolledBack ?? null) !== plan.stateToWrite.rolledBack\n ) {\n const rb = plan.stateToWrite.rolledBack\n console.warn(\n `[starter] ROLLED BACK ${rb.fromVersion} -> ${rb.toVersion ?? 'baked seed'}: ${rb.reason}`,\n )\n }\n }\n\n // ── 3. Load the chosen closure ───────────────────────────────────────────\n if (plan.kind === 'data-root') {\n const activeRootDir = versionDir(rootDir, plan.version)\n const entry = rootEntryPath(activeRootDir, options.spec)\n console.log(\n `[starter] booting ${options.spec.packageName}@${plan.version} from ${activeRootDir}` +\n (plan.probation ? ' (probation boot — health-check armed)' : ''),\n )\n env[options.envNames.bootMode] = 'data-root'\n env[options.envNames.activeRoot] = activeRootDir\n env[options.envNames.activeVersion] = plan.version\n registerResolver(activeRootDir)\n options.loadEntry(entry)\n return\n }\n\n console.log(`[starter] booting baked seed closure (${plan.reason})`)\n env[options.envNames.bootMode] = 'baked'\n options.loadEntry(options.seedEntry)\n}\n","/**\n * RootUpdateService — the shared install/update/rollback engine for a node's\n * runtime-updatable ROOT package (`@camstack/server` on the hub,\n * `@camstack/agent` on agents). Phase 2 of the runtime-updatable node\n * packages design: the hub's `ServerUpdateService` and the agent's\n * `AgentUpdateService` are thin adapters over this one class, so the\n * security-review fixes (rollback reentrancy guards, fresh-transient prune\n * protection, corrupt-state visibility) exist in exactly one place.\n *\n * Responsibilities:\n * - stage a new `<pkg>@X` closure into\n * `<dataDir>/server-root/versions/<X>/` (npm install into a same-fs\n * staging dir + atomic rename activation),\n * - keep exactly one previous version (N-1) for rollback,\n * - drive the probation-boot protocol via the durable `state.json`\n * (`pendingBoot` armed here, consumed by the STARTER on the next boot,\n * promoted by `confirmBootHealthy()` once the node reaches ready),\n * - back the `server-management` cap methods on every node type.\n *\n * The starter core (starter-core.ts) owns the OTHER half: choosing the\n * closure at boot, bumping the probation counter, auto-rolling-back a\n * version whose probation boot never confirmed.\n *\n * Spec: docs/superpowers/specs/2026-07-12-runtime-updatable-node-packages-design.md\n */\nimport { execFile } from 'node:child_process'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { promisify } from 'node:util'\nimport type {\n ServerBootMode,\n ServerPackageStatus,\n ServerUpdateActionResult,\n ServerUpdateCheckResult,\n ServerUpdateState,\n} from '@camstack/types'\nimport {\n DEV_UPLOADS_KEEP_COUNT,\n devChannelEpoch,\n devUploadVersionDir,\n devUploadsDir,\n isDevChannelVersion,\n readDevUploadManifest,\n} from './dev-uploads.js'\nimport type { RootPackageSpec } from './root-package-spec.js'\nimport { compareSemver } from './semver-compare.js'\nimport {\n emptyServerRootState,\n readServerRootState,\n rootEntryPath,\n rootPackageDir,\n serverRootDir,\n stateFilePath,\n validateVersionDir,\n versionDir,\n versionsDir,\n writeServerRootState,\n type ServerRootState,\n} from './root-state.js'\nimport { detectWorkspaceRoot } from './workspace-detect.js'\n\nconst execFileAsync = promisify(execFile)\n\n/** npm registry override args — mirrors addon-package.service (module-local there). */\nfunction buildNpmRegistryArgs(registry: string | undefined): readonly string[] {\n if (registry === undefined || registry.length === 0) return []\n return ['--registry', registry, `--@camstack:registry=${registry}`]\n}\n\nexport type NpmExecFn = (\n args: readonly string[],\n opts: { readonly cwd?: string; readonly timeoutMs: number },\n) => Promise<{ stdout: string }>\n\n/**\n * Structural logger surface — satisfied by the hub's `IScopedLogger` AND the\n * agent's console logger, without a runtime dependency on `@camstack/types`.\n */\nexport interface RootUpdateLogger {\n debug(message: string, context?: { readonly meta?: Record<string, unknown> }): void\n info(message: string, context?: { readonly meta?: Record<string, unknown> }): void\n warn(message: string, context?: { readonly meta?: Record<string, unknown> }): void\n error(message: string, context?: { readonly meta?: Record<string, unknown> }): void\n}\n\n/** Per-node env-var names the update service reads (written by the starter). */\nexport interface RootUpdateEnvNames {\n /** `workspace` / `baked` / `data-root` marker set by the starter. */\n readonly bootMode: string\n /** Active data-root version (data-root boots only). */\n readonly activeVersion: string\n /** Directory of the immutable baked seed root package (image-set). */\n readonly seedDir: string\n}\n\nexport interface RootUpdateServiceOptions {\n readonly spec: RootPackageSpec\n readonly envNames: RootUpdateEnvNames\n readonly logger: RootUpdateLogger\n /** Graceful restart seam (hub: AddonPackageService.restartServer; agent: SIGTERM self). */\n readonly restartServer: (requestedBy: string) => void\n /** The node's resolved data dir (adapters own the per-node default). */\n readonly dataDir: string\n /** package.json of the RUNNING root-package copy (adapters own the default). */\n readonly runningPackageJsonPath: string\n /** Anchor for the boot-mode fallback classification (adapter `__dirname`). */\n readonly workspaceProbeDir: string\n /** Overrides for tests. */\n readonly execNpm?: NpmExecFn\n readonly env?: NodeJS.ProcessEnv\n readonly now?: () => number\n}\n\ninterface CheckCache {\n readonly latestVersion: string | null\n readonly checkedAtMs: number\n readonly error: string | null\n}\n\nconst NPM_VIEW_TIMEOUT_MS = 20_000\nconst NPM_INSTALL_TIMEOUT_MS = 15 * 60_000\nconst RESTART_REASON_PREFIX = 'server-update'\n\nfunction readPackageVersion(pkgJsonPath: string): string | null {\n try {\n const raw: unknown = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'))\n if (typeof raw === 'object' && raw !== null) {\n const version = (raw as Record<string, unknown>)['version']\n if (typeof version === 'string') return version\n }\n } catch {\n // missing / malformed — treated as unknown\n }\n return null\n}\n\nexport class RootUpdateService {\n private readonly spec: RootPackageSpec\n private readonly envNames: RootUpdateEnvNames\n private readonly logger: RootUpdateLogger\n private readonly restartServerFn: (requestedBy: string) => void\n private readonly dataDir: string\n private readonly execNpm: NpmExecFn\n private readonly env: NodeJS.ProcessEnv\n private readonly now: () => number\n private readonly runningPackageJsonPath: string\n private readonly workspaceProbeDir: string\n\n private checkCache: CheckCache | null = null\n private inFlight: 'idle' | 'checking' | 'staging' = 'idle'\n\n constructor(options: RootUpdateServiceOptions) {\n this.spec = options.spec\n this.envNames = options.envNames\n this.logger = options.logger\n this.restartServerFn = options.restartServer\n this.dataDir = path.resolve(options.dataDir)\n this.execNpm =\n options.execNpm ??\n (async (args, opts) => {\n const { stdout } = await execFileAsync('npm', [...args], {\n cwd: opts.cwd,\n timeout: opts.timeoutMs,\n })\n return { stdout }\n })\n this.env = options.env ?? process.env\n this.now = options.now ?? Date.now\n this.runningPackageJsonPath = options.runningPackageJsonPath\n this.workspaceProbeDir = options.workspaceProbeDir\n }\n\n // ── Status ────────────────────────────────────────────────────────────\n\n resolveBootMode(): ServerBootMode {\n const raw = this.env[this.envNames.bootMode]\n if (raw === 'workspace' || raw === 'baked' || raw === 'data-root') return raw\n // The starter didn't run (dev tsx entry, `npm start`, or a pre-starter\n // image) — classify by checkout shape.\n return detectWorkspaceRoot(this.workspaceProbeDir) !== null ? 'workspace' : 'baked'\n }\n\n private runningVersion(): string | null {\n return readPackageVersion(this.runningPackageJsonPath)\n }\n\n /** The running root package's identity — feeds registerNode / topology rows. */\n getRunningRootPackage(): { readonly name: string; readonly version: string | null } {\n return { name: this.spec.packageName, version: this.runningVersion() }\n }\n\n private seedVersion(): string | null {\n const seedDir = this.env[this.envNames.seedDir]\n if (seedDir === undefined || seedDir.length === 0) return null\n return readPackageVersion(path.join(seedDir, 'package.json'))\n }\n\n private rootDir(): string {\n return serverRootDir(this.dataDir)\n }\n\n private readState(): ServerRootState {\n return readServerRootState(this.rootDir()) ?? emptyServerRootState()\n }\n\n /**\n * Like `readState`, but distinguishes \"state.json missing\" (normal on a\n * fresh node) from \"state.json present but corrupt\" — in the corrupt case\n * the starter booted the baked seed while installed data-dir versions are\n * silently ignored, which the status surface must make visible.\n */\n private readStateInfo(): { state: ServerRootState; corrupt: boolean } {\n const rootDir = this.rootDir()\n const raw = readServerRootState(rootDir)\n if (raw !== null) return { state: raw, corrupt: false }\n return { state: emptyServerRootState(), corrupt: fs.existsSync(stateFilePath(rootDir)) }\n }\n\n private updateState(state: ServerRootState): ServerUpdateState {\n if (this.inFlight === 'checking') return 'checking'\n if (this.inFlight === 'staging') return 'staging'\n if (state.pendingBoot !== null) {\n // Distinguish \"staged, not yet restarted\" (still on the OLD version)\n // from \"restarted onto the staged version, awaiting its boot-health\n // confirmation\" — the latter is the probation boot: apply/rollback are\n // refused and the node must not be manually restarted (security review\n // #2). We ARE the probation boot when the active version the starter\n // booted equals the pending version.\n const activeVersion = this.env[this.envNames.activeVersion] ?? null\n if (activeVersion !== null && activeVersion === state.pendingBoot.version) {\n return 'awaiting-confirmation'\n }\n return 'pending-restart'\n }\n return 'idle'\n }\n\n async getServerPackageStatus(): Promise<ServerPackageStatus> {\n const { state, corrupt } = this.readStateInfo()\n const runningVersion = this.runningVersion()\n const latestVersion = this.checkCache?.latestVersion ?? null\n const updateAvailable =\n latestVersion !== null &&\n runningVersion !== null &&\n compareSemver(latestVersion, runningVersion) > 0\n return {\n packageName: this.spec.packageName,\n runningVersion,\n nodeRuntimeVersion: process.versions.node,\n activeVersion: this.env[this.envNames.activeVersion] ?? state.currentVersion,\n previousVersion: state.previousVersion,\n seedVersion: this.seedVersion(),\n latestVersion,\n updateAvailable,\n bootMode: this.resolveBootMode(),\n updateState: this.updateState(state),\n pendingVersion: state.pendingBoot?.version ?? null,\n rolledBack: state.rolledBack,\n stateFileCorrupt: corrupt,\n lastCheckedAtMs: this.checkCache?.checkedAtMs ?? null,\n }\n }\n\n // ── Check ─────────────────────────────────────────────────────────────\n\n async checkServerUpdate(): Promise<ServerUpdateCheckResult> {\n const runningVersion = this.runningVersion()\n if (this.inFlight !== 'idle') {\n return {\n packageName: this.spec.packageName,\n runningVersion,\n latestVersion: this.checkCache?.latestVersion ?? null,\n updateAvailable: false,\n checkedAtMs: this.checkCache?.checkedAtMs ?? this.now(),\n error: `busy: ${this.inFlight}`,\n }\n }\n this.inFlight = 'checking'\n try {\n const registry = this.env['CAMSTACK_NPM_REGISTRY']\n const args = [\n 'view',\n `${this.spec.packageName}@latest`,\n 'version',\n ...buildNpmRegistryArgs(registry),\n ]\n const { stdout } = await this.execNpm(args, { timeoutMs: NPM_VIEW_TIMEOUT_MS })\n const latestVersion = stdout.trim().length > 0 ? stdout.trim() : null\n this.checkCache = { latestVersion, checkedAtMs: this.now(), error: null }\n const updateAvailable =\n latestVersion !== null &&\n runningVersion !== null &&\n compareSemver(latestVersion, runningVersion) > 0\n return {\n packageName: this.spec.packageName,\n runningVersion,\n latestVersion,\n updateAvailable,\n checkedAtMs: this.checkCache.checkedAtMs,\n error: null,\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n this.checkCache = {\n latestVersion: this.checkCache?.latestVersion ?? null,\n checkedAtMs: this.now(),\n error: message,\n }\n this.logger.warn('root package update check failed', { meta: { error: message } })\n return {\n packageName: this.spec.packageName,\n runningVersion,\n latestVersion: this.checkCache.latestVersion,\n updateAvailable: false,\n checkedAtMs: this.checkCache.checkedAtMs,\n error: message,\n }\n } finally {\n this.inFlight = 'idle'\n }\n }\n\n // ── Apply ─────────────────────────────────────────────────────────────\n\n async applyServerUpdate(input: { version?: string }): Promise<ServerUpdateActionResult> {\n const bootMode = this.resolveBootMode()\n if (bootMode === 'workspace') {\n return {\n accepted: false,\n targetVersion: input.version ?? null,\n restarting: false,\n message:\n 'Refused: running from a workspace checkout — the dev loop resolves code from the ' +\n 'workspace, a data-root update would never be loaded.',\n }\n }\n if (this.inFlight !== 'idle') {\n return {\n accepted: false,\n targetVersion: input.version ?? null,\n restarting: false,\n message: `Refused: another operation is in flight (${this.inFlight}).`,\n }\n }\n const stateBefore = this.readState()\n if (stateBefore.pendingBoot !== null) {\n return {\n accepted: false,\n targetVersion: input.version ?? null,\n restarting: false,\n message: `Refused: version ${stateBefore.pendingBoot.version} is already staged and awaiting restart.`,\n }\n }\n\n // Resolve the target version (explicit or latest from the registry).\n let target = input.version ?? null\n if (target === null) {\n const check = await this.checkServerUpdate()\n if (check.error !== null || check.latestVersion === null) {\n return {\n accepted: false,\n targetVersion: null,\n restarting: false,\n message: `Refused: could not resolve the latest version (${check.error ?? 'no version returned'}).`,\n }\n }\n target = check.latestVersion\n }\n\n const runningVersion = this.runningVersion()\n if (target === runningVersion) {\n return {\n accepted: false,\n targetVersion: target,\n restarting: false,\n message: `Refused: ${this.spec.packageName}@${target} is already running.`,\n }\n }\n\n // Dev server-deploy channel boundary (phase 1: hub only). A `-dev.<epoch>`\n // version stages EXCLUSIVELY from tarballs previously uploaded to THIS\n // node's `server-root/dev-uploads/<version>/` — it never exists on the\n // npm registry, so a node without the uploads must fail loudly here\n // instead of timing out inside `npm install`.\n if (isDevChannelVersion(target)) {\n const uploadsDir = devUploadVersionDir(this.rootDir(), target)\n if (!fs.existsSync(uploadsDir)) {\n return {\n accepted: false,\n targetVersion: target,\n restarting: false,\n message:\n `Refused: dev-channel version ${target} has no uploaded tarballs on this node ` +\n `(${uploadsDir} is missing). Dev server deploys are hub-only in this phase — ` +\n `upload via \\`camstack deploy-server\\` against this node first.`,\n }\n }\n }\n\n this.inFlight = 'staging'\n try {\n await this.stageAndActivate(target)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n this.logger.error('root package update staging failed', {\n meta: { targetVersion: target, error: message },\n })\n return {\n accepted: false,\n targetVersion: target,\n restarting: false,\n message: `Staging failed: ${message}`,\n }\n } finally {\n this.inFlight = 'idle'\n }\n\n // Arm the probation boot + schedule the graceful restart.\n const state = this.readState()\n writeServerRootState(this.rootDir(), {\n ...state,\n pendingBoot: {\n version: target,\n fromVersion: state.currentVersion,\n requestedAtMs: this.now(),\n bootAttempts: 0,\n },\n rolledBack: null,\n })\n this.logger.info('root package update staged — restarting to apply', {\n meta: { targetVersion: target, fromVersion: state.currentVersion },\n })\n this.restartServerFn(`${RESTART_REASON_PREFIX}: ${this.spec.packageName}@${target}`)\n return {\n accepted: true,\n targetVersion: target,\n restarting: true,\n message: `Staged ${this.spec.packageName}@${target} — restarting to apply (probation boot with auto-rollback).`,\n }\n }\n\n /**\n * npm-install the target closure into a SAME-FS staging dir inside\n * `versions/`, validate it, then atomically rename it into place.\n */\n private async stageAndActivate(target: string): Promise<void> {\n const rootDir = this.rootDir()\n const vDir = versionsDir(rootDir)\n fs.mkdirSync(vDir, { recursive: true })\n const stagingDir = path.join(vDir, `.staging-${target}-${process.pid}-${this.now()}`)\n fs.mkdirSync(stagingDir, { recursive: true })\n\n try {\n // Dev server-deploy channel: when tarballs for `target` were uploaded to\n // `server-root/dev-uploads/<target>/`, stage from THEM instead of the\n // registry — a synthetic package.json depends on the root tgz via\n // `file:` and pins every other uploaded package through `overrides`, so\n // ANY transitive occurrence resolves to the uploaded copy. External deps\n // still come from npmjs / CAMSTACK_NPM_REGISTRY. Everything downstream\n // (entry check, version check, activation, probation) is unchanged.\n const devDir = devUploadVersionDir(rootDir, target)\n const registry = this.env['CAMSTACK_NPM_REGISTRY']\n const installArgs = ['install', '--omit=dev', '--no-audit', '--no-fund', '--loglevel=error']\n if (fs.existsSync(devDir)) {\n fs.writeFileSync(\n path.join(stagingDir, 'package.json'),\n JSON.stringify(this.buildDevUploadsPackageJson(devDir, target), null, 2),\n 'utf-8',\n )\n await this.execNpm([...installArgs, ...buildNpmRegistryArgs(registry)], {\n cwd: stagingDir,\n timeoutMs: NPM_INSTALL_TIMEOUT_MS,\n })\n } else {\n fs.writeFileSync(\n path.join(stagingDir, 'package.json'),\n JSON.stringify({ name: 'camstack-node-root', private: true }, null, 2),\n 'utf-8',\n )\n await this.execNpm(\n [...installArgs, `${this.spec.packageName}@${target}`, ...buildNpmRegistryArgs(registry)],\n { cwd: stagingDir, timeoutMs: NPM_INSTALL_TIMEOUT_MS },\n )\n }\n\n const entry = rootEntryPath(stagingDir, this.spec)\n if (!fs.existsSync(entry)) {\n throw new Error(`staged closure is missing the root entry (${entry})`)\n }\n const stagedVersion = readPackageVersion(\n path.join(rootPackageDir(stagingDir, this.spec), 'package.json'),\n )\n if (stagedVersion !== target) {\n throw new Error(\n `staged closure version mismatch: expected ${target}, got ${stagedVersion ?? 'unknown'}`,\n )\n }\n\n // Activate: evict any existing dir for this version, then atomic rename.\n const dest = versionDir(rootDir, target)\n if (fs.existsSync(dest)) {\n const aside = `${dest}.evicted-${this.now()}`\n await fs.promises.rename(dest, aside)\n await fs.promises.rm(aside, { recursive: true, force: true }).catch(() => undefined)\n }\n await fs.promises.rename(stagingDir, dest)\n } catch (err) {\n await fs.promises.rm(stagingDir, { recursive: true, force: true }).catch(() => undefined)\n throw err\n }\n }\n\n /**\n * Synthetic staging package.json for the dev server-deploy channel:\n * `dependencies` carries the root package as a `file:` ref to its uploaded\n * tgz; `overrides` maps EVERY OTHER uploaded `@camstack/<name>` to its tgz\n * so any transitive occurrence resolves to the uploaded copy, never npm.\n * Fail-closed: a missing/invalid manifest, a manifest for a different\n * version, a missing root package, or a listed-but-absent tgz all throw\n * (surfaced by `applyServerUpdate` as a staging failure).\n */\n private buildDevUploadsPackageJson(\n devDir: string,\n target: string,\n ): {\n readonly name: string\n readonly private: true\n readonly dependencies: Readonly<Record<string, string>>\n readonly overrides?: Readonly<Record<string, string>>\n } {\n const manifest = readDevUploadManifest(devDir)\n if (manifest === null) {\n throw new Error(`dev-uploads manifest missing or invalid in ${devDir}`)\n }\n if (manifest.version !== target) {\n throw new Error(\n `dev-uploads manifest version mismatch: dir is for ${target}, manifest says ${manifest.version}`,\n )\n }\n const rootTgz = manifest.packages[this.spec.packageName]\n if (rootTgz === undefined) {\n throw new Error(\n `dev-uploads for ${target} do not include the root package ${this.spec.packageName}`,\n )\n }\n const fileRef = (filename: string): string => {\n const abs = path.join(devDir, filename)\n if (!fs.existsSync(abs)) {\n throw new Error(`dev-uploads tarball listed in the manifest is missing: ${abs}`)\n }\n return `file:${abs}`\n }\n const overrides: Record<string, string> = {}\n for (const [pkgName, filename] of Object.entries(manifest.packages)) {\n if (pkgName === this.spec.packageName) continue\n overrides[pkgName] = fileRef(filename)\n }\n return {\n name: 'camstack-node-root',\n private: true,\n dependencies: { [this.spec.packageName]: fileRef(rootTgz) },\n ...(Object.keys(overrides).length > 0 ? { overrides } : {}),\n }\n }\n\n // ── Rollback ──────────────────────────────────────────────────────────\n\n async rollbackServerUpdate(): Promise<ServerUpdateActionResult> {\n // Same reentrancy/precondition guards as applyServerUpdate: without them a\n // rollback racing an armed update (10s restart grace) silently overwrites\n // pendingBoot — last write wins with no error to the loser.\n if (this.inFlight !== 'idle') {\n return {\n accepted: false,\n targetVersion: null,\n restarting: false,\n message: `Refused: another operation is in flight (${this.inFlight}).`,\n }\n }\n const state = this.readState()\n if (state.pendingBoot !== null) {\n return {\n accepted: false,\n targetVersion: null,\n restarting: false,\n message: `Refused: version ${state.pendingBoot.version} is already staged and awaiting restart.`,\n }\n }\n if (state.currentVersion === null || state.previousVersion === null) {\n return {\n accepted: false,\n targetVersion: state.previousVersion,\n restarting: false,\n message: 'Refused: no previous version available to roll back to.',\n }\n }\n const nodeMajor = Number.parseInt(process.versions.node.split('.')[0] ?? '0', 10)\n const invalidReason = validateVersionDir(\n this.rootDir(),\n state.previousVersion,\n nodeMajor,\n this.spec,\n )\n if (invalidReason !== null) {\n return {\n accepted: false,\n targetVersion: state.previousVersion,\n restarting: false,\n message: `Refused: previous version ${state.previousVersion} is not loadable (${invalidReason}).`,\n }\n }\n\n writeServerRootState(this.rootDir(), {\n ...state,\n pendingBoot: {\n version: state.previousVersion,\n fromVersion: state.currentVersion,\n requestedAtMs: this.now(),\n bootAttempts: 0,\n },\n })\n this.logger.info('root package rollback requested — restarting', {\n meta: { fromVersion: state.currentVersion, toVersion: state.previousVersion },\n })\n this.restartServerFn(\n `${RESTART_REASON_PREFIX}: rollback to ${this.spec.packageName}@${state.previousVersion}`,\n )\n return {\n accepted: true,\n targetVersion: state.previousVersion,\n restarting: true,\n message: `Rolling back to ${this.spec.packageName}@${state.previousVersion} — restarting.`,\n }\n }\n\n // ── Plain restart ─────────────────────────────────────────────────────\n\n /**\n * Plain process restart — no version change. Backs the `restartServer` cap\n * method. Guards mirror apply/rollback: refuse while a stage is in flight\n * (a concurrent npm install must not be interrupted) or while a version is\n * already staged awaiting restart (a naive bounce would boot the OLD version\n * and orphan the pending one — the operator should apply/rollback instead).\n */\n restartNode(): ServerUpdateActionResult {\n const runningVersion = this.runningVersion()\n if (this.inFlight !== 'idle') {\n return {\n accepted: false,\n targetVersion: runningVersion,\n restarting: false,\n message: `Refused: another operation is in flight (${this.inFlight}).`,\n }\n }\n const state = this.readState()\n if (state.pendingBoot !== null) {\n return {\n accepted: false,\n targetVersion: runningVersion,\n restarting: false,\n message: `Refused: version ${state.pendingBoot.version} is staged and awaiting restart — apply or roll it back instead of a plain restart.`,\n }\n }\n this.logger.info('plain node restart requested', { meta: { runningVersion } })\n this.restartServerFn(`${RESTART_REASON_PREFIX}: manual restart`)\n return {\n accepted: true,\n targetVersion: runningVersion,\n restarting: true,\n message: 'Restarting the node — the supervisor relaunches it on the same version.',\n }\n }\n\n // ── Boot health confirmation ──────────────────────────────────────────\n\n /**\n * Called by the post-boot path once the node is READY (hub: post-boot\n * pipeline; agent: first acked `$hub.registerNode`). When THIS process is\n * the probation boot of a pending version, promote it: `currentVersion` ←\n * pending, `previousVersion` ← the version it replaced (N-1 retained for\n * rollback), clear `pendingBoot` + `rolledBack`, and prune every version\n * dir beyond {current, previous}. No-op otherwise.\n */\n confirmBootHealthy(): { promoted: string | null } {\n const state = readServerRootState(this.rootDir())\n if (state === null || state.pendingBoot === null) return { promoted: null }\n\n const activeVersion = this.env[this.envNames.activeVersion] ?? null\n if (activeVersion !== state.pendingBoot.version) {\n // We are NOT the probation boot of this pending version (e.g. the\n // starter never ran, or it booted a different closure). Leave the\n // marker for the starter's own rollback logic.\n return { promoted: null }\n }\n\n const promoted: ServerRootState = {\n ...state,\n currentVersion: state.pendingBoot.version,\n previousVersion: state.pendingBoot.fromVersion,\n pendingBoot: null,\n rolledBack: null,\n }\n writeServerRootState(this.rootDir(), promoted)\n this.pruneVersions(\n [promoted.currentVersion, promoted.previousVersion].filter(\n (v): v is string => typeof v === 'string',\n ),\n )\n this.sweepDevUploads(DEV_UPLOADS_KEEP_COUNT)\n this.logger.info('root package update confirmed healthy', {\n meta: {\n version: promoted.currentVersion,\n previousVersion: promoted.previousVersion,\n },\n })\n return { promoted: promoted.currentVersion }\n }\n\n /**\n * Remove version dirs under `versions/` not in `keep`.\n *\n * Transient dot-entries (`.staging-*` / `.evicted-*`) are NOT pruned unless\n * older than {@link RootUpdateService.STALE_TRANSIENT_MS}: the API starts\n * serving BEFORE the post-boot `confirmBootHealthy()` runs, so a concurrent\n * `applyServerUpdate` can be mid-npm-install into a fresh `.staging-*` dir\n * when this prune fires — deleting it would corrupt that install. Fresh\n * transient dirs are owned by their in-flight operation (which cleans up on\n * failure); the staleness sweep only collects dirs orphaned by a crash.\n */\n private pruneVersions(keep: readonly string[]): void {\n const vDir = versionsDir(this.rootDir())\n let entries: string[]\n try {\n entries = fs.readdirSync(vDir)\n } catch {\n return\n }\n for (const entry of entries) {\n if (keep.includes(entry)) continue\n const full = path.join(vDir, entry)\n if (entry.startsWith('.')) {\n try {\n const ageMs = this.now() - fs.statSync(full).mtimeMs\n if (ageMs < RootUpdateService.STALE_TRANSIENT_MS) continue\n } catch {\n // stat failed (already gone / unreadable) — skip it\n continue\n }\n }\n try {\n fs.rmSync(full, { recursive: true, force: true })\n this.logger.debug('pruned server-root version dir', { meta: { entry } })\n } catch (err) {\n this.logger.warn('failed to prune server-root version dir', {\n meta: { entry, error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n }\n\n /**\n * Promote-time sweep of `server-root/dev-uploads/`: keep only the\n * `keepCount` most recent entries (ordered by the self-describing\n * `-dev.<epochSeconds>` suffix; non-dev-shaped residue counts as oldest).\n * Uses the same rename-aside graveyard pattern as the addon installer —\n * a rename is a metadata op that succeeds even when a tarball is held\n * open, and residue in the graveyard is re-swept on the next promote.\n */\n private sweepDevUploads(keepCount: number): void {\n const dir = devUploadsDir(this.rootDir())\n let entries: string[]\n try {\n entries = fs.readdirSync(dir)\n } catch {\n return // no dev-uploads dir — nothing to sweep\n }\n const graveyard = path.join(dir, '.sweeping')\n // Best-effort collect residue left behind by an earlier sweep first.\n try {\n for (const residue of fs.readdirSync(graveyard)) {\n try {\n fs.rmSync(path.join(graveyard, residue), { recursive: true, force: true })\n } catch {\n /* still held open — retried on the next promote */\n }\n }\n } catch {\n /* graveyard absent — nothing to collect */\n }\n\n const versions = entries.filter((name) => !name.startsWith('.'))\n const byNewestFirst = [...versions].sort(\n (a, b) => (devChannelEpoch(b) ?? -1) - (devChannelEpoch(a) ?? -1),\n )\n const doomed = byNewestFirst.slice(keepCount)\n if (doomed.length === 0) return\n fs.mkdirSync(graveyard, { recursive: true })\n for (const entry of doomed) {\n const aside = path.join(graveyard, `${entry}-${this.now()}`)\n try {\n fs.renameSync(path.join(dir, entry), aside)\n } catch (err) {\n this.logger.warn('failed to move dev-uploads entry aside for sweep', {\n meta: { entry, error: err instanceof Error ? err.message : String(err) },\n })\n continue\n }\n try {\n fs.rmSync(aside, { recursive: true, force: true })\n this.logger.debug('swept dev-uploads entry', { meta: { entry } })\n } catch {\n /* held open — the aside copy is collected on the next promote */\n }\n }\n }\n\n /** Transient (.staging- / .evicted-) dirs younger than this are never pruned. */\n private static readonly STALE_TRANSIENT_MS = 24 * 60 * 60 * 1000\n}\n","export { loadAgentConfig } from './agent-config'\nexport type { AgentConfig } from './agent-config'\n\nexport { createAgentService } from './agent-service'\nexport type { AgentServiceDeps, LoadedAddonEntry } from './agent-service'\n\nexport { startAgent } from './agent-bootstrap'\nexport { createAgentHttpServer, startAgentHttpServer } from './agent-http'\nexport type { AgentHttpConfig } from './agent-http'\n","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\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 applyBundle: (buf) =>\n applyDeployedBundle({\n addonsDir: deps.addonsDir,\n addonId,\n bundle: buf,\n extract,\n logger: deploySwapLogger,\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","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 })\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 * 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","/**\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":["src_exports","__export","AGENT_ROOT_SPEC","RootUpdateService","serverRootDir","writeRestartIntentMarker","module","__toCommonJS","fs","__toESM","path","path2","fs2","path3","fs3","path4","fs4","path5","fs","os","path","fs","path","import_node_crypto","undicicFetch","resolve","buf","addonDir","cpus","fs","path","fs","path","import_node_crypto","Fastify","import_system","import_types","fs","path","fs","path","import_system","import_system","readConfigFile"]}
|
|
1
|
+
{"version":3,"sources":["../../node-root/src/index.ts","../../node-root/src/dev-uploads.ts","../../node-root/src/root-package-spec.ts","../../node-root/src/root-state.ts","../../node-root/src/semver-compare.ts","../../node-root/src/boot-plan.ts","../../node-root/src/workspace-detect.ts","../../node-root/src/starter-core.ts","../../node-root/src/root-update-service.ts","../src/index.ts","../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-bootstrap.ts","../src/agent-http.ts","../src/derive-hub-url.ts","../src/agent-http-auth.ts","../src/agent-update-service.ts","../src/agent-group-runner.ts","../src/agent-cap-dispatch-service.ts","../src/register-agent-cap-dispatch.ts"],"sourcesContent":["/**\n * @camstack/node-root — the shared, zero-runtime-dep node-root boot core.\n *\n * PRIVATE workspace package: it is BUNDLED at build time into the hub's\n * `dist/server-root/index.js` (tsup) and into the agent's tsup output —\n * never published, never resolved from node_modules at runtime. Both node\n * starters and both update services are thin per-node adapters over these\n * modules, so the boot-plan / probation / rollback semantics can never\n * drift between the hub and the agent.\n *\n * Spec: docs/superpowers/specs/2026-07-12-runtime-updatable-node-packages-design.md\n */\nexport * from './dev-uploads.js'\nexport * from './root-package-spec.js'\nexport * from './root-state.js'\nexport * from './boot-plan.js'\nexport * from './semver-compare.js'\nexport * from './workspace-detect.js'\nexport * from './starter-core.js'\nexport * from './root-update-service.js'\n","/**\n * Dev server-deploy channel — layout + manifest for locally-uploaded root\n * package closures (ZERO-DEP: node:fs/node:path only, same constraint as\n * root-state.ts — the baked starter bundles this module).\n *\n * `camstack deploy-server` packs every workspace package in the server's\n * `@camstack/*` closure at one uniform dev version\n * (`<base>-dev.<epochSeconds>`) and uploads the tarballs to\n * `POST /api/server-upload`. The hub stores them under:\n *\n * <dataDir>/server-root/dev-uploads/<version>/\n * <flattened-pkg-name>.tgz one per uploaded package\n * manifest.json { version, packages: { name → filename } }\n *\n * `RootUpdateService.stageAndActivate` detects the dir and stages from these\n * tarballs (synthetic package.json with `file:` refs + `overrides`) instead\n * of the npm registry. Everything downstream — probation boot, promote,\n * rollback — is version-string driven and unchanged; the `-dev.` suffix is\n * self-describing.\n *\n * Spec: docs/superpowers/specs — dev server-deploy channel (2026-07-13).\n */\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\n\nexport const DEV_UPLOADS_DIRNAME = 'dev-uploads'\nexport const DEV_UPLOAD_MANIFEST_FILE = 'manifest.json'\n\n/** Number of dev-uploads entries retained by the promote-time sweep. */\nexport const DEV_UPLOADS_KEEP_COUNT = 3\n\n/**\n * Dev-channel version shape: `<semver-base>-dev.<epochSeconds>`. The suffix\n * is sortable, self-describing, and can never collide with a published\n * registry version. The upload route and the staging branch BOTH gate on it.\n */\nconst DEV_CHANNEL_VERSION_RE = /-dev\\.\\d+$/\n\nexport function isDevChannelVersion(version: string): boolean {\n return DEV_CHANNEL_VERSION_RE.test(version)\n}\n\n/**\n * The `<epochSeconds>` ordering key of a dev-channel version (or dir name).\n * Returns null for anything that is not dev-channel shaped — the sweep\n * treats those as oldest so malformed residue is collected first.\n */\nexport function devChannelEpoch(version: string): number | null {\n const match = /-dev\\.(\\d+)$/.exec(version)\n if (match === null) return null\n const epoch = Number.parseInt(match[1] ?? '', 10)\n return Number.isNaN(epoch) ? null : epoch\n}\n\n/** `<dataDir>/server-root/dev-uploads` */\nexport function devUploadsDir(rootDir: string): string {\n return path.join(rootDir, DEV_UPLOADS_DIRNAME)\n}\n\n/** `<dataDir>/server-root/dev-uploads/<version>` */\nexport function devUploadVersionDir(rootDir: string, version: string): string {\n return path.join(devUploadsDir(rootDir), version)\n}\n\n// ---------------------------------------------------------------------------\n// Manifest IO\n// ---------------------------------------------------------------------------\n\n/**\n * Written by the hub's `/api/server-upload` route after validating every\n * tarball; read by the staging branch. Maps each uploaded package NAME to\n * its tgz FILENAME inside the version dir — staging never has to re-derive\n * package names from flattened filenames.\n */\nexport interface DevUploadManifest {\n readonly version: string\n readonly packages: Readonly<Record<string, string>>\n}\n\nexport function devUploadManifestPath(versionDirPath: string): string {\n return path.join(versionDirPath, DEV_UPLOAD_MANIFEST_FILE)\n}\n\nfunction isDevUploadManifest(v: unknown): v is DevUploadManifest {\n if (typeof v !== 'object' || v === null) return false\n const m = v as Record<string, unknown>\n if (typeof m['version'] !== 'string') return false\n const packages = m['packages']\n if (typeof packages !== 'object' || packages === null || Array.isArray(packages)) return false\n return Object.values(packages).every((filename) => typeof filename === 'string')\n}\n\n/** Read + validate the manifest. Returns null when missing/corrupt. */\nexport function readDevUploadManifest(versionDirPath: string): DevUploadManifest | null {\n try {\n const raw: unknown = JSON.parse(fs.readFileSync(devUploadManifestPath(versionDirPath), 'utf-8'))\n return isDevUploadManifest(raw) ? raw : null\n } catch {\n return null\n }\n}\n\n/** Write the manifest atomically (tmp sibling + rename). Creates the dir. */\nexport function writeDevUploadManifest(versionDirPath: string, manifest: DevUploadManifest): void {\n fs.mkdirSync(versionDirPath, { recursive: true })\n const target = devUploadManifestPath(versionDirPath)\n const tmp = `${target}.tmp`\n fs.writeFileSync(tmp, JSON.stringify(manifest, null, 2), 'utf-8')\n fs.renameSync(tmp, target)\n}\n","/**\n * Root-package spec — the per-node-type parameters of the shared node-root\n * boot machinery. Everything else (the `server-root/` data-dir layout, the\n * `state.json` schema, the probation/rollback boot plan, the update service)\n * is IDENTICAL between the hub and the agent; only the root package's name\n * and its loadable entry differ.\n *\n * Spec: docs/superpowers/specs/2026-07-12-runtime-updatable-node-packages-design.md\n */\n\nexport interface RootPackageSpec {\n /** The npm name of the node's root package. */\n readonly packageName: string\n /**\n * Path segments of the loadable entry INSIDE the root package dir\n * (`node_modules/<packageName>/<...entryRelPath>`).\n */\n readonly entryRelPath: readonly string[]\n}\n\n/** The hub's root package — the starter loads its launcher. */\nexport const HUB_ROOT_SPEC: RootPackageSpec = {\n packageName: '@camstack/server',\n entryRelPath: ['dist', 'launcher.js'],\n}\n\n/** The agent's root package — the starter loads its CLI entry. */\nexport const AGENT_ROOT_SPEC: RootPackageSpec = {\n packageName: '@camstack/agent',\n entryRelPath: ['dist', 'cli.js'],\n}\n","/**\n * Data-dir server-root layout + state file — ZERO-DEP (node:fs/node:path only).\n *\n * Loaded by each node's baked STARTER before any node_modules resolution is\n * trusted, so this module must never import from `@camstack/*` (runtime) or\n * any npm package. The hub's `ServerUpdateService` and the agent's update\n * service consume the same module — one source of truth for the layout and\n * the state shape. The layout is IDENTICAL on both node types (the dir is\n * named `server-root/` everywhere); only the root package name + entry differ\n * and arrive via {@link RootPackageSpec}.\n *\n * Layout (`<dataDir>/server-root/`):\n * versions/<semver>/ npm-installed closure for one version\n * node_modules/<pkg>/<entry> ← the loadable entry (spec-defined)\n * state.json pointer file (ServerRootState, atomic)\n *\n * A POINTER FILE (state.json) is used instead of `current`/`previous`\n * symlinks: atomic via tmp+rename, works on every fs, and carries the\n * probation/rollback bookkeeping in the same durable record.\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 type { RootPackageSpec } from './root-package-spec.js'\n\nexport const SERVER_ROOT_DIRNAME = 'server-root'\nexport const SERVER_ROOT_STATE_FILE = 'state.json'\n/**\n * Restart-intent marker filename. Written by the update service moments before\n * a graceful self-restart (apply/rollback), then read + consumed by an\n * external process supervisor so it can tell an INTENTIONAL restart-for-update\n * apart from a crash — a crash-backoff policy must never penalise an update\n * restart. Lives in the server-root dir next to `state.json`.\n *\n * (Phase 3 — the Electron shell supervisor reads it; the docker restart policy\n * relaunches unconditionally so the marker is a harmless no-op there.)\n */\nexport const RESTART_INTENT_FILE = '.restart-intent'\n\n// ---------------------------------------------------------------------------\n// State shape\n// ---------------------------------------------------------------------------\n\nexport interface ServerRootPendingBoot {\n /** The freshly-staged version awaiting its probation boot. */\n readonly version: string\n /** The version that was current when the update was requested (null = baked). */\n readonly fromVersion: string | null\n readonly requestedAtMs: number\n /** 0 = not yet booted; >=1 = a probation boot started and never confirmed. */\n readonly bootAttempts: number\n}\n\nexport interface ServerRootRollbackInfo {\n readonly fromVersion: string\n /** null = rolled back all the way to the baked seed. */\n readonly toVersion: string | null\n readonly atMs: number\n readonly reason: string\n}\n\nexport interface ServerRootState {\n readonly schemaVersion: 1\n /** The confirmed-healthy active version (null = boot from the baked seed). */\n readonly currentVersion: string | null\n /** N-1 kept for rollback (its versions/ dir is retained). */\n readonly previousVersion: string | null\n readonly pendingBoot: ServerRootPendingBoot | null\n /** Set when the most recent update attempt was rolled back. */\n readonly rolledBack: ServerRootRollbackInfo | null\n}\n\nexport function emptyServerRootState(): ServerRootState {\n return {\n schemaVersion: 1,\n currentVersion: null,\n previousVersion: null,\n pendingBoot: null,\n rolledBack: null,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Layout helpers\n// ---------------------------------------------------------------------------\n\nexport function serverRootDir(dataDir: string): string {\n return path.join(dataDir, SERVER_ROOT_DIRNAME)\n}\n\nexport function versionsDir(rootDir: string): string {\n return path.join(rootDir, 'versions')\n}\n\nexport function versionDir(rootDir: string, version: string): string {\n return path.join(versionsDir(rootDir), version)\n}\n\n/** `versions/<v>/node_modules/<packageName>` — the installed root package dir. */\nexport function rootPackageDir(versionDirPath: string, spec: RootPackageSpec): string {\n return path.join(versionDirPath, 'node_modules', ...spec.packageName.split('/'))\n}\n\n/** The loadable entry inside the installed root package dir. */\nexport function rootEntryPath(versionDirPath: string, spec: RootPackageSpec): string {\n return path.join(rootPackageDir(versionDirPath, spec), ...spec.entryRelPath)\n}\n\nexport function stateFilePath(rootDir: string): string {\n return path.join(rootDir, SERVER_ROOT_STATE_FILE)\n}\n\n// ---------------------------------------------------------------------------\n// State IO (atomic, corruption-tolerant)\n// ---------------------------------------------------------------------------\n\nfunction isNullableString(v: unknown): v is string | null {\n return v === null || typeof v === 'string'\n}\n\nfunction isPendingBoot(v: unknown): v is ServerRootPendingBoot {\n if (typeof v !== 'object' || v === null) return false\n const p = v as Record<string, unknown>\n return (\n typeof p['version'] === 'string' &&\n isNullableString(p['fromVersion']) &&\n typeof p['requestedAtMs'] === 'number' &&\n typeof p['bootAttempts'] === 'number'\n )\n}\n\nfunction isRollbackInfo(v: unknown): v is ServerRootRollbackInfo {\n if (typeof v !== 'object' || v === null) return false\n const r = v as Record<string, unknown>\n return (\n typeof r['fromVersion'] === 'string' &&\n isNullableString(r['toVersion']) &&\n typeof r['atMs'] === 'number' &&\n typeof r['reason'] === 'string'\n )\n}\n\nexport function isServerRootState(v: unknown): v is ServerRootState {\n if (typeof v !== 'object' || v === null) return false\n const s = v as Record<string, unknown>\n if (s['schemaVersion'] !== 1) return false\n if (!isNullableString(s['currentVersion'])) return false\n if (!isNullableString(s['previousVersion'])) return false\n if (s['pendingBoot'] !== null && !isPendingBoot(s['pendingBoot'])) return false\n if (s['rolledBack'] !== null && !isRollbackInfo(s['rolledBack'])) return false\n return true\n}\n\n/** Read + validate the state file. Returns null when missing/corrupt/mismatched. */\nexport function readServerRootState(rootDir: string): ServerRootState | null {\n try {\n const raw: unknown = JSON.parse(fs.readFileSync(stateFilePath(rootDir), 'utf-8'))\n return isServerRootState(raw) ? raw : null\n } catch {\n return null\n }\n}\n\n/** Write the state file atomically (tmp sibling + rename). Creates rootDir. */\nexport function writeServerRootState(rootDir: string, state: ServerRootState): void {\n fs.mkdirSync(rootDir, { recursive: true })\n const target = stateFilePath(rootDir)\n const tmp = `${target}.tmp`\n fs.writeFileSync(tmp, JSON.stringify(state, null, 2), 'utf-8')\n fs.renameSync(tmp, target)\n}\n\n// ---------------------------------------------------------------------------\n// Restart-intent marker (supervisor: update-restart vs crash discrimination)\n// ---------------------------------------------------------------------------\n\n/**\n * Payload of the restart-intent marker. `requestedAtMs` lets a reader ignore a\n * STALE marker (e.g. one left behind by a hard kill between write and\n * relaunch) via a freshness window instead of mis-classifying a much-later\n * crash as an update restart. `reason` carries the human `requestedBy` string.\n */\nexport interface RestartIntentMarker {\n readonly requestedAtMs: number\n readonly reason: string\n}\n\nexport function restartIntentMarkerPath(rootDir: string): string {\n return path.join(rootDir, RESTART_INTENT_FILE)\n}\n\nfunction isRestartIntentMarker(v: unknown): v is RestartIntentMarker {\n if (typeof v !== 'object' || v === null) return false\n const m = v as Record<string, unknown>\n return typeof m['requestedAtMs'] === 'number' && typeof m['reason'] === 'string'\n}\n\n/** Write the marker atomically (tmp sibling + rename). Creates rootDir. */\nexport function writeRestartIntentMarker(rootDir: string, marker: RestartIntentMarker): void {\n fs.mkdirSync(rootDir, { recursive: true })\n const target = restartIntentMarkerPath(rootDir)\n const tmp = `${target}.tmp`\n fs.writeFileSync(tmp, JSON.stringify(marker, null, 2), 'utf-8')\n fs.renameSync(tmp, target)\n}\n\n/** Read + validate the marker. Returns null when missing/corrupt. */\nexport function readRestartIntentMarker(rootDir: string): RestartIntentMarker | null {\n try {\n const raw: unknown = JSON.parse(fs.readFileSync(restartIntentMarkerPath(rootDir), 'utf-8'))\n return isRestartIntentMarker(raw) ? raw : null\n } catch {\n return null\n }\n}\n\n/** Delete the marker if present (idempotent — a missing file is not an error). */\nexport function clearRestartIntentMarker(rootDir: string): void {\n try {\n fs.rmSync(restartIntentMarkerPath(rootDir), { force: true })\n } catch {\n /* best-effort */\n }\n}\n\n// ---------------------------------------------------------------------------\n// Version-dir validation\n// ---------------------------------------------------------------------------\n\n/**\n * Extract the minimum required Node major from an `engines.node` range like\n * `>=22`, `>= 22.12`, `^24.0.0`, `22.x`. Returns null when unparseable\n * (validation is then permissive — a bogus range must not brick boot).\n */\nexport function minNodeMajorOf(enginesNode: string): number | null {\n const match = /(\\d+)/.exec(enginesNode)\n if (match === null) return null\n const major = Number.parseInt(match[1] ?? '', 10)\n return Number.isNaN(major) ? null : major\n}\n\n/**\n * Validate that `versions/<version>` holds a loadable root-package closure\n * for `spec`. Returns null when valid, else a human-readable reason. Checks:\n * 1. the entry file exists,\n * 2. the package.json parses with the right `name` + `version`,\n * 3. `engines.node` (when parseable) is satisfied by the running runtime —\n * the starter refuses a native-ABI mismatch with a clear error instead\n * of segfaulting (design risk: node bump vs shm-ring prebuilds).\n */\nexport function validateVersionDir(\n rootDir: string,\n version: string,\n nodeMajor: number,\n spec: RootPackageSpec,\n): string | null {\n const vDir = versionDir(rootDir, version)\n const entry = rootEntryPath(vDir, spec)\n if (!fs.existsSync(entry)) {\n return `root entry missing: ${entry}`\n }\n\n const pkgJsonPath = path.join(rootPackageDir(vDir, spec), 'package.json')\n let pkg: Record<string, unknown>\n try {\n const raw: unknown = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'))\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n return `package.json malformed: ${pkgJsonPath}`\n }\n pkg = raw as Record<string, unknown>\n } catch {\n return `package.json unreadable: ${pkgJsonPath}`\n }\n\n if (pkg['name'] !== spec.packageName) {\n return `package name mismatch: expected ${spec.packageName}, got ${String(pkg['name'])}`\n }\n if (pkg['version'] !== version) {\n return `package version mismatch: dir says ${version}, package.json says ${String(pkg['version'])}`\n }\n\n const engines = pkg['engines']\n if (typeof engines === 'object' && engines !== null) {\n const enginesNode = (engines as Record<string, unknown>)['node']\n if (typeof enginesNode === 'string') {\n const minMajor = minNodeMajorOf(enginesNode)\n if (minMajor !== null && nodeMajor < minMajor) {\n return `engines.node \"${enginesNode}\" requires Node >= ${minMajor}, runtime is ${nodeMajor}`\n }\n }\n }\n\n return null\n}\n","/**\n * Minimal semver comparison — zero-dep (used by the starter, which must never\n * import from node_modules). Handles `MAJOR.MINOR.PATCH[-prerelease]`:\n * numeric segment compare, missing segments = 0, prerelease < its release,\n * prereleases of the same base compare lexically. Malformed segments compare\n * as 0 (best effort — never throws).\n */\n\ntype SemverParts = {\n readonly nums: readonly number[]\n readonly prerelease: string | null\n}\n\nfunction parse(version: string): SemverParts {\n const dashIdx = version.indexOf('-')\n const base = dashIdx === -1 ? version : version.slice(0, dashIdx)\n const prerelease = dashIdx === -1 ? null : version.slice(dashIdx + 1)\n const nums = base.split('.').map((seg) => {\n const n = Number.parseInt(seg, 10)\n return Number.isNaN(n) ? 0 : n\n })\n return { nums, prerelease }\n}\n\n/** Returns -1 when a < b, 0 when equal, 1 when a > b. */\nexport function compareSemver(a: string, b: string): -1 | 0 | 1 {\n const pa = parse(a)\n const pb = parse(b)\n const len = Math.max(pa.nums.length, pb.nums.length)\n for (let i = 0; i < len; i++) {\n const na = pa.nums[i] ?? 0\n const nb = pb.nums[i] ?? 0\n if (na < nb) return -1\n if (na > nb) return 1\n }\n if (pa.prerelease === null && pb.prerelease === null) return 0\n if (pa.prerelease === null) return 1\n if (pb.prerelease === null) return -1\n if (pa.prerelease < pb.prerelease) return -1\n if (pa.prerelease > pb.prerelease) return 1\n return 0\n}\n","/**\n * Pure boot-plan decision for the server-root starter — ZERO-DEP.\n *\n * Given the durable `ServerRootState` and a validity predicate for staged\n * version dirs, decide WHICH closure this boot should load and what the\n * state file must be updated to. All filesystem access is behind the\n * injected `isValidVersion` callback so the decision itself is unit-testable.\n *\n * Probation / rollback protocol (mirrors `launcher-framework-swap.ts`):\n * - `pendingBoot.bootAttempts === 0` → this is the probation boot: bump the\n * counter durably BEFORE loading the new version. The hub clears the\n * pending record (promoting the version to `currentVersion`) only once it\n * reaches ready (`ServerUpdateService.confirmBootHealthy`).\n * - `pendingBoot.bootAttempts >= 1` → the previous probation boot died\n * before confirming: auto-rollback to `currentVersion` (or baked) and\n * record `rolledBack` so the status cap can surface it.\n */\nimport type { ServerRootState } from './root-state.js'\nimport { compareSemver } from './semver-compare.js'\n\nexport type BootPlan =\n | {\n readonly kind: 'baked'\n readonly reason: string\n readonly stateToWrite: ServerRootState | null\n }\n | {\n readonly kind: 'data-root'\n readonly version: string\n /** True when this boot must be confirmed healthy (freshly-staged version). */\n readonly probation: boolean\n readonly stateToWrite: ServerRootState | null\n }\n\ntype IsValidVersion = (version: string) => boolean\ntype NowFn = () => number\n\n/**\n * Decide the boot target. Never throws; never mutates `state`.\n * `stateToWrite` is non-null whenever the durable state must change\n * (probation counter bump, rollback, invalid-version eviction).\n *\n * `seedVersion` is the version baked into THIS image's seed closure (null when\n * unknown — adoption is then disabled, preserving the historical behaviour).\n * When a release image ships a strictly-newer seed than the confirmed active\n * data-root version, we ADOPT the baked seed: booting baked runs the new\n * image's code, so a stale `applyServerUpdate` data-root can no longer shadow\n * a newer image (the \"new caps 404 after a release swap\" bug). Adoption is\n * gated on `!changed` so it never fires in the same boot as a rollback /\n * eviction, and the pending/probation branch returns earlier — probation and\n * rollback semantics are untouched.\n */\nexport function planBoot(\n state: ServerRootState | null,\n isValidVersion: IsValidVersion,\n now: NowFn,\n seedVersion: string | null = null,\n): BootPlan {\n if (state === null) {\n return { kind: 'baked', reason: 'no server-root state', stateToWrite: null }\n }\n\n let next: ServerRootState = state\n let changed = false\n\n // ── Pending (freshly-staged) version ──────────────────────────────────\n if (next.pendingBoot !== null) {\n const pending = next.pendingBoot\n if (pending.bootAttempts >= 1) {\n // Previous probation boot never confirmed healthy — roll back.\n next = {\n ...next,\n pendingBoot: null,\n rolledBack: {\n fromVersion: pending.version,\n toVersion: next.currentVersion,\n atMs: now(),\n reason: 'probation boot did not reach ready — rolled back',\n },\n }\n changed = true\n } else if (!isValidVersion(pending.version)) {\n next = {\n ...next,\n pendingBoot: null,\n rolledBack: {\n fromVersion: pending.version,\n toVersion: next.currentVersion,\n atMs: now(),\n reason: 'staged version failed validation',\n },\n }\n changed = true\n } else {\n // Probation boot: bump the attempt counter durably, keep currentVersion\n // untouched until the hub confirms the boot healthy.\n return {\n kind: 'data-root',\n version: pending.version,\n probation: true,\n stateToWrite: {\n ...next,\n pendingBoot: { ...pending, bootAttempts: pending.bootAttempts + 1 },\n },\n }\n }\n }\n\n // ── Confirmed current version ──────────────────────────────────────────\n if (next.currentVersion !== null) {\n if (isValidVersion(next.currentVersion)) {\n // Baked-seed adoption: a release image whose seed is strictly newer than\n // the confirmed active version wins — otherwise a stale applyServerUpdate\n // data-root shadows the newer image's code (new caps 404 after a swap).\n // Gated on `!changed` so it never pre-empts an in-flight rollback.\n if (!changed && seedVersion !== null && compareSemver(seedVersion, next.currentVersion) > 0) {\n return {\n kind: 'baked',\n reason: `baked seed ${seedVersion} is newer than active data-root ${next.currentVersion} — adopting the image seed`,\n stateToWrite: {\n ...next,\n currentVersion: null,\n previousVersion: null,\n pendingBoot: null,\n rolledBack: null,\n },\n }\n }\n return {\n kind: 'data-root',\n version: next.currentVersion,\n probation: false,\n stateToWrite: changed ? next : null,\n }\n }\n\n // Current version dir is broken — try N-1, else baked.\n const broken = next.currentVersion\n if (next.previousVersion !== null && isValidVersion(next.previousVersion)) {\n const fallback = next.previousVersion\n return {\n kind: 'data-root',\n version: fallback,\n probation: false,\n stateToWrite: {\n ...next,\n currentVersion: fallback,\n previousVersion: null,\n rolledBack: {\n fromVersion: broken,\n toVersion: fallback,\n atMs: now(),\n reason: 'active version failed validation',\n },\n },\n }\n }\n\n return {\n kind: 'baked',\n reason: `active version ${broken} failed validation and no valid previous version exists`,\n stateToWrite: {\n ...next,\n currentVersion: null,\n rolledBack: {\n fromVersion: broken,\n toVersion: null,\n atMs: now(),\n reason: 'active version failed validation',\n },\n },\n }\n }\n\n return {\n kind: 'baked',\n reason: 'no active data-root version',\n stateToWrite: changed ? next : null,\n }\n}\n","/**\n * Workspace-checkout detection for the starter — ZERO-DEP.\n *\n * The starter must defer to plain resolution in a dev checkout (`npm run\n * dev:full` / `npm start` from the monorepo): the data-dir server root is a\n * production mechanism and must never shadow freshly-built workspace code.\n * Detection = any ancestor package.json declaring `workspaces` (the monorepo\n * root has one; the baked image run-dir's `npm init -y` package.json and the\n * data-dir version closures never do).\n */\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\n\n/**\n * Walk up from `fromDir` looking for a package.json with a `workspaces`\n * field. Returns the directory containing it, or null when none exists.\n */\nexport function detectWorkspaceRoot(fromDir: string): string | null {\n let dir = path.resolve(fromDir)\n for (;;) {\n const pkgPath = path.join(dir, 'package.json')\n try {\n const raw: unknown = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))\n if (\n typeof raw === 'object' &&\n raw !== null &&\n (raw as Record<string, unknown>)['workspaces'] !== undefined\n ) {\n return dir\n }\n } catch {\n // missing / unreadable package.json — keep walking\n }\n const parent = path.dirname(dir)\n if (parent === dir) return null\n dir = parent\n }\n}\n","/**\n * Shared STARTER core — the boot flow both node starters (hub + agent) run.\n * ZERO-DEP: bundled into each starter at build time; must never resolve\n * anything from node_modules at runtime.\n *\n * Boot sequence (identical on both node types; per-node names arrive via\n * {@link NodeStarterOptions}):\n * 1. Workspace checkout? → defer to plain resolution (load the sibling\n * seed entry; the dev loop never runs the built starter at all).\n * 2. Read `<dataDir>/server-root/state.json`, run the pure boot plan\n * (probation counter / auto-rollback / validity fallback) and persist\n * any state change BEFORE loading code.\n * 3. Data-root chosen → register in-process resolver hooks so the node's\n * MAIN process resolves the host-external packages (@camstack/system, …)\n * from the ACTIVE root's node_modules, then load that root's entry.\n * 4. Otherwise → load the baked seed entry next to the starter.\n *\n * Console usage is intentional: the starter runs before any logging exists.\n * Every line is prefixed `[starter]`.\n */\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { planBoot } from './boot-plan.js'\nimport type { RootPackageSpec } from './root-package-spec.js'\nimport {\n readServerRootState,\n rootEntryPath,\n serverRootDir,\n validateVersionDir,\n versionDir,\n writeServerRootState,\n} from './root-state.js'\nimport { detectWorkspaceRoot } from './workspace-detect.js'\n\n/**\n * Mirror of the host-external list in\n * `packages/system/src/kernel/moleculer/framework-resolver-hook.mjs` — kept\n * inline because the starter must stay zero-dep. Only these specifiers are\n * redirected to the active root; everything else resolves normally.\n */\nexport const HOST_EXTERNAL_SPECIFIERS: readonly string[] = [\n '@camstack/system',\n '@camstack/shm-ring',\n '@camstack/types',\n '@camstack/sdk',\n 'zod',\n '@trpc/server',\n '@trpc/client',\n 'sharp',\n 'node-av',\n]\n\nexport function isHostExternal(specifier: string): boolean {\n return HOST_EXTERNAL_SPECIFIERS.some(\n (name) => specifier === name || specifier.startsWith(`${name}/`),\n )\n}\n\ntype ResolveContext = { readonly parentURL?: string }\ntype NextResolve = (specifier: string, context?: ResolveContext) => unknown\ntype RegisterHooksFn = (hooks: {\n resolve: (specifier: string, context: ResolveContext, nextResolve: NextResolve) => unknown\n}) => void\n\n/**\n * Register in-process resolver hooks (Node >= 22.15 `module.registerHooks`,\n * covers BOTH require and import) that anchor host-external resolution inside\n * the active root's node_modules. Failure-tolerant: if the anchored resolve\n * fails (package absent from the closure, e.g. sharp/node-av), fall back to\n * the default walk (NODE_PATH /data/framework still covers those).\n */\nexport function registerActiveRootResolver(activeRootDir: string): void {\n // Documented type boundary: `module.registerHooks` is not yet in the\n // bundled @types/node — feature-detect it off the untyped module surface.\n const moduleApi: Record<string, unknown> = require('node:module')\n const registerHooks = moduleApi['registerHooks']\n if (typeof registerHooks !== 'function') {\n console.warn(\n '[starter] module.registerHooks unavailable (Node < 22.15) — host-external redirect skipped',\n )\n return\n }\n const anchorURL = pathToFileURL(\n path.join(activeRootDir, 'node_modules', '__camstack_starter_anchor__.js'),\n ).href\n const hooks: Parameters<RegisterHooksFn>[0] = {\n resolve: (specifier, context, nextResolve) => {\n if (isHostExternal(specifier)) {\n try {\n return nextResolve(specifier, { ...context, parentURL: anchorURL })\n } catch {\n // Not in the active root's closure — fall through to default.\n }\n }\n return nextResolve(specifier, context)\n },\n }\n ;(registerHooks as RegisterHooksFn)(hooks)\n}\n\n/** Per-node env-var names the starter writes / reads. */\nexport interface StarterEnvNames {\n /** Boot-mode marker read by the node's update service (`workspace`/`baked`/`data-root`). */\n readonly bootMode: string\n /** Absolute path of the active data-root version dir (data-root boots only). */\n readonly activeRoot: string\n /** Active data-root version (data-root boots only). */\n readonly activeVersion: string\n /** Kill switch: `<killSwitch>=off` forces the baked seed closure. */\n readonly killSwitch: string\n}\n\nexport interface NodeStarterOptions {\n readonly spec: RootPackageSpec\n readonly envNames: StarterEnvNames\n /** The node's resolved data dir (owner-specific env/argv resolution). */\n readonly dataDir: string\n /** Absolute path of the baked seed entry sibling to the starter. */\n readonly seedEntry: string\n /** The starter's own dir (`__dirname`) — anchors workspace detection. */\n readonly starterDir: string\n /** Load an entry file (a plain CJS `require` in the real starters). */\n readonly loadEntry: (entryPath: string) => void\n /** Test seams. Defaults: `process.env`, `Date.now`, running node major. */\n readonly env?: NodeJS.ProcessEnv\n readonly now?: () => number\n readonly nodeMajor?: number\n readonly registerResolver?: (activeRootDir: string) => void\n}\n\n/**\n * Read the version baked into the seed closure from its `package.json` (sibling\n * to the seed package dir: `<seedEntry>/../../package.json`). Zero-dep, never\n * throws — any failure returns null, which disables baked-seed adoption in\n * {@link planBoot} and preserves the historical \"boot the data-root version\n * unconditionally\" behaviour.\n */\nexport function readSeedVersion(seedEntry: string): string | null {\n try {\n const pkgJsonPath = path.join(path.dirname(seedEntry), '..', 'package.json')\n const raw: unknown = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'))\n if (typeof raw === 'object' && raw !== null) {\n const version = (raw as Record<string, unknown>)['version']\n if (typeof version === 'string') return version\n }\n return null\n } catch {\n return null\n }\n}\n\n/**\n * Run the shared starter flow. Never throws for boot-plan reasons — every\n * failure path degrades to the baked seed. (A `loadEntry` throw propagates:\n * that is the caller's fatal handler's job.)\n */\nexport function runNodeStarter(options: NodeStarterOptions): void {\n const env = options.env ?? process.env\n const now = options.now ?? Date.now\n const registerResolver = options.registerResolver ?? registerActiveRootResolver\n\n // ── 1. Workspace checkout → plain resolution, dev loop untouched ────────\n const disabled = env[options.envNames.killSwitch] === 'off'\n const workspaceRoot = detectWorkspaceRoot(options.starterDir)\n if (workspaceRoot !== null) {\n console.log(\n `[starter] workspace checkout detected at ${workspaceRoot} — using plain resolution`,\n )\n env[options.envNames.bootMode] = 'workspace'\n options.loadEntry(options.seedEntry)\n return\n }\n if (disabled) {\n console.log(`[starter] ${options.envNames.killSwitch}=off — booting the baked seed closure`)\n env[options.envNames.bootMode] = 'baked'\n options.loadEntry(options.seedEntry)\n return\n }\n\n // ── 2. Boot plan from the durable server-root state ─────────────────────\n const rootDir = serverRootDir(options.dataDir)\n const state = readServerRootState(rootDir)\n const nodeMajor =\n options.nodeMajor ?? Number.parseInt(process.versions.node.split('.')[0] ?? '0', 10)\n const isValidVersion = (version: string): boolean => {\n const reason = validateVersionDir(rootDir, version, nodeMajor, options.spec)\n if (reason !== null) console.warn(`[starter] version ${version} invalid: ${reason}`)\n return reason === null\n }\n\n const seedVersion = readSeedVersion(options.seedEntry)\n const plan = planBoot(state, isValidVersion, now, seedVersion)\n if (plan.stateToWrite !== null) {\n try {\n writeServerRootState(rootDir, plan.stateToWrite)\n } catch (err) {\n // A state write failure must not brick boot — fall back to the seed\n // (booting a probation version WITHOUT the durable attempt counter\n // would disarm the crash-loop rollback).\n console.error('[starter] FAILED to persist server-root state — booting baked seed:', err)\n env[options.envNames.bootMode] = 'baked'\n options.loadEntry(options.seedEntry)\n return\n }\n if (\n plan.stateToWrite.rolledBack !== null &&\n (state?.rolledBack ?? null) !== plan.stateToWrite.rolledBack\n ) {\n const rb = plan.stateToWrite.rolledBack\n console.warn(\n `[starter] ROLLED BACK ${rb.fromVersion} -> ${rb.toVersion ?? 'baked seed'}: ${rb.reason}`,\n )\n }\n }\n\n // ── 3. Load the chosen closure ───────────────────────────────────────────\n if (plan.kind === 'data-root') {\n const activeRootDir = versionDir(rootDir, plan.version)\n const entry = rootEntryPath(activeRootDir, options.spec)\n console.log(\n `[starter] booting ${options.spec.packageName}@${plan.version} from ${activeRootDir}` +\n (plan.probation ? ' (probation boot — health-check armed)' : ''),\n )\n env[options.envNames.bootMode] = 'data-root'\n env[options.envNames.activeRoot] = activeRootDir\n env[options.envNames.activeVersion] = plan.version\n registerResolver(activeRootDir)\n options.loadEntry(entry)\n return\n }\n\n console.log(`[starter] booting baked seed closure (${plan.reason})`)\n env[options.envNames.bootMode] = 'baked'\n options.loadEntry(options.seedEntry)\n}\n","/**\n * RootUpdateService — the shared install/update/rollback engine for a node's\n * runtime-updatable ROOT package (`@camstack/server` on the hub,\n * `@camstack/agent` on agents). Phase 2 of the runtime-updatable node\n * packages design: the hub's `ServerUpdateService` and the agent's\n * `AgentUpdateService` are thin adapters over this one class, so the\n * security-review fixes (rollback reentrancy guards, fresh-transient prune\n * protection, corrupt-state visibility) exist in exactly one place.\n *\n * Responsibilities:\n * - stage a new `<pkg>@X` closure into\n * `<dataDir>/server-root/versions/<X>/` (npm install into a same-fs\n * staging dir + atomic rename activation),\n * - keep exactly one previous version (N-1) for rollback,\n * - drive the probation-boot protocol via the durable `state.json`\n * (`pendingBoot` armed here, consumed by the STARTER on the next boot,\n * promoted by `confirmBootHealthy()` once the node reaches ready),\n * - back the `server-management` cap methods on every node type.\n *\n * The starter core (starter-core.ts) owns the OTHER half: choosing the\n * closure at boot, bumping the probation counter, auto-rolling-back a\n * version whose probation boot never confirmed.\n *\n * Spec: docs/superpowers/specs/2026-07-12-runtime-updatable-node-packages-design.md\n */\nimport { execFile } from 'node:child_process'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { promisify } from 'node:util'\nimport type {\n ServerBootMode,\n ServerPackageStatus,\n ServerUpdateActionResult,\n ServerUpdateCheckResult,\n ServerUpdateState,\n} from '@camstack/types'\nimport {\n DEV_UPLOADS_KEEP_COUNT,\n devChannelEpoch,\n devUploadVersionDir,\n devUploadsDir,\n isDevChannelVersion,\n readDevUploadManifest,\n} from './dev-uploads.js'\nimport type { RootPackageSpec } from './root-package-spec.js'\nimport { compareSemver } from './semver-compare.js'\nimport {\n emptyServerRootState,\n readServerRootState,\n rootEntryPath,\n rootPackageDir,\n serverRootDir,\n stateFilePath,\n validateVersionDir,\n versionDir,\n versionsDir,\n writeServerRootState,\n type ServerRootState,\n} from './root-state.js'\nimport { detectWorkspaceRoot } from './workspace-detect.js'\n\nconst execFileAsync = promisify(execFile)\n\n/** npm registry override args — mirrors addon-package.service (module-local there). */\nfunction buildNpmRegistryArgs(registry: string | undefined): readonly string[] {\n if (registry === undefined || registry.length === 0) return []\n return ['--registry', registry, `--@camstack:registry=${registry}`]\n}\n\nexport type NpmExecFn = (\n args: readonly string[],\n opts: { readonly cwd?: string; readonly timeoutMs: number },\n) => Promise<{ stdout: string }>\n\n/**\n * Structural logger surface — satisfied by the hub's `IScopedLogger` AND the\n * agent's console logger, without a runtime dependency on `@camstack/types`.\n */\nexport interface RootUpdateLogger {\n debug(message: string, context?: { readonly meta?: Record<string, unknown> }): void\n info(message: string, context?: { readonly meta?: Record<string, unknown> }): void\n warn(message: string, context?: { readonly meta?: Record<string, unknown> }): void\n error(message: string, context?: { readonly meta?: Record<string, unknown> }): void\n}\n\n/** Per-node env-var names the update service reads (written by the starter). */\nexport interface RootUpdateEnvNames {\n /** `workspace` / `baked` / `data-root` marker set by the starter. */\n readonly bootMode: string\n /** Active data-root version (data-root boots only). */\n readonly activeVersion: string\n /** Directory of the immutable baked seed root package (image-set). */\n readonly seedDir: string\n}\n\nexport interface RootUpdateServiceOptions {\n readonly spec: RootPackageSpec\n readonly envNames: RootUpdateEnvNames\n readonly logger: RootUpdateLogger\n /** Graceful restart seam (hub: AddonPackageService.restartServer; agent: SIGTERM self). */\n readonly restartServer: (requestedBy: string) => void\n /** The node's resolved data dir (adapters own the per-node default). */\n readonly dataDir: string\n /** package.json of the RUNNING root-package copy (adapters own the default). */\n readonly runningPackageJsonPath: string\n /** Anchor for the boot-mode fallback classification (adapter `__dirname`). */\n readonly workspaceProbeDir: string\n /** Overrides for tests. */\n readonly execNpm?: NpmExecFn\n readonly env?: NodeJS.ProcessEnv\n readonly now?: () => number\n}\n\ninterface CheckCache {\n readonly latestVersion: string | null\n readonly checkedAtMs: number\n readonly error: string | null\n}\n\nconst NPM_VIEW_TIMEOUT_MS = 20_000\nconst NPM_INSTALL_TIMEOUT_MS = 15 * 60_000\nconst RESTART_REASON_PREFIX = 'server-update'\n\nfunction readPackageVersion(pkgJsonPath: string): string | null {\n try {\n const raw: unknown = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'))\n if (typeof raw === 'object' && raw !== null) {\n const version = (raw as Record<string, unknown>)['version']\n if (typeof version === 'string') return version\n }\n } catch {\n // missing / malformed — treated as unknown\n }\n return null\n}\n\nexport class RootUpdateService {\n private readonly spec: RootPackageSpec\n private readonly envNames: RootUpdateEnvNames\n private readonly logger: RootUpdateLogger\n private readonly restartServerFn: (requestedBy: string) => void\n private readonly dataDir: string\n private readonly execNpm: NpmExecFn\n private readonly env: NodeJS.ProcessEnv\n private readonly now: () => number\n private readonly runningPackageJsonPath: string\n private readonly workspaceProbeDir: string\n\n private checkCache: CheckCache | null = null\n private inFlight: 'idle' | 'checking' | 'staging' = 'idle'\n\n constructor(options: RootUpdateServiceOptions) {\n this.spec = options.spec\n this.envNames = options.envNames\n this.logger = options.logger\n this.restartServerFn = options.restartServer\n this.dataDir = path.resolve(options.dataDir)\n this.execNpm =\n options.execNpm ??\n (async (args, opts) => {\n const { stdout } = await execFileAsync('npm', [...args], {\n cwd: opts.cwd,\n timeout: opts.timeoutMs,\n })\n return { stdout }\n })\n this.env = options.env ?? process.env\n this.now = options.now ?? Date.now\n this.runningPackageJsonPath = options.runningPackageJsonPath\n this.workspaceProbeDir = options.workspaceProbeDir\n }\n\n // ── Status ────────────────────────────────────────────────────────────\n\n resolveBootMode(): ServerBootMode {\n const raw = this.env[this.envNames.bootMode]\n if (raw === 'workspace' || raw === 'baked' || raw === 'data-root') return raw\n // The starter didn't run (dev tsx entry, `npm start`, or a pre-starter\n // image) — classify by checkout shape.\n return detectWorkspaceRoot(this.workspaceProbeDir) !== null ? 'workspace' : 'baked'\n }\n\n private runningVersion(): string | null {\n return readPackageVersion(this.runningPackageJsonPath)\n }\n\n /** The running root package's identity — feeds registerNode / topology rows. */\n getRunningRootPackage(): { readonly name: string; readonly version: string | null } {\n return { name: this.spec.packageName, version: this.runningVersion() }\n }\n\n private seedVersion(): string | null {\n const seedDir = this.env[this.envNames.seedDir]\n if (seedDir === undefined || seedDir.length === 0) return null\n return readPackageVersion(path.join(seedDir, 'package.json'))\n }\n\n private rootDir(): string {\n return serverRootDir(this.dataDir)\n }\n\n private readState(): ServerRootState {\n return readServerRootState(this.rootDir()) ?? emptyServerRootState()\n }\n\n /**\n * Like `readState`, but distinguishes \"state.json missing\" (normal on a\n * fresh node) from \"state.json present but corrupt\" — in the corrupt case\n * the starter booted the baked seed while installed data-dir versions are\n * silently ignored, which the status surface must make visible.\n */\n private readStateInfo(): { state: ServerRootState; corrupt: boolean } {\n const rootDir = this.rootDir()\n const raw = readServerRootState(rootDir)\n if (raw !== null) return { state: raw, corrupt: false }\n return { state: emptyServerRootState(), corrupt: fs.existsSync(stateFilePath(rootDir)) }\n }\n\n private updateState(state: ServerRootState): ServerUpdateState {\n if (this.inFlight === 'checking') return 'checking'\n if (this.inFlight === 'staging') return 'staging'\n if (state.pendingBoot !== null) {\n // Distinguish \"staged, not yet restarted\" (still on the OLD version)\n // from \"restarted onto the staged version, awaiting its boot-health\n // confirmation\" — the latter is the probation boot: apply/rollback are\n // refused and the node must not be manually restarted (security review\n // #2). We ARE the probation boot when the active version the starter\n // booted equals the pending version.\n const activeVersion = this.env[this.envNames.activeVersion] ?? null\n if (activeVersion !== null && activeVersion === state.pendingBoot.version) {\n return 'awaiting-confirmation'\n }\n return 'pending-restart'\n }\n return 'idle'\n }\n\n async getServerPackageStatus(): Promise<ServerPackageStatus> {\n const { state, corrupt } = this.readStateInfo()\n const runningVersion = this.runningVersion()\n const latestVersion = this.checkCache?.latestVersion ?? null\n const updateAvailable =\n latestVersion !== null &&\n runningVersion !== null &&\n compareSemver(latestVersion, runningVersion) > 0\n return {\n packageName: this.spec.packageName,\n runningVersion,\n nodeRuntimeVersion: process.versions.node,\n activeVersion: this.env[this.envNames.activeVersion] ?? state.currentVersion,\n previousVersion: state.previousVersion,\n seedVersion: this.seedVersion(),\n latestVersion,\n updateAvailable,\n bootMode: this.resolveBootMode(),\n updateState: this.updateState(state),\n pendingVersion: state.pendingBoot?.version ?? null,\n rolledBack: state.rolledBack,\n stateFileCorrupt: corrupt,\n lastCheckedAtMs: this.checkCache?.checkedAtMs ?? null,\n }\n }\n\n // ── Check ─────────────────────────────────────────────────────────────\n\n async checkServerUpdate(): Promise<ServerUpdateCheckResult> {\n const runningVersion = this.runningVersion()\n if (this.inFlight !== 'idle') {\n return {\n packageName: this.spec.packageName,\n runningVersion,\n latestVersion: this.checkCache?.latestVersion ?? null,\n updateAvailable: false,\n checkedAtMs: this.checkCache?.checkedAtMs ?? this.now(),\n error: `busy: ${this.inFlight}`,\n }\n }\n this.inFlight = 'checking'\n try {\n const registry = this.env['CAMSTACK_NPM_REGISTRY']\n const args = [\n 'view',\n `${this.spec.packageName}@latest`,\n 'version',\n ...buildNpmRegistryArgs(registry),\n ]\n const { stdout } = await this.execNpm(args, { timeoutMs: NPM_VIEW_TIMEOUT_MS })\n const latestVersion = stdout.trim().length > 0 ? stdout.trim() : null\n this.checkCache = { latestVersion, checkedAtMs: this.now(), error: null }\n const updateAvailable =\n latestVersion !== null &&\n runningVersion !== null &&\n compareSemver(latestVersion, runningVersion) > 0\n return {\n packageName: this.spec.packageName,\n runningVersion,\n latestVersion,\n updateAvailable,\n checkedAtMs: this.checkCache.checkedAtMs,\n error: null,\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n this.checkCache = {\n latestVersion: this.checkCache?.latestVersion ?? null,\n checkedAtMs: this.now(),\n error: message,\n }\n this.logger.warn('root package update check failed', { meta: { error: message } })\n return {\n packageName: this.spec.packageName,\n runningVersion,\n latestVersion: this.checkCache.latestVersion,\n updateAvailable: false,\n checkedAtMs: this.checkCache.checkedAtMs,\n error: message,\n }\n } finally {\n this.inFlight = 'idle'\n }\n }\n\n // ── Apply ─────────────────────────────────────────────────────────────\n\n async applyServerUpdate(input: { version?: string }): Promise<ServerUpdateActionResult> {\n const bootMode = this.resolveBootMode()\n if (bootMode === 'workspace') {\n return {\n accepted: false,\n targetVersion: input.version ?? null,\n restarting: false,\n message:\n 'Refused: running from a workspace checkout — the dev loop resolves code from the ' +\n 'workspace, a data-root update would never be loaded.',\n }\n }\n if (this.inFlight !== 'idle') {\n return {\n accepted: false,\n targetVersion: input.version ?? null,\n restarting: false,\n message: `Refused: another operation is in flight (${this.inFlight}).`,\n }\n }\n const stateBefore = this.readState()\n if (stateBefore.pendingBoot !== null) {\n return {\n accepted: false,\n targetVersion: input.version ?? null,\n restarting: false,\n message: `Refused: version ${stateBefore.pendingBoot.version} is already staged and awaiting restart.`,\n }\n }\n\n // Resolve the target version (explicit or latest from the registry).\n let target = input.version ?? null\n if (target === null) {\n const check = await this.checkServerUpdate()\n if (check.error !== null || check.latestVersion === null) {\n return {\n accepted: false,\n targetVersion: null,\n restarting: false,\n message: `Refused: could not resolve the latest version (${check.error ?? 'no version returned'}).`,\n }\n }\n target = check.latestVersion\n }\n\n const runningVersion = this.runningVersion()\n if (target === runningVersion) {\n return {\n accepted: false,\n targetVersion: target,\n restarting: false,\n message: `Refused: ${this.spec.packageName}@${target} is already running.`,\n }\n }\n\n // Dev server-deploy channel boundary (phase 1: hub only). A `-dev.<epoch>`\n // version stages EXCLUSIVELY from tarballs previously uploaded to THIS\n // node's `server-root/dev-uploads/<version>/` — it never exists on the\n // npm registry, so a node without the uploads must fail loudly here\n // instead of timing out inside `npm install`.\n if (isDevChannelVersion(target)) {\n const uploadsDir = devUploadVersionDir(this.rootDir(), target)\n if (!fs.existsSync(uploadsDir)) {\n return {\n accepted: false,\n targetVersion: target,\n restarting: false,\n message:\n `Refused: dev-channel version ${target} has no uploaded tarballs on this node ` +\n `(${uploadsDir} is missing). Dev server deploys are hub-only in this phase — ` +\n `upload via \\`camstack deploy-server\\` against this node first.`,\n }\n }\n }\n\n this.inFlight = 'staging'\n try {\n await this.stageAndActivate(target)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n this.logger.error('root package update staging failed', {\n meta: { targetVersion: target, error: message },\n })\n return {\n accepted: false,\n targetVersion: target,\n restarting: false,\n message: `Staging failed: ${message}`,\n }\n } finally {\n this.inFlight = 'idle'\n }\n\n // Arm the probation boot + schedule the graceful restart.\n const state = this.readState()\n writeServerRootState(this.rootDir(), {\n ...state,\n pendingBoot: {\n version: target,\n fromVersion: state.currentVersion,\n requestedAtMs: this.now(),\n bootAttempts: 0,\n },\n rolledBack: null,\n })\n this.logger.info('root package update staged — restarting to apply', {\n meta: { targetVersion: target, fromVersion: state.currentVersion },\n })\n this.restartServerFn(`${RESTART_REASON_PREFIX}: ${this.spec.packageName}@${target}`)\n return {\n accepted: true,\n targetVersion: target,\n restarting: true,\n message: `Staged ${this.spec.packageName}@${target} — restarting to apply (probation boot with auto-rollback).`,\n }\n }\n\n /**\n * npm-install the target closure into a SAME-FS staging dir inside\n * `versions/`, validate it, then atomically rename it into place.\n */\n private async stageAndActivate(target: string): Promise<void> {\n const rootDir = this.rootDir()\n const vDir = versionsDir(rootDir)\n fs.mkdirSync(vDir, { recursive: true })\n const stagingDir = path.join(vDir, `.staging-${target}-${process.pid}-${this.now()}`)\n fs.mkdirSync(stagingDir, { recursive: true })\n\n try {\n // Dev server-deploy channel: when tarballs for `target` were uploaded to\n // `server-root/dev-uploads/<target>/`, stage from THEM instead of the\n // registry — a synthetic package.json depends on the root tgz via\n // `file:` and pins every other uploaded package through `overrides`, so\n // ANY transitive occurrence resolves to the uploaded copy. External deps\n // still come from npmjs / CAMSTACK_NPM_REGISTRY. Everything downstream\n // (entry check, version check, activation, probation) is unchanged.\n const devDir = devUploadVersionDir(rootDir, target)\n const registry = this.env['CAMSTACK_NPM_REGISTRY']\n const installArgs = ['install', '--omit=dev', '--no-audit', '--no-fund', '--loglevel=error']\n if (fs.existsSync(devDir)) {\n fs.writeFileSync(\n path.join(stagingDir, 'package.json'),\n JSON.stringify(this.buildDevUploadsPackageJson(devDir, target), null, 2),\n 'utf-8',\n )\n await this.execNpm([...installArgs, ...buildNpmRegistryArgs(registry)], {\n cwd: stagingDir,\n timeoutMs: NPM_INSTALL_TIMEOUT_MS,\n })\n } else {\n fs.writeFileSync(\n path.join(stagingDir, 'package.json'),\n JSON.stringify({ name: 'camstack-node-root', private: true }, null, 2),\n 'utf-8',\n )\n await this.execNpm(\n [...installArgs, `${this.spec.packageName}@${target}`, ...buildNpmRegistryArgs(registry)],\n { cwd: stagingDir, timeoutMs: NPM_INSTALL_TIMEOUT_MS },\n )\n }\n\n const entry = rootEntryPath(stagingDir, this.spec)\n if (!fs.existsSync(entry)) {\n throw new Error(`staged closure is missing the root entry (${entry})`)\n }\n const stagedVersion = readPackageVersion(\n path.join(rootPackageDir(stagingDir, this.spec), 'package.json'),\n )\n if (stagedVersion !== target) {\n throw new Error(\n `staged closure version mismatch: expected ${target}, got ${stagedVersion ?? 'unknown'}`,\n )\n }\n\n // Activate: evict any existing dir for this version, then atomic rename.\n const dest = versionDir(rootDir, target)\n if (fs.existsSync(dest)) {\n const aside = `${dest}.evicted-${this.now()}`\n await fs.promises.rename(dest, aside)\n await fs.promises.rm(aside, { recursive: true, force: true }).catch(() => undefined)\n }\n await fs.promises.rename(stagingDir, dest)\n } catch (err) {\n await fs.promises.rm(stagingDir, { recursive: true, force: true }).catch(() => undefined)\n throw err\n }\n }\n\n /**\n * Synthetic staging package.json for the dev server-deploy channel:\n * `dependencies` carries the root package as a `file:` ref to its uploaded\n * tgz; `overrides` maps EVERY OTHER uploaded `@camstack/<name>` to its tgz\n * so any transitive occurrence resolves to the uploaded copy, never npm.\n * Fail-closed: a missing/invalid manifest, a manifest for a different\n * version, a missing root package, or a listed-but-absent tgz all throw\n * (surfaced by `applyServerUpdate` as a staging failure).\n */\n private buildDevUploadsPackageJson(\n devDir: string,\n target: string,\n ): {\n readonly name: string\n readonly private: true\n readonly dependencies: Readonly<Record<string, string>>\n readonly overrides?: Readonly<Record<string, string>>\n } {\n const manifest = readDevUploadManifest(devDir)\n if (manifest === null) {\n throw new Error(`dev-uploads manifest missing or invalid in ${devDir}`)\n }\n if (manifest.version !== target) {\n throw new Error(\n `dev-uploads manifest version mismatch: dir is for ${target}, manifest says ${manifest.version}`,\n )\n }\n const rootTgz = manifest.packages[this.spec.packageName]\n if (rootTgz === undefined) {\n throw new Error(\n `dev-uploads for ${target} do not include the root package ${this.spec.packageName}`,\n )\n }\n const fileRef = (filename: string): string => {\n const abs = path.join(devDir, filename)\n if (!fs.existsSync(abs)) {\n throw new Error(`dev-uploads tarball listed in the manifest is missing: ${abs}`)\n }\n return `file:${abs}`\n }\n const overrides: Record<string, string> = {}\n for (const [pkgName, filename] of Object.entries(manifest.packages)) {\n if (pkgName === this.spec.packageName) continue\n overrides[pkgName] = fileRef(filename)\n }\n return {\n name: 'camstack-node-root',\n private: true,\n dependencies: { [this.spec.packageName]: fileRef(rootTgz) },\n ...(Object.keys(overrides).length > 0 ? { overrides } : {}),\n }\n }\n\n // ── Rollback ──────────────────────────────────────────────────────────\n\n async rollbackServerUpdate(): Promise<ServerUpdateActionResult> {\n // Same reentrancy/precondition guards as applyServerUpdate: without them a\n // rollback racing an armed update (10s restart grace) silently overwrites\n // pendingBoot — last write wins with no error to the loser.\n if (this.inFlight !== 'idle') {\n return {\n accepted: false,\n targetVersion: null,\n restarting: false,\n message: `Refused: another operation is in flight (${this.inFlight}).`,\n }\n }\n const state = this.readState()\n if (state.pendingBoot !== null) {\n return {\n accepted: false,\n targetVersion: null,\n restarting: false,\n message: `Refused: version ${state.pendingBoot.version} is already staged and awaiting restart.`,\n }\n }\n if (state.currentVersion === null || state.previousVersion === null) {\n return {\n accepted: false,\n targetVersion: state.previousVersion,\n restarting: false,\n message: 'Refused: no previous version available to roll back to.',\n }\n }\n const nodeMajor = Number.parseInt(process.versions.node.split('.')[0] ?? '0', 10)\n const invalidReason = validateVersionDir(\n this.rootDir(),\n state.previousVersion,\n nodeMajor,\n this.spec,\n )\n if (invalidReason !== null) {\n return {\n accepted: false,\n targetVersion: state.previousVersion,\n restarting: false,\n message: `Refused: previous version ${state.previousVersion} is not loadable (${invalidReason}).`,\n }\n }\n\n writeServerRootState(this.rootDir(), {\n ...state,\n pendingBoot: {\n version: state.previousVersion,\n fromVersion: state.currentVersion,\n requestedAtMs: this.now(),\n bootAttempts: 0,\n },\n })\n this.logger.info('root package rollback requested — restarting', {\n meta: { fromVersion: state.currentVersion, toVersion: state.previousVersion },\n })\n this.restartServerFn(\n `${RESTART_REASON_PREFIX}: rollback to ${this.spec.packageName}@${state.previousVersion}`,\n )\n return {\n accepted: true,\n targetVersion: state.previousVersion,\n restarting: true,\n message: `Rolling back to ${this.spec.packageName}@${state.previousVersion} — restarting.`,\n }\n }\n\n // ── Plain restart ─────────────────────────────────────────────────────\n\n /**\n * Plain process restart — no version change. Backs the `restartServer` cap\n * method. Guards mirror apply/rollback: refuse while a stage is in flight\n * (a concurrent npm install must not be interrupted) or while a version is\n * already staged awaiting restart (a naive bounce would boot the OLD version\n * and orphan the pending one — the operator should apply/rollback instead).\n */\n restartNode(): ServerUpdateActionResult {\n const runningVersion = this.runningVersion()\n if (this.inFlight !== 'idle') {\n return {\n accepted: false,\n targetVersion: runningVersion,\n restarting: false,\n message: `Refused: another operation is in flight (${this.inFlight}).`,\n }\n }\n const state = this.readState()\n if (state.pendingBoot !== null) {\n return {\n accepted: false,\n targetVersion: runningVersion,\n restarting: false,\n message: `Refused: version ${state.pendingBoot.version} is staged and awaiting restart — apply or roll it back instead of a plain restart.`,\n }\n }\n this.logger.info('plain node restart requested', { meta: { runningVersion } })\n this.restartServerFn(`${RESTART_REASON_PREFIX}: manual restart`)\n return {\n accepted: true,\n targetVersion: runningVersion,\n restarting: true,\n message: 'Restarting the node — the supervisor relaunches it on the same version.',\n }\n }\n\n // ── Boot health confirmation ──────────────────────────────────────────\n\n /**\n * Called by the post-boot path once the node is READY (hub: post-boot\n * pipeline; agent: first acked `$hub.registerNode`). When THIS process is\n * the probation boot of a pending version, promote it: `currentVersion` ←\n * pending, `previousVersion` ← the version it replaced (N-1 retained for\n * rollback), clear `pendingBoot` + `rolledBack`, and prune every version\n * dir beyond {current, previous}. No-op otherwise.\n */\n confirmBootHealthy(): { promoted: string | null } {\n const state = readServerRootState(this.rootDir())\n if (state === null || state.pendingBoot === null) return { promoted: null }\n\n const activeVersion = this.env[this.envNames.activeVersion] ?? null\n if (activeVersion !== state.pendingBoot.version) {\n // We are NOT the probation boot of this pending version (e.g. the\n // starter never ran, or it booted a different closure). Leave the\n // marker for the starter's own rollback logic.\n return { promoted: null }\n }\n\n const promoted: ServerRootState = {\n ...state,\n currentVersion: state.pendingBoot.version,\n previousVersion: state.pendingBoot.fromVersion,\n pendingBoot: null,\n rolledBack: null,\n }\n writeServerRootState(this.rootDir(), promoted)\n this.pruneVersions(\n [promoted.currentVersion, promoted.previousVersion].filter(\n (v): v is string => typeof v === 'string',\n ),\n )\n this.sweepDevUploads(DEV_UPLOADS_KEEP_COUNT)\n this.logger.info('root package update confirmed healthy', {\n meta: {\n version: promoted.currentVersion,\n previousVersion: promoted.previousVersion,\n },\n })\n return { promoted: promoted.currentVersion }\n }\n\n /**\n * Remove version dirs under `versions/` not in `keep`.\n *\n * Transient dot-entries (`.staging-*` / `.evicted-*`) are NOT pruned unless\n * older than {@link RootUpdateService.STALE_TRANSIENT_MS}: the API starts\n * serving BEFORE the post-boot `confirmBootHealthy()` runs, so a concurrent\n * `applyServerUpdate` can be mid-npm-install into a fresh `.staging-*` dir\n * when this prune fires — deleting it would corrupt that install. Fresh\n * transient dirs are owned by their in-flight operation (which cleans up on\n * failure); the staleness sweep only collects dirs orphaned by a crash.\n */\n private pruneVersions(keep: readonly string[]): void {\n const vDir = versionsDir(this.rootDir())\n let entries: string[]\n try {\n entries = fs.readdirSync(vDir)\n } catch {\n return\n }\n for (const entry of entries) {\n if (keep.includes(entry)) continue\n const full = path.join(vDir, entry)\n if (entry.startsWith('.')) {\n try {\n const ageMs = this.now() - fs.statSync(full).mtimeMs\n if (ageMs < RootUpdateService.STALE_TRANSIENT_MS) continue\n } catch {\n // stat failed (already gone / unreadable) — skip it\n continue\n }\n }\n try {\n fs.rmSync(full, { recursive: true, force: true })\n this.logger.debug('pruned server-root version dir', { meta: { entry } })\n } catch (err) {\n this.logger.warn('failed to prune server-root version dir', {\n meta: { entry, error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n }\n\n /**\n * Promote-time sweep of `server-root/dev-uploads/`: keep only the\n * `keepCount` most recent entries (ordered by the self-describing\n * `-dev.<epochSeconds>` suffix; non-dev-shaped residue counts as oldest).\n * Uses the same rename-aside graveyard pattern as the addon installer —\n * a rename is a metadata op that succeeds even when a tarball is held\n * open, and residue in the graveyard is re-swept on the next promote.\n */\n private sweepDevUploads(keepCount: number): void {\n const dir = devUploadsDir(this.rootDir())\n let entries: string[]\n try {\n entries = fs.readdirSync(dir)\n } catch {\n return // no dev-uploads dir — nothing to sweep\n }\n const graveyard = path.join(dir, '.sweeping')\n // Best-effort collect residue left behind by an earlier sweep first.\n try {\n for (const residue of fs.readdirSync(graveyard)) {\n try {\n fs.rmSync(path.join(graveyard, residue), { recursive: true, force: true })\n } catch {\n /* still held open — retried on the next promote */\n }\n }\n } catch {\n /* graveyard absent — nothing to collect */\n }\n\n const versions = entries.filter((name) => !name.startsWith('.'))\n const byNewestFirst = [...versions].sort(\n (a, b) => (devChannelEpoch(b) ?? -1) - (devChannelEpoch(a) ?? -1),\n )\n const doomed = byNewestFirst.slice(keepCount)\n if (doomed.length === 0) return\n fs.mkdirSync(graveyard, { recursive: true })\n for (const entry of doomed) {\n const aside = path.join(graveyard, `${entry}-${this.now()}`)\n try {\n fs.renameSync(path.join(dir, entry), aside)\n } catch (err) {\n this.logger.warn('failed to move dev-uploads entry aside for sweep', {\n meta: { entry, error: err instanceof Error ? err.message : String(err) },\n })\n continue\n }\n try {\n fs.rmSync(aside, { recursive: true, force: true })\n this.logger.debug('swept dev-uploads entry', { meta: { entry } })\n } catch {\n /* held open — the aside copy is collected on the next promote */\n }\n }\n }\n\n /** Transient (.staging- / .evicted-) dirs younger than this are never pruned. */\n private static readonly STALE_TRANSIENT_MS = 24 * 60 * 60 * 1000\n}\n","export { loadAgentConfig } from './agent-config'\nexport type { AgentConfig } from './agent-config'\n\nexport { createAgentService } from './agent-service'\nexport type { AgentServiceDeps, LoadedAddonEntry } from './agent-service'\n\nexport { startAgent } from './agent-bootstrap'\nexport { createAgentHttpServer, startAgentHttpServer } from './agent-http'\nexport type { AgentHttpConfig } from './agent-http'\n","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\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 applyBundle: (buf) =>\n applyDeployedBundle({\n addonsDir: deps.addonsDir,\n addonId,\n bundle: buf,\n extract,\n logger: deploySwapLogger,\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","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 })\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 * 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","/**\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":["src_exports","__export","AGENT_ROOT_SPEC","RootUpdateService","serverRootDir","writeRestartIntentMarker","module","__toCommonJS","fs","__toESM","path","path2","fs2","path3","fs3","path4","fs4","fs5","path5","fs","os","path","fs","path","import_node_crypto","undicicFetch","resolve","buf","addonDir","cpus","fs","path","fs","path","import_node_crypto","Fastify","import_system","import_types","fs","path","fs","path","import_system","import_system","readConfigFile"]}
|