@camstack/agent 1.0.8 → 1.1.1
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-U6SD2QE6.mjs → chunk-EMAD7O4T.mjs} +132 -16
- package/dist/chunk-EMAD7O4T.mjs.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +153 -16
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +26 -1
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +126 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +3 -2
- package/dist/chunk-U6SD2QE6.mjs.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
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-
|
|
5
|
+
} from "./chunk-EMAD7O4T.mjs";
|
|
6
6
|
|
|
7
7
|
// src/cli.ts
|
|
8
|
+
import { existsSync } from "fs";
|
|
9
|
+
import { Module } from "module";
|
|
8
10
|
import * as os from "os";
|
|
11
|
+
import * as path from "path";
|
|
9
12
|
function parseArgs(argv) {
|
|
10
13
|
const args2 = {};
|
|
11
14
|
for (let i = 2; i < argv.length; i++) {
|
|
@@ -114,6 +117,28 @@ if (args.secret && !process.env["CAMSTACK_CLUSTER_SECRET"]) {
|
|
|
114
117
|
if (args.statusPort && !process.env["CAMSTACK_STATUS_PORT"]) {
|
|
115
118
|
process.env["CAMSTACK_STATUS_PORT"] = args.statusPort;
|
|
116
119
|
}
|
|
120
|
+
if (!process.env["CAMSTACK_FRAMEWORK_DIR"]) {
|
|
121
|
+
const scriptPath = process.argv[1];
|
|
122
|
+
if (scriptPath) {
|
|
123
|
+
const frameworkDir = path.resolve(path.dirname(scriptPath), "..", "..");
|
|
124
|
+
const frameworkModules = path.join(frameworkDir, "node_modules");
|
|
125
|
+
if (existsSync(path.join(frameworkModules, "@camstack", "shm-ring"))) {
|
|
126
|
+
process.env["CAMSTACK_FRAMEWORK_DIR"] = frameworkDir;
|
|
127
|
+
const existingNodePath = process.env["NODE_PATH"];
|
|
128
|
+
process.env["NODE_PATH"] = existingNodePath ? `${frameworkModules}${path.delimiter}${existingNodePath}` : frameworkModules;
|
|
129
|
+
Module._initPaths();
|
|
130
|
+
console.log(
|
|
131
|
+
`[Agent] Self-contained framework detected \u2014 CAMSTACK_FRAMEWORK_DIR=${frameworkDir}`
|
|
132
|
+
);
|
|
133
|
+
if (!process.env["CAMSTACK_BUNDLED_ADDONS_DIR"]) {
|
|
134
|
+
const bundledAddons = path.join(frameworkDir, "addons");
|
|
135
|
+
if (existsSync(bundledAddons)) {
|
|
136
|
+
process.env["CAMSTACK_BUNDLED_ADDONS_DIR"] = bundledAddons;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
117
142
|
if (!process.env["CAMSTACK_NODE_ID"]) {
|
|
118
143
|
process.env["CAMSTACK_NODE_ID"] = os.hostname();
|
|
119
144
|
}
|
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 * as os from 'node:os'\nimport { startAgent } from './agent-bootstrap.js'\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// 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":";;;;;;;AASA,YAAY,QAAQ;AAapB,SAAS,UAAU,MAAkC;AACnD,QAAMA,QAAgB,CAAC;AACvB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,UAAM,OAAO,KAAK,IAAI,CAAC;AACvB,YAAQ,KAAK;AAAA,MACX,KAAK;AAAA,MACL,KAAK;AACH,YAAI,QAAQ,QAAQ,CAAC,MAAM,MAAM,SAAS,GAAG;AAC3C,UAAAA,MAAK,OAAO;AACZ;AAAA,QACF;AACA,QAAAA,MAAK,MAAM;AACX;AACA;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,QAAAA,MAAK,OAAO;AACZ;AACA;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,QAAAA,MAAK,OAAO;AACZ;AACA;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,QAAAA,MAAK,WAAW;AAChB;AACA;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,QAAAA,MAAK,SAAS;AACd;AACA;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,QAAAA,MAAK,aAAa;AAClB;AACA;AAAA,MACF,KAAK;AACH,QAAAA,MAAK,OAAO;AACZ;AAAA,MACF;AACE,YAAI,CAAC,IAAI,WAAW,GAAG,KAAK,CAACA,MAAK,KAAK;AACrC,UAAAA,MAAK,MAAM;AACX;AAAA,QACF;AACA,gBAAQ,MAAM,qBAAqB,GAAG,EAAE;AACxC,QAAAA,MAAK,OAAO;AAAA,IAChB;AAAA,EACF;AACA,SAAOA;AACT;AAEA,SAAS,YAAkB;AACzB,UAAQ;AAAA,IACN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0BF,KAAK;AAAA,EACL;AACF;AAEA,IAAM,OAAO,UAAU,QAAQ,IAAI;AAEnC,IAAI,KAAK,MAAM;AACb,YAAU;AACV,UAAQ,KAAK,CAAC;AAChB;AAGA,IAAI,KAAK,OAAO,CAAC,QAAQ,IAAI,sBAAsB,GAAG;AACpD,UAAQ,IAAI,sBAAsB,IAAI,KAAK;AAC7C;AACA,IAAI,KAAK,QAAQ,CAAC,QAAQ,IAAI,kBAAkB,GAAG;AACjD,UAAQ,IAAI,kBAAkB,IAAI,KAAK;AACzC;AACA,IAAI,KAAK,QAAQ,CAAC,QAAQ,IAAI,mBAAmB,GAAG;AAClD,UAAQ,IAAI,mBAAmB,IAAI,KAAK;AAC1C;AACA,IAAI,KAAK,YAAY,CAAC,QAAQ,IAAI,oBAAoB,GAAG;AACvD,UAAQ,IAAI,oBAAoB,IAAI,KAAK;AAC3C;AACA,IAAI,KAAK,UAAU,CAAC,QAAQ,IAAI,yBAAyB,GAAG;AAC1D,UAAQ,IAAI,yBAAyB,IAAI,KAAK;AAChD;AACA,IAAI,KAAK,cAAc,CAAC,QAAQ,IAAI,sBAAsB,GAAG;AAC3D,UAAQ,IAAI,sBAAsB,IAAI,KAAK;AAC7C;AAGA,IAAI,CAAC,QAAQ,IAAI,kBAAkB,GAAG;AACpC,UAAQ,IAAI,kBAAkB,IAAO,YAAS;AAChD;AAIA,IAAI,CAAC,QAAQ,IAAI,sBAAsB,GAAG;AACxC,UAAQ,IAAI,oEAA+D;AAC3E,UAAQ,IAAI,2DAA2D;AACzE;AAEA,WAAW,EAAE,MAAM,CAAC,QAAiB;AACnC,UAAQ,MAAM,wBAAwB,GAAG;AACzC,UAAQ,KAAK,CAAC;AAChB,CAAC;","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 } 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 Module._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.d.ts
CHANGED
|
@@ -75,6 +75,8 @@ interface AgentServiceDeps {
|
|
|
75
75
|
* presenting a matching `clusterSecretHash`. (Cluster-secret gate.)
|
|
76
76
|
*/
|
|
77
77
|
readonly expectedClusterSecretHash?: string;
|
|
78
|
+
/** Pull-install a published addon from npm/GHCR (wired by agent-bootstrap). */
|
|
79
|
+
readonly installFromNpm?: (packageName: string, version: string) => Promise<void>;
|
|
78
80
|
}
|
|
79
81
|
declare function createAgentService(deps: AgentServiceDeps): ServiceSchema;
|
|
80
82
|
|
package/dist/index.js
CHANGED
|
@@ -176,6 +176,37 @@ async function applyDeployedBundle(input) {
|
|
|
176
176
|
return { addonDir: liveDir };
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
// src/fetch-bundle-from-hub.ts
|
|
180
|
+
var import_node_crypto2 = require("crypto");
|
|
181
|
+
var import_undici = require("undici");
|
|
182
|
+
async function fetchBundleFromHub(opts) {
|
|
183
|
+
const authHeader = { Authorization: `Bearer ${opts.token}`, "accept-encoding": "identity" };
|
|
184
|
+
const res = opts.fetchImpl ? await opts.fetchImpl(opts.url, { headers: authHeader }) : await (0, import_undici.fetch)(opts.url, {
|
|
185
|
+
headers: authHeader,
|
|
186
|
+
dispatcher: new import_undici.Agent({ connect: { rejectUnauthorized: false } })
|
|
187
|
+
});
|
|
188
|
+
if (!res.ok) {
|
|
189
|
+
throw new Error(`bundle fetch failed: HTTP ${res.status}`);
|
|
190
|
+
}
|
|
191
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
192
|
+
if (buffer.length !== opts.bytes) {
|
|
193
|
+
throw new Error(`bundle bytes mismatch: expected ${opts.bytes}, got ${buffer.length}`);
|
|
194
|
+
}
|
|
195
|
+
const sha = (0, import_node_crypto2.createHash)("sha256").update(buffer).digest("hex");
|
|
196
|
+
if (sha !== opts.sha256) {
|
|
197
|
+
throw new Error(`bundle sha256 mismatch: expected ${opts.sha256}, got ${sha}`);
|
|
198
|
+
}
|
|
199
|
+
return buffer;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/apply-model-distribution.ts
|
|
203
|
+
async function applyModelDistribution(seam, params) {
|
|
204
|
+
seam.mkdirp(seam.modelsDir);
|
|
205
|
+
const buffer = await seam.fetchBundle(params.source);
|
|
206
|
+
await seam.extract(buffer, seam.modelsDir);
|
|
207
|
+
return { success: true, modelId: params.modelId, format: params.format, path: seam.modelsDir };
|
|
208
|
+
}
|
|
209
|
+
|
|
179
210
|
// src/agent-service.ts
|
|
180
211
|
var deploySwapLogger = {
|
|
181
212
|
info: (msg) => console.log(`[Agent] ${msg}`),
|
|
@@ -209,6 +240,28 @@ async function withTimeout(p, ms, fallback) {
|
|
|
209
240
|
}
|
|
210
241
|
}
|
|
211
242
|
var METRICS_SNAPSHOT_TIMEOUT_MS = 2e3;
|
|
243
|
+
function resolveDeployAction(seam) {
|
|
244
|
+
return async (params) => {
|
|
245
|
+
const { addonId, source, bundle } = params;
|
|
246
|
+
if (source && (0, import_system.isAddonDeploySource)(source)) {
|
|
247
|
+
if (source.kind === "npm") {
|
|
248
|
+
await seam.installFromNpm(addonId, source.version);
|
|
249
|
+
return { success: true, addonId };
|
|
250
|
+
}
|
|
251
|
+
const buf2 = await seam.fetchBundle(source);
|
|
252
|
+
const { addonDir: addonDir2 } = await seam.applyBundle(buf2);
|
|
253
|
+
seam.onApplied(addonDir2);
|
|
254
|
+
return { success: true, addonId, path: addonDir2 };
|
|
255
|
+
}
|
|
256
|
+
if (bundle === void 0) {
|
|
257
|
+
throw new Error("$agent.deploy: no source descriptor and no legacy bundle provided");
|
|
258
|
+
}
|
|
259
|
+
const buf = typeof bundle === "string" ? Buffer.from(bundle, "base64") : bundle;
|
|
260
|
+
const { addonDir } = await seam.applyBundle(buf);
|
|
261
|
+
seam.onApplied(addonDir);
|
|
262
|
+
return { success: true, addonId, path: addonDir };
|
|
263
|
+
};
|
|
264
|
+
}
|
|
212
265
|
function readHubAddressFromConfig(configPath) {
|
|
213
266
|
if (!configPath) return null;
|
|
214
267
|
try {
|
|
@@ -381,8 +434,7 @@ function createAgentService(deps) {
|
|
|
381
434
|
deploy: {
|
|
382
435
|
handler: async (ctx) => {
|
|
383
436
|
const { params } = ctx;
|
|
384
|
-
const { addonId, bundle } = params;
|
|
385
|
-
const bufferData = typeof bundle === "string" ? Buffer.from(bundle, "base64") : bundle;
|
|
437
|
+
const { addonId, source, bundle } = params;
|
|
386
438
|
const { execFile } = await import("child_process");
|
|
387
439
|
const { promisify } = await import("util");
|
|
388
440
|
const execFileAsync = promisify(execFile);
|
|
@@ -400,17 +452,74 @@ function createAgentService(deps) {
|
|
|
400
452
|
}
|
|
401
453
|
}
|
|
402
454
|
};
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
455
|
+
const seam = {
|
|
456
|
+
installFromNpm: (pkg, v) => {
|
|
457
|
+
if (!deps.installFromNpm) {
|
|
458
|
+
throw new Error("npm deploy source unsupported on this agent");
|
|
459
|
+
}
|
|
460
|
+
return deps.installFromNpm(pkg, v);
|
|
461
|
+
},
|
|
462
|
+
applyBundle: (buf) => applyDeployedBundle({
|
|
463
|
+
addonsDir: deps.addonsDir,
|
|
464
|
+
addonId,
|
|
465
|
+
bundle: buf,
|
|
466
|
+
extract,
|
|
467
|
+
logger: deploySwapLogger
|
|
468
|
+
}),
|
|
469
|
+
fetchBundle: (s) => fetchBundleFromHub(s),
|
|
470
|
+
// Evict every addon DECLARATION id this package contributes
|
|
471
|
+
// from `loadedAddons` so the follow-up `$agent.reload` actually
|
|
472
|
+
// re-instantiates it. `loadDeployedAddons` skips any addon
|
|
473
|
+
// still present in `loadedAddons`; without this eviction a
|
|
474
|
+
// redeploy would leave the agent pinned to the pre-update
|
|
475
|
+
// version. The `deploy` param `addonId` is the PACKAGE name
|
|
476
|
+
// (used only as the on-disk dir), whereas `loadedAddons` is
|
|
477
|
+
// keyed by the addon DECLARATION id — they differ for scoped
|
|
478
|
+
// packages, so we read the extracted manifest to bridge them.
|
|
479
|
+
onApplied: (addonDir) => {
|
|
480
|
+
for (const declId of readDeployedAddonIds(addonDir)) {
|
|
481
|
+
deps.loadedAddons.delete(declId);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
return resolveDeployAction(seam)({ addonId, source, bundle });
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
/**
|
|
489
|
+
* Pull a staged model tarball from the hub and untar it into this node's
|
|
490
|
+
* `<dataDir>/models` (Model Studio P2). Reuses the agent-pull machinery:
|
|
491
|
+
* `fetchBundleFromHub` verifies bytes + sha256 before extraction. The
|
|
492
|
+
* existing `isModelDownloaded` then reports the model present, so the
|
|
493
|
+
* detection-pipeline can load it locally.
|
|
494
|
+
*/
|
|
495
|
+
distributeModel: {
|
|
496
|
+
handler: async (ctx) => {
|
|
497
|
+
const { params } = ctx;
|
|
498
|
+
const { execFile } = await import("child_process");
|
|
499
|
+
const { promisify } = await import("util");
|
|
500
|
+
const execFileAsync = promisify(execFile);
|
|
501
|
+
const modelsDir = path3.join(deps.dataDir, "models");
|
|
502
|
+
const seam = {
|
|
503
|
+
modelsDir,
|
|
504
|
+
fetchBundle: (s) => fetchBundleFromHub(s),
|
|
505
|
+
extract: async (tgz, destDir) => {
|
|
506
|
+
const tmp = path3.join(
|
|
507
|
+
destDir,
|
|
508
|
+
`.dist-${Date.now()}-${Math.random().toString(36).slice(2)}.tgz`
|
|
509
|
+
);
|
|
510
|
+
fs3.writeFileSync(tmp, tgz);
|
|
511
|
+
try {
|
|
512
|
+
await execFileAsync("tar", ["-xzf", tmp, "-C", destDir], { timeout: 6e4 });
|
|
513
|
+
} finally {
|
|
514
|
+
try {
|
|
515
|
+
fs3.unlinkSync(tmp);
|
|
516
|
+
} catch {
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
},
|
|
520
|
+
mkdirp: (dir) => fs3.mkdirSync(dir, { recursive: true })
|
|
521
|
+
};
|
|
522
|
+
return applyModelDistribution(seam, params);
|
|
414
523
|
}
|
|
415
524
|
},
|
|
416
525
|
/**
|
|
@@ -1082,7 +1191,10 @@ async function startAgent(configPath) {
|
|
|
1082
1191
|
subtree.registerNode(params);
|
|
1083
1192
|
triggerUpwardRegistration();
|
|
1084
1193
|
},
|
|
1085
|
-
expectedClusterSecretHash: config.secret ? (0, import_system3.hashClusterSecret)(config.secret) : void 0
|
|
1194
|
+
expectedClusterSecretHash: config.secret ? (0, import_system3.hashClusterSecret)(config.secret) : void 0,
|
|
1195
|
+
installFromNpm: async (pkg, version) => {
|
|
1196
|
+
await installer.install(pkg, version);
|
|
1197
|
+
}
|
|
1086
1198
|
});
|
|
1087
1199
|
broker.createService(agentServiceSchema);
|
|
1088
1200
|
const agentTcpPort = (0, import_system3.deriveAgentListenPort)(broker.nodeID);
|