@camstack/agent 1.1.18 → 1.1.20

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/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import { createRequire as __cr } from 'node:module'; const require = globalThis.require ?? __cr(import.meta.url);
3
3
  import {
4
4
  startAgent
5
- } from "./chunk-XEHYJVPH.mjs";
5
+ } from "./chunk-BWAPSCEC.mjs";
6
6
 
7
7
  // src/cli.ts
8
8
  import { existsSync } from "fs";
@@ -86,6 +86,9 @@ Environment variables (override CLI args):
86
86
  CAMSTACK_DATA_DIR Data directory path
87
87
  CAMSTACK_LOG_LEVEL Log level
88
88
  CAMSTACK_CLUSTER_SECRET Cluster secret
89
+ CAMSTACK_HUB_URL Derived automatically from the hub address; set
90
+ only to override the cross-node stream/recording
91
+ pull host
89
92
 
90
93
  Examples:
91
94
  camstack-agent --hub 192.168.1.100
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * CamStack Agent CLI — starts a remote agent node that connects to a hub.\n *\n * Usage:\n * camstack-agent --hub 192.168.1.100\n * camstack-agent --hub 192.168.1.100 --name gpu-box --data ./agent-data\n * CAMSTACK_HUB_ADDRESS=192.168.1.100 camstack-agent\n */\nimport { existsSync } from 'node:fs'\nimport { Module as nodeModule } from 'node:module'\nimport * as os from 'node:os'\nimport * as path from 'node:path'\nimport { startAgent } from './agent-bootstrap.js'\n\n// `Module._initPaths()` re-derives the CJS search paths from a NODE_PATH set\n// after process start. It's a stable Node internal backing NODE_PATH support\n// but is absent from @types/node — declare it via merging (no cast).\ndeclare module 'node:module' {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Module {\n function _initPaths(): void\n }\n}\n\ninterface CliArgs {\n hub?: string\n name?: string\n data?: string\n logLevel?: string\n secret?: string\n statusPort?: string\n help?: boolean\n}\n\nfunction parseArgs(argv: readonly string[]): CliArgs {\n const args: CliArgs = {}\n for (let i = 2; i < argv.length; i++) {\n const arg = argv[i]!\n const next = argv[i + 1]\n switch (arg) {\n case '--hub':\n case '-h':\n if (arg === '-h' && !next?.match(/^[\\d.]+/)) {\n args.help = true\n break\n }\n args.hub = next\n i++\n break\n case '--name':\n case '-n':\n args.name = next\n i++\n break\n case '--data':\n case '-d':\n args.data = next\n i++\n break\n case '--log-level':\n case '-l':\n args.logLevel = next\n i++\n break\n case '--secret':\n case '-s':\n args.secret = next\n i++\n break\n case '--status-port':\n case '-p':\n args.statusPort = next\n i++\n break\n case '--help':\n args.help = true\n break\n default:\n if (!arg.startsWith('-') && !args.hub) {\n args.hub = arg\n break\n }\n console.error(`Unknown argument: ${arg}`)\n args.help = true\n }\n }\n return args\n}\n\nfunction printHelp(): void {\n console.log(\n `\ncamstack-agent — CamStack remote agent node\n\nUsage:\n camstack-agent --hub <hub-address> [options]\n camstack-agent <hub-address> [options]\n\nOptions:\n --hub, -h <address> Hub address (IP or hostname, e.g. 192.168.1.100)\n --name, -n <name> Node name (default: hostname)\n --data, -d <path> Data directory (default: ./camstack-data)\n --log-level, -l <level> Log level: trace|debug|info|warn|error (default: info)\n --secret, -s <secret> Cluster secret (nodes must share the same secret)\n --help Show this help\n\nEnvironment variables (override CLI args):\n CAMSTACK_NODE_ID Node identifier\n CAMSTACK_HUB_ADDRESS Hub address\n CAMSTACK_DATA_DIR Data directory path\n CAMSTACK_LOG_LEVEL Log level\n CAMSTACK_CLUSTER_SECRET Cluster secret\n\nExamples:\n camstack-agent --hub 192.168.1.100\n camstack-agent --hub 192.168.1.100 --name gpu-box --log-level debug\n CAMSTACK_HUB_ADDRESS=192.168.1.100 camstack-agent\n`.trim(),\n )\n}\n\nconst args = parseArgs(process.argv)\n\nif (args.help) {\n printHelp()\n process.exit(0)\n}\n\n// CLI args → env vars (env vars set externally take precedence via agent-config.ts)\nif (args.hub && !process.env['CAMSTACK_HUB_ADDRESS']) {\n process.env['CAMSTACK_HUB_ADDRESS'] = args.hub\n}\nif (args.name && !process.env['CAMSTACK_NODE_ID']) {\n process.env['CAMSTACK_NODE_ID'] = args.name\n}\nif (args.data && !process.env['CAMSTACK_DATA_DIR']) {\n process.env['CAMSTACK_DATA_DIR'] = args.data\n}\nif (args.logLevel && !process.env['CAMSTACK_LOG_LEVEL']) {\n process.env['CAMSTACK_LOG_LEVEL'] = args.logLevel\n}\nif (args.secret && !process.env['CAMSTACK_CLUSTER_SECRET']) {\n process.env['CAMSTACK_CLUSTER_SECRET'] = args.secret\n}\nif (args.statusPort && !process.env['CAMSTACK_STATUS_PORT']) {\n process.env['CAMSTACK_STATUS_PORT'] = args.statusPort\n}\n\n// Packaged-app framework resolution. In a self-contained build (e.g. the\n// Electron `.app`, where this CLI lives at `<root>/agent/dist/cli.js` and the\n// host-provided package closure at `<root>/node_modules`), addons installed\n// under the data dir cannot walk up to the framework's node_modules. Mirror the\n// Docker image's setup: point the addon-runner's resolver hook at the framework\n// (CAMSTACK_FRAMEWORK_DIR, for ESM imports) AND seed NODE_PATH (for CJS\n// `require`) so host externals (@camstack/shm-ring, sharp, node-av, …) resolve.\n// No-op in dev (workspace symlinks resolve the framework) and when an operator\n// or the Docker image has already set CAMSTACK_FRAMEWORK_DIR.\nif (!process.env['CAMSTACK_FRAMEWORK_DIR']) {\n const scriptPath = process.argv[1]\n if (scriptPath) {\n const frameworkDir = path.resolve(path.dirname(scriptPath), '..', '..')\n const frameworkModules = path.join(frameworkDir, 'node_modules')\n // Sentinel: a host-provided native package present in the framework's\n // node_modules confirms the self-contained layout (absent in dev).\n if (existsSync(path.join(frameworkModules, '@camstack', 'shm-ring'))) {\n process.env['CAMSTACK_FRAMEWORK_DIR'] = frameworkDir\n const existingNodePath = process.env['NODE_PATH']\n process.env['NODE_PATH'] = existingNodePath\n ? `${frameworkModules}${path.delimiter}${existingNodePath}`\n : frameworkModules\n // Node reads NODE_PATH once at startup into the CJS module search paths;\n // setting it now (post-start) only reaches CHILD processes that inherit\n // it at spawn (the forked addon-runners). The agent's OWN main process —\n // where bootCoreAddons imports the in-process infra builtins (storage /\n // settings / metrics from @camstack/system) — must re-derive its search\n // paths or those addons' host-dep requires (@camstack/types, …) fail with\n // \"Cannot find module\" and the agent boots with NO infrastructure.\n nodeModule._initPaths()\n console.log(\n `[Agent] Self-contained framework detected — CAMSTACK_FRAMEWORK_DIR=${frameworkDir}`,\n )\n // Also point the bootstrap installer at the bundled addons so a fresh\n // (empty) data dir gets seeded with the agent's required packages\n // (@camstack/system — platform-probe/storage/settings/metrics builtins —\n // plus the bundled addons). Without this, bootCoreAddons finds no addon\n // packages and the agent runs with NO infrastructure (no platform-probe →\n // detection engine can't auto-pick the node's accelerator, no storage,\n // etc.). Mirrors the Docker image's CAMSTACK_BUNDLED_ADDONS_DIR.\n if (!process.env['CAMSTACK_BUNDLED_ADDONS_DIR']) {\n const bundledAddons = path.join(frameworkDir, 'addons')\n if (existsSync(bundledAddons)) {\n process.env['CAMSTACK_BUNDLED_ADDONS_DIR'] = bundledAddons\n }\n }\n }\n }\n}\n\n// Default node name to hostname if not set\nif (!process.env['CAMSTACK_NODE_ID']) {\n process.env['CAMSTACK_NODE_ID'] = os.hostname()\n}\n\n// Hub address is optional — agent starts in discovery mode without it.\n// The agent UI on :4444 lets the user configure the hub connection.\nif (!process.env['CAMSTACK_HUB_ADDRESS']) {\n console.log('[Agent] No hub address specified — starting in discovery mode')\n console.log('[Agent] Configure hub connection at http://localhost:4444')\n}\n\nstartAgent().catch((err: unknown) => {\n console.error('[Agent] Fatal error:', err)\n process.exit(1)\n})\n"],"mappings":"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","names":["args"]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * CamStack Agent CLI — starts a remote agent node that connects to a hub.\n *\n * Usage:\n * camstack-agent --hub 192.168.1.100\n * camstack-agent --hub 192.168.1.100 --name gpu-box --data ./agent-data\n * CAMSTACK_HUB_ADDRESS=192.168.1.100 camstack-agent\n */\nimport { existsSync } from 'node:fs'\nimport { Module as nodeModule } from 'node:module'\nimport * as os from 'node:os'\nimport * as path from 'node:path'\nimport { startAgent } from './agent-bootstrap.js'\n\n// `Module._initPaths()` re-derives the CJS search paths from a NODE_PATH set\n// after process start. It's a stable Node internal backing NODE_PATH support\n// but is absent from @types/node — declare it via merging (no cast).\ndeclare module 'node:module' {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Module {\n function _initPaths(): void\n }\n}\n\ninterface CliArgs {\n hub?: string\n name?: string\n data?: string\n logLevel?: string\n secret?: string\n statusPort?: string\n help?: boolean\n}\n\nfunction parseArgs(argv: readonly string[]): CliArgs {\n const args: CliArgs = {}\n for (let i = 2; i < argv.length; i++) {\n const arg = argv[i]!\n const next = argv[i + 1]\n switch (arg) {\n case '--hub':\n case '-h':\n if (arg === '-h' && !next?.match(/^[\\d.]+/)) {\n args.help = true\n break\n }\n args.hub = next\n i++\n break\n case '--name':\n case '-n':\n args.name = next\n i++\n break\n case '--data':\n case '-d':\n args.data = next\n i++\n break\n case '--log-level':\n case '-l':\n args.logLevel = next\n i++\n break\n case '--secret':\n case '-s':\n args.secret = next\n i++\n break\n case '--status-port':\n case '-p':\n args.statusPort = next\n i++\n break\n case '--help':\n args.help = true\n break\n default:\n if (!arg.startsWith('-') && !args.hub) {\n args.hub = arg\n break\n }\n console.error(`Unknown argument: ${arg}`)\n args.help = true\n }\n }\n return args\n}\n\nfunction printHelp(): void {\n console.log(\n `\ncamstack-agent — CamStack remote agent node\n\nUsage:\n camstack-agent --hub <hub-address> [options]\n camstack-agent <hub-address> [options]\n\nOptions:\n --hub, -h <address> Hub address (IP or hostname, e.g. 192.168.1.100)\n --name, -n <name> Node name (default: hostname)\n --data, -d <path> Data directory (default: ./camstack-data)\n --log-level, -l <level> Log level: trace|debug|info|warn|error (default: info)\n --secret, -s <secret> Cluster secret (nodes must share the same secret)\n --help Show this help\n\nEnvironment variables (override CLI args):\n CAMSTACK_NODE_ID Node identifier\n CAMSTACK_HUB_ADDRESS Hub address\n CAMSTACK_DATA_DIR Data directory path\n CAMSTACK_LOG_LEVEL Log level\n CAMSTACK_CLUSTER_SECRET Cluster secret\n CAMSTACK_HUB_URL Derived automatically from the hub address; set\n only to override the cross-node stream/recording\n pull host\n\nExamples:\n camstack-agent --hub 192.168.1.100\n camstack-agent --hub 192.168.1.100 --name gpu-box --log-level debug\n CAMSTACK_HUB_ADDRESS=192.168.1.100 camstack-agent\n`.trim(),\n )\n}\n\nconst args = parseArgs(process.argv)\n\nif (args.help) {\n printHelp()\n process.exit(0)\n}\n\n// CLI args → env vars (env vars set externally take precedence via agent-config.ts)\nif (args.hub && !process.env['CAMSTACK_HUB_ADDRESS']) {\n process.env['CAMSTACK_HUB_ADDRESS'] = args.hub\n}\nif (args.name && !process.env['CAMSTACK_NODE_ID']) {\n process.env['CAMSTACK_NODE_ID'] = args.name\n}\nif (args.data && !process.env['CAMSTACK_DATA_DIR']) {\n process.env['CAMSTACK_DATA_DIR'] = args.data\n}\nif (args.logLevel && !process.env['CAMSTACK_LOG_LEVEL']) {\n process.env['CAMSTACK_LOG_LEVEL'] = args.logLevel\n}\nif (args.secret && !process.env['CAMSTACK_CLUSTER_SECRET']) {\n process.env['CAMSTACK_CLUSTER_SECRET'] = args.secret\n}\nif (args.statusPort && !process.env['CAMSTACK_STATUS_PORT']) {\n process.env['CAMSTACK_STATUS_PORT'] = args.statusPort\n}\n\n// Packaged-app framework resolution. In a self-contained build (e.g. the\n// Electron `.app`, where this CLI lives at `<root>/agent/dist/cli.js` and the\n// host-provided package closure at `<root>/node_modules`), addons installed\n// under the data dir cannot walk up to the framework's node_modules. Mirror the\n// Docker image's setup: point the addon-runner's resolver hook at the framework\n// (CAMSTACK_FRAMEWORK_DIR, for ESM imports) AND seed NODE_PATH (for CJS\n// `require`) so host externals (@camstack/shm-ring, sharp, node-av, …) resolve.\n// No-op in dev (workspace symlinks resolve the framework) and when an operator\n// or the Docker image has already set CAMSTACK_FRAMEWORK_DIR.\nif (!process.env['CAMSTACK_FRAMEWORK_DIR']) {\n const scriptPath = process.argv[1]\n if (scriptPath) {\n const frameworkDir = path.resolve(path.dirname(scriptPath), '..', '..')\n const frameworkModules = path.join(frameworkDir, 'node_modules')\n // Sentinel: a host-provided native package present in the framework's\n // node_modules confirms the self-contained layout (absent in dev).\n if (existsSync(path.join(frameworkModules, '@camstack', 'shm-ring'))) {\n process.env['CAMSTACK_FRAMEWORK_DIR'] = frameworkDir\n const existingNodePath = process.env['NODE_PATH']\n process.env['NODE_PATH'] = existingNodePath\n ? `${frameworkModules}${path.delimiter}${existingNodePath}`\n : frameworkModules\n // Node reads NODE_PATH once at startup into the CJS module search paths;\n // setting it now (post-start) only reaches CHILD processes that inherit\n // it at spawn (the forked addon-runners). The agent's OWN main process —\n // where bootCoreAddons imports the in-process infra builtins (storage /\n // settings / metrics from @camstack/system) — must re-derive its search\n // paths or those addons' host-dep requires (@camstack/types, …) fail with\n // \"Cannot find module\" and the agent boots with NO infrastructure.\n nodeModule._initPaths()\n console.log(\n `[Agent] Self-contained framework detected — CAMSTACK_FRAMEWORK_DIR=${frameworkDir}`,\n )\n // Also point the bootstrap installer at the bundled addons so a fresh\n // (empty) data dir gets seeded with the agent's required packages\n // (@camstack/system — platform-probe/storage/settings/metrics builtins —\n // plus the bundled addons). Without this, bootCoreAddons finds no addon\n // packages and the agent runs with NO infrastructure (no platform-probe →\n // detection engine can't auto-pick the node's accelerator, no storage,\n // etc.). Mirrors the Docker image's CAMSTACK_BUNDLED_ADDONS_DIR.\n if (!process.env['CAMSTACK_BUNDLED_ADDONS_DIR']) {\n const bundledAddons = path.join(frameworkDir, 'addons')\n if (existsSync(bundledAddons)) {\n process.env['CAMSTACK_BUNDLED_ADDONS_DIR'] = bundledAddons\n }\n }\n }\n }\n}\n\n// Default node name to hostname if not set\nif (!process.env['CAMSTACK_NODE_ID']) {\n process.env['CAMSTACK_NODE_ID'] = os.hostname()\n}\n\n// Hub address is optional — agent starts in discovery mode without it.\n// The agent UI on :4444 lets the user configure the hub connection.\nif (!process.env['CAMSTACK_HUB_ADDRESS']) {\n console.log('[Agent] No hub address specified — starting in discovery mode')\n console.log('[Agent] Configure hub connection at http://localhost:4444')\n}\n\nstartAgent().catch((err: unknown) => {\n console.error('[Agent] Fatal error:', err)\n process.exit(1)\n})\n"],"mappings":"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","names":["args"]}
package/dist/index.js CHANGED
@@ -643,17 +643,108 @@ var path6 = __toESM(require("path"));
643
643
  var fs4 = __toESM(require("fs"));
644
644
  var path4 = __toESM(require("path"));
645
645
  var import_fastify = __toESM(require("fastify"));
646
- function resolveUiDistDir(dataDir) {
647
- const candidates = [
648
- path4.resolve(__dirname, "../../addon-agent-ui/dist"),
649
- path4.join(dataDir, "addons", "@camstack", "addon-agent-ui", "dist"),
650
- path4.join(dataDir, "agent-ui")
651
- ];
652
- for (const dir of candidates) {
653
- if (fs4.existsSync(path4.join(dir, "index.html"))) return dir;
646
+
647
+ // src/derive-hub-url.ts
648
+ var HUB_API_PORT = 4443;
649
+ function deriveHubUrlFromHubAddress(hubAddress) {
650
+ const trimmed = hubAddress.trim();
651
+ if (trimmed.length === 0) return void 0;
652
+ const afterAt = trimmed.includes("@") ? trimmed.split("@")[1] ?? "" : trimmed;
653
+ const authority = afterAt.split("/")[0] ?? "";
654
+ const host = extractHostFromAuthority(authority);
655
+ if (host === void 0) return void 0;
656
+ return `https://${host}:${HUB_API_PORT}`;
657
+ }
658
+ function deriveHubUrlForExport(hubUrlWasExplicit, hubAddress) {
659
+ if (hubUrlWasExplicit) return void 0;
660
+ if (hubAddress === void 0) return void 0;
661
+ return deriveHubUrlFromHubAddress(hubAddress);
662
+ }
663
+ function extractHostFromAuthority(authority) {
664
+ const value = authority.trim();
665
+ if (value.length === 0) return void 0;
666
+ if (value.startsWith("[")) {
667
+ const end = value.indexOf("]");
668
+ if (end > 0) return value.slice(0, end + 1);
669
+ return void 0;
670
+ }
671
+ const host = value.split(":")[0] ?? "";
672
+ return host.length > 0 ? host : void 0;
673
+ }
674
+ var HUB_NODE_ID = "hub";
675
+ var IPV4_MAPPED_PREFIX = "::ffff:";
676
+ var IPV4_DOTTED_QUAD = /^\d{1,3}(?:\.\d{1,3}){3}$/;
677
+ function pickIpv4(ipList) {
678
+ if (!ipList) return null;
679
+ for (const ip of ipList) {
680
+ if (ip.includes(":")) continue;
681
+ if (ip.startsWith("127.")) continue;
682
+ return ip;
654
683
  }
655
684
  return null;
656
685
  }
686
+ function normalizeObservedHost(raw) {
687
+ if (raw === void 0 || raw === null) return void 0;
688
+ const trimmed = raw.trim();
689
+ if (trimmed.length === 0) return void 0;
690
+ if (trimmed.startsWith("[")) return trimmed;
691
+ if (trimmed.toLowerCase().startsWith(IPV4_MAPPED_PREFIX)) {
692
+ const mapped = trimmed.slice(IPV4_MAPPED_PREFIX.length);
693
+ if (IPV4_DOTTED_QUAD.test(mapped)) return mapped;
694
+ }
695
+ if (trimmed.includes(":")) return `[${trimmed}]`;
696
+ return trimmed;
697
+ }
698
+ function deriveHubUrlFromRegistry(nodes) {
699
+ const hub = nodes.find((node) => node.id === HUB_NODE_ID);
700
+ if (hub === void 0) return void 0;
701
+ const host = normalizeObservedHost(hub.udpAddress) ?? pickIpv4(hub.ipList) ?? hub.hostname;
702
+ if (host === void 0 || host.length === 0) return void 0;
703
+ return `https://${host}:${HUB_API_PORT}`;
704
+ }
705
+
706
+ // src/agent-http.ts
707
+ function compareVersions(a, b) {
708
+ if (a === null && b === null) return 0;
709
+ if (a === null) return -1;
710
+ if (b === null) return 1;
711
+ const pa = a.split(".").map((s) => Number.parseInt(s, 10) || 0);
712
+ const pb = b.split(".").map((s) => Number.parseInt(s, 10) || 0);
713
+ const len = Math.max(pa.length, pb.length);
714
+ for (let i = 0; i < len; i++) {
715
+ const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
716
+ if (diff !== 0) return diff;
717
+ }
718
+ return 0;
719
+ }
720
+ function readUiDistCandidate(baseDir) {
721
+ const dir = path4.join(baseDir, "dist");
722
+ if (!fs4.existsSync(path4.join(dir, "index.html"))) return null;
723
+ let version = null;
724
+ try {
725
+ const parsed = JSON.parse(fs4.readFileSync(path4.join(baseDir, "package.json"), "utf-8"));
726
+ version = typeof parsed.version === "string" ? parsed.version : null;
727
+ } catch {
728
+ version = null;
729
+ }
730
+ return { dir, version };
731
+ }
732
+ function pickUiDist(installed, bundled) {
733
+ if (installed && bundled) {
734
+ return compareVersions(bundled.version, installed.version) > 0 ? bundled.dir : installed.dir;
735
+ }
736
+ return installed?.dir ?? bundled?.dir ?? null;
737
+ }
738
+ function resolveUiDistDir(dataDir, bundledAddonsDir, siblingDistDir = path4.resolve(__dirname, "../../addon-agent-ui/dist")) {
739
+ if (fs4.existsSync(path4.join(siblingDistDir, "index.html"))) return siblingDistDir;
740
+ const installed = readUiDistCandidate(path4.join(dataDir, "addons", "@camstack", "addon-agent-ui"));
741
+ const bundled = bundledAddonsDir ? readUiDistCandidate(path4.join(bundledAddonsDir, "addon-agent-ui")) : null;
742
+ const picked = pickUiDist(installed, bundled);
743
+ if (picked) return picked;
744
+ const legacy = path4.join(dataDir, "agent-ui");
745
+ if (fs4.existsSync(path4.join(legacy, "index.html"))) return legacy;
746
+ return null;
747
+ }
657
748
  function readConfigFile(configPath) {
658
749
  if (!fs4.existsSync(configPath)) return {};
659
750
  try {
@@ -674,15 +765,6 @@ function getRegistryNodes(broker) {
674
765
  return [];
675
766
  }
676
767
  }
677
- function pickIpv4(ipList) {
678
- if (!ipList) return null;
679
- for (const ip of ipList) {
680
- if (ip.includes(":")) continue;
681
- if (ip.startsWith("127.")) continue;
682
- return ip;
683
- }
684
- return null;
685
- }
686
768
  function resolveHubEndpoint(nodes) {
687
769
  const hub = nodes.find((n) => n.id === "hub");
688
770
  if (!hub) return null;
@@ -830,19 +912,26 @@ async function createAgentHttpServer(getBroker, config) {
830
912
  const nodes = getRegistryNodes(b);
831
913
  return mapDiscoveredNodes(nodes, b.nodeID);
832
914
  });
833
- const uiDir = resolveUiDistDir(config.dataDir);
915
+ const uiDir = resolveUiDistDir(config.dataDir, process.env["CAMSTACK_BUNDLED_ADDONS_DIR"]);
834
916
  if (uiDir) {
835
917
  const fastifyStatic = await import("@fastify/static");
836
918
  await app.register(fastifyStatic.default, {
837
919
  root: uiDir,
838
920
  prefix: "/",
839
921
  wildcard: false,
840
- decorateReply: false
922
+ // MUST stay true: the SPA-fallback below uses `reply.sendFile`, which
923
+ // only exists when the plugin decorates Reply. With `false` every
924
+ // fallback request 500'd with "reply.sendFile is not a function".
925
+ decorateReply: true
841
926
  });
842
927
  app.setNotFoundHandler(async (req, reply) => {
843
928
  if (req.url.startsWith("/api/") || req.url.startsWith("/health")) {
844
929
  return reply.status(404).send({ error: "Not found" });
845
930
  }
931
+ if (req.url.startsWith("/assets/")) {
932
+ console.warn(`[Agent] UI asset not found (stale index.html?): ${req.url}`);
933
+ return reply.status(404).send({ error: "Asset not found" });
934
+ }
846
935
  return reply.type("text/html").sendFile("index.html");
847
936
  });
848
937
  console.log(`[Agent] UI served from: ${uiDir}`);
@@ -1014,6 +1103,14 @@ var import_system4 = require("@camstack/system");
1014
1103
  var agentLogManager = new import_system4.LogManager(5e3);
1015
1104
  async function startAgent(configPath) {
1016
1105
  const config = loadAgentConfig(configPath);
1106
+ const hubUrlWasExplicit = process.env["CAMSTACK_HUB_URL"] !== void 0;
1107
+ const derivedHubUrl = deriveHubUrlForExport(hubUrlWasExplicit, config.hubAddress);
1108
+ if (derivedHubUrl !== void 0) {
1109
+ process.env["CAMSTACK_HUB_URL"] = derivedHubUrl;
1110
+ console.log(
1111
+ `[Agent] Derived CAMSTACK_HUB_URL=${derivedHubUrl} from hub address "${config.hubAddress}"`
1112
+ );
1113
+ }
1017
1114
  if (config.hubAddress) {
1018
1115
  console.log(
1019
1116
  `[Agent] Starting node "${config.nodeId}" (name="${config.name}") connecting to ${config.hubAddress}`
@@ -1060,6 +1157,13 @@ async function startAgent(configPath) {
1060
1157
  console.log(
1061
1158
  `[Agent] New config: hub=${fresh.hubAddress ?? "discovery"}, secret=${fresh.secret ? "yes" : "none"}`
1062
1159
  );
1160
+ const freshHubUrl = deriveHubUrlForExport(hubUrlWasExplicit, fresh.hubAddress);
1161
+ if (freshHubUrl !== void 0 && process.env["CAMSTACK_HUB_URL"] !== freshHubUrl) {
1162
+ process.env["CAMSTACK_HUB_URL"] = freshHubUrl;
1163
+ console.log(
1164
+ `[Agent] Derived CAMSTACK_HUB_URL=${freshHubUrl} from hub address "${fresh.hubAddress}"`
1165
+ );
1166
+ }
1063
1167
  broker = (0, import_system3.createBroker)({
1064
1168
  nodeID: config.nodeId,
1065
1169
  mode: "agent",
@@ -1324,6 +1428,25 @@ async function startAgent(configPath) {
1324
1428
  (0, import_system3.registerEventBusService)(broker);
1325
1429
  broker.createService((0, import_system3.createHwAccelService)((0, import_system3.createKernelHwAccel)()));
1326
1430
  await broker.start();
1431
+ let hubUrlFromRegistry;
1432
+ const reconcileHubUrlFromRegistry = () => {
1433
+ if (hubUrlWasExplicit) return;
1434
+ const current = process.env["CAMSTACK_HUB_URL"];
1435
+ if (current !== void 0 && current !== hubUrlFromRegistry) return;
1436
+ const fromRegistry = deriveHubUrlFromRegistry(
1437
+ getRegistryNodes(broker)
1438
+ );
1439
+ if (fromRegistry === void 0 || fromRegistry === current) return;
1440
+ process.env["CAMSTACK_HUB_URL"] = fromRegistry;
1441
+ hubUrlFromRegistry = fromRegistry;
1442
+ console.log(`[Agent] Derived CAMSTACK_HUB_URL=${fromRegistry} from live hub connection`);
1443
+ };
1444
+ reconcileHubUrlFromRegistry();
1445
+ broker.localBus.on("$node.connected", (data) => {
1446
+ const node = data.node;
1447
+ if (node?.id !== "hub") return;
1448
+ reconcileHubUrlFromRegistry();
1449
+ });
1327
1450
  let udsEventBridgeDispose = null;
1328
1451
  if (agentUdsRegistry !== void 0) {
1329
1452
  const agentBrokerEventBus = (0, import_system3.getBrokerEventBus)(broker);