@camstack/server 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/api/addon-upload.js +73 -8
- package/dist/api/deploy-stage-registry.js +60 -0
- package/dist/api/model-distributor.js +128 -0
- package/dist/api/trpc/generated-cap-mounts.js +20 -1
- package/dist/api/trpc/generated-cap-routers.js +1068 -1025
- package/dist/api/trpc/trpc.router.js +1 -0
- package/dist/core/addon/addon-package.service.js +5 -3
- package/dist/core/addon/addon-registry.service.js +7 -3
- package/dist/core/addon/addon-settings-provider.js +7 -3
- package/dist/core/agent/agent-registry.service.js +9 -1
- package/dist/core/auth/auth.service.js +0 -3
- package/dist/core/config/config.service.js +0 -3
- package/dist/core/feature/feature.service.js +0 -3
- package/dist/core/moleculer/moleculer.service.js +5 -5
- package/dist/main.js +16 -8
- package/package.json +1 -1
|
@@ -128,6 +128,7 @@ function buildCapabilityRouters(services) {
|
|
|
128
128
|
audioAnalyzer: (0, generated_cap_routers_1.createCapRouter_audioAnalyzer)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'audio-analyzer'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
|
|
129
129
|
audioCodec: (0, generated_cap_routers_1.createCapRouter_audioCodec)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'audio-codec'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
|
|
130
130
|
decoder: (0, generated_cap_routers_1.createCapRouter_decoder)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'decoder'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
|
|
131
|
+
modelConvert: (0, generated_cap_routers_1.createCapRouter_modelConvert)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'model-convert'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
|
|
131
132
|
platformProbe: (0, generated_cap_routers_1.createCapRouter_platformProbe)((_ctx) => (0, cap_mount_helpers_js_1.requireSingleton)(services.capabilityRegistry, 'platform-probe'), (capName, nodeId) => services.moleculer.createCapabilityProxy(capName, nodeId)),
|
|
132
133
|
// ── Cap overrides: hub-only, no remote fallback ─────────────────
|
|
133
134
|
// The cap is intentionally single-node; agents are not directly
|
|
@@ -793,11 +793,11 @@ class AddonPackageService {
|
|
|
793
793
|
// hub/node_modules/@camstack/ — addons resolve everything from
|
|
794
794
|
// their own inlined deps.
|
|
795
795
|
const addonsDir = this.resolveAddonsDir();
|
|
796
|
-
const {
|
|
796
|
+
const { installPackageFromNpm } = await Promise.resolve().then(() => __importStar(require('@camstack/system')));
|
|
797
797
|
const dirName = name.replace(/^@camstack\//, '');
|
|
798
798
|
const targetDir = path.join(addonsDir, dirName);
|
|
799
799
|
const packageSpec = version ? `${name}@${version}` : name;
|
|
800
|
-
|
|
800
|
+
await installPackageFromNpm(packageSpec, targetDir);
|
|
801
801
|
updatedVersion = this.getInstalledPackageVersion(name);
|
|
802
802
|
// Invalidate cache after update
|
|
803
803
|
this.cachedUpdates = null;
|
|
@@ -1794,7 +1794,9 @@ async function packTarball(pkg, version, destRoot, registry) {
|
|
|
1794
1794
|
await execFileAsync('npm', args, { timeout: 60_000, killSignal: 'SIGKILL' });
|
|
1795
1795
|
const tgz = fs.readdirSync(dir).find((f) => f.endsWith('.tgz'));
|
|
1796
1796
|
if (tgz === undefined) {
|
|
1797
|
-
throw new Error(`download failed for ${pkg}@${version}: ${(0, types_1.errMsg)(httpErr)}
|
|
1797
|
+
throw new Error(`download failed for ${pkg}@${version}: ${(0, types_1.errMsg)(httpErr)}`, {
|
|
1798
|
+
cause: httpErr,
|
|
1799
|
+
});
|
|
1798
1800
|
}
|
|
1799
1801
|
return path.join(dir, tgz);
|
|
1800
1802
|
}
|
|
@@ -119,14 +119,14 @@ function parseSerializableRouteDescriptors(raw) {
|
|
|
119
119
|
throw new Error('addon-routes: route descriptor is not an object');
|
|
120
120
|
}
|
|
121
121
|
const method = Reflect.get(entry, 'method');
|
|
122
|
-
const
|
|
123
|
-
if (typeof method !== 'string' || typeof
|
|
122
|
+
const routePath = Reflect.get(entry, 'path');
|
|
123
|
+
if (typeof method !== 'string' || typeof routePath !== 'string') {
|
|
124
124
|
throw new Error('addon-routes: route descriptor missing method/path');
|
|
125
125
|
}
|
|
126
126
|
const description = Reflect.get(entry, 'description');
|
|
127
127
|
return {
|
|
128
128
|
method: asRouteMethod(method),
|
|
129
|
-
path,
|
|
129
|
+
path: routePath,
|
|
130
130
|
access: asRouteAccess(Reflect.get(entry, 'access')),
|
|
131
131
|
...(typeof description === 'string' ? { description } : {}),
|
|
132
132
|
};
|
|
@@ -2431,6 +2431,10 @@ class AddonRegistryService {
|
|
|
2431
2431
|
capabilityRegistry: this.capabilityRegistry,
|
|
2432
2432
|
streamProbe: kernelStreamProbe,
|
|
2433
2433
|
});
|
|
2434
|
+
// Captured for use inside the `ctx` object-literal getters/methods below
|
|
2435
|
+
// (e.g. `get api()`), where `this` rebinds to the literal — an arrow can't
|
|
2436
|
+
// be used for an accessor, so an explicit alias is required.
|
|
2437
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
2434
2438
|
const registry = this;
|
|
2435
2439
|
const rr = this.moleculer.readinessRegistry;
|
|
2436
2440
|
const capHandleCache = new Map();
|
|
@@ -58,12 +58,16 @@ function createAddonSettingsProvider(deps) {
|
|
|
58
58
|
const addon = getAddon(input.addonId);
|
|
59
59
|
if (!addon || typeof addon.getGlobalSettings !== 'function')
|
|
60
60
|
return null;
|
|
61
|
-
|
|
61
|
+
// Pass the REQUESTED nodeId so an addon serving a cluster-central,
|
|
62
|
+
// per-node config (the store is hub-resident) can scope to that node —
|
|
63
|
+
// the hub addon answers for every node, so it must not assume "self".
|
|
64
|
+
const result = await addon.getGlobalSettings(input.overlay, input.cap, input.nodeId);
|
|
62
65
|
return result ? reshapeForOutput(result) : null;
|
|
63
66
|
}
|
|
64
67
|
return forkedGet(input.addonId, 'getGlobalSettings', {
|
|
65
68
|
...(input.overlay ? { overlay: input.overlay } : {}),
|
|
66
69
|
...(input.cap ? { cap: input.cap } : {}),
|
|
70
|
+
...(input.nodeId ? { nodeId: input.nodeId } : {}),
|
|
67
71
|
}, input.nodeId);
|
|
68
72
|
},
|
|
69
73
|
async updateGlobalSettings(input) {
|
|
@@ -72,10 +76,10 @@ function createAddonSettingsProvider(deps) {
|
|
|
72
76
|
if (!addon || typeof addon.updateGlobalSettings !== 'function') {
|
|
73
77
|
throw new Error(`Addon "${input.addonId}" does not implement updateGlobalSettings`);
|
|
74
78
|
}
|
|
75
|
-
await addon.updateGlobalSettings(input.patch);
|
|
79
|
+
await addon.updateGlobalSettings(input.patch, input.nodeId);
|
|
76
80
|
return { success: true };
|
|
77
81
|
}
|
|
78
|
-
return forkedUpdate(input.addonId, 'updateGlobalSettings', { patch: input.patch }, input.nodeId);
|
|
82
|
+
return forkedUpdate(input.addonId, 'updateGlobalSettings', { patch: input.patch, ...(input.nodeId ? { nodeId: input.nodeId } : {}) }, input.nodeId);
|
|
79
83
|
},
|
|
80
84
|
async getDeviceSettings(input) {
|
|
81
85
|
if (gateway.isInProcess(input.addonId, input.nodeId)) {
|
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.AgentRegistryService = void 0;
|
|
37
37
|
const node_crypto_1 = require("node:crypto");
|
|
38
38
|
const os = __importStar(require("node:os"));
|
|
39
|
+
const system_1 = require("@camstack/system");
|
|
39
40
|
const types_1 = require("@camstack/types");
|
|
40
41
|
/** Per-call timeout for `$agent.*` RPC during reconciliation. */
|
|
41
42
|
const AGENT_RECONCILE_RPC_TIMEOUT_MS = 8_000;
|
|
@@ -241,7 +242,14 @@ class AgentRegistryService {
|
|
|
241
242
|
}
|
|
242
243
|
try {
|
|
243
244
|
const broker = this.broker;
|
|
244
|
-
|
|
245
|
+
// The reconcile fires the moment `$hub.registerNode` acks, which can race
|
|
246
|
+
// ahead of Moleculer's service-discovery INFO packet for the agent — a
|
|
247
|
+
// bare call then fails with "Service '$agent.status' is not found". Wrap
|
|
248
|
+
// it so a discovery miss waits for the `$agent` service (the framework's
|
|
249
|
+
// waitForServices primitive) and retries once, instead of giving up and
|
|
250
|
+
// leaving the agent unreconciled until the next connect. (No timers/backoff
|
|
251
|
+
// — that's banned for node readiness; this is the sanctioned primitive.)
|
|
252
|
+
const statusRaw = await (0, system_1.callWithServiceDiscovery)(broker, '$agent', '$agent.status', {}, {
|
|
245
253
|
nodeID: agentId,
|
|
246
254
|
timeout: AGENT_RECONCILE_RPC_TIMEOUT_MS,
|
|
247
255
|
});
|
|
@@ -3,8 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.AuthService = void 0;
|
|
4
4
|
const system_1 = require("@camstack/system");
|
|
5
5
|
class AuthService extends system_1.AuthManager {
|
|
6
|
-
constructor(config) {
|
|
7
|
-
super(config);
|
|
8
|
-
}
|
|
9
6
|
}
|
|
10
7
|
exports.AuthService = AuthService;
|
|
@@ -3,8 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ConfigService = void 0;
|
|
4
4
|
const system_1 = require("@camstack/system");
|
|
5
5
|
class ConfigService extends system_1.ConfigManager {
|
|
6
|
-
constructor(configPath) {
|
|
7
|
-
super(configPath);
|
|
8
|
-
}
|
|
9
6
|
}
|
|
10
7
|
exports.ConfigService = ConfigService;
|
|
@@ -3,8 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.FeatureService = void 0;
|
|
4
4
|
const system_1 = require("@camstack/system");
|
|
5
5
|
class FeatureService extends system_1.FeatureManager {
|
|
6
|
-
constructor(configService) {
|
|
7
|
-
super(configService);
|
|
8
|
-
}
|
|
9
6
|
}
|
|
10
7
|
exports.FeatureService = FeatureService;
|
|
@@ -266,18 +266,18 @@ class MoleculerService {
|
|
|
266
266
|
// double-apply is idempotent (same nodeId + same caps → no-op on the second call).
|
|
267
267
|
registry.onChildRegistered((child) => {
|
|
268
268
|
const hubNodeId = this.brokerSafe.nodeID;
|
|
269
|
-
const
|
|
270
|
-
const params = buildChildUdsManifest(
|
|
269
|
+
const childNodeId = `${hubNodeId}/${child.childId}`;
|
|
270
|
+
const params = buildChildUdsManifest(childNodeId, child.childId, child.caps);
|
|
271
271
|
this.onRegisterNode(params);
|
|
272
|
-
logger.info('UDS child registered — manifest applied', { meta: { nodeId } });
|
|
272
|
+
logger.info('UDS child registered — manifest applied', { meta: { nodeId: childNodeId } });
|
|
273
273
|
});
|
|
274
274
|
// E1: cleanup on child disconnect — same effect as `$node.disconnected`
|
|
275
275
|
// for hub-local children. The Moleculer path stays for AGENT nodes.
|
|
276
276
|
registry.onChildGone((childId) => {
|
|
277
277
|
const hubNodeId = this.brokerSafe.nodeID;
|
|
278
|
-
const
|
|
278
|
+
const childNodeId = `${hubNodeId}/${childId}`;
|
|
279
279
|
logger.info('UDS child gone — removing from registry', { meta: { childId } });
|
|
280
|
-
this.removeNodeFromRegistry(
|
|
280
|
+
this.removeNodeFromRegistry(childNodeId);
|
|
281
281
|
});
|
|
282
282
|
// B2: ingest UDS child logs into the hub's LoggingService so they appear
|
|
283
283
|
// in the LogManager / admin-UI log stream alongside broker-forwarded logs.
|
package/dist/main.js
CHANGED
|
@@ -62,6 +62,7 @@ const stream_probe_service_1 = require("./core/streaming/stream-probe.service");
|
|
|
62
62
|
const feature_service_1 = require("./core/feature/feature.service");
|
|
63
63
|
const agent_registry_service_1 = require("./core/agent/agent-registry.service");
|
|
64
64
|
const moleculer_service_1 = require("./core/moleculer/moleculer.service");
|
|
65
|
+
const model_distributor_js_1 = require("./api/model-distributor.js");
|
|
65
66
|
const addon_registry_service_1 = require("./core/addon/addon-registry.service");
|
|
66
67
|
const addon_package_service_1 = require("./core/addon/addon-package.service");
|
|
67
68
|
const repl_engine_service_1 = require("./core/repl/repl-engine.service");
|
|
@@ -293,6 +294,13 @@ async function bootstrap() {
|
|
|
293
294
|
// so Moleculer re-advertises the service list to the network)
|
|
294
295
|
const moleculer = app.get(moleculer_service_1.MoleculerService);
|
|
295
296
|
moleculer.registerLogReceiver();
|
|
297
|
+
// Hub-internal `model-distributor` provider — pushes a model format that is
|
|
298
|
+
// resident on the hub `/data/models` to an agent's `/data/models` (Model
|
|
299
|
+
// Studio P2). Registered in the CapabilityRegistry (not just the tRPC
|
|
300
|
+
// router) so forked addons (addon-model-studio) can reach it via `ctx.api`,
|
|
301
|
+
// not only external HTTP clients. Needs the broker + the deploy-stage
|
|
302
|
+
// registry, which an addon can't access.
|
|
303
|
+
capabilityRegistry.registerProvider('model-distributor', '$hub', (0, model_distributor_js_1.buildModelDistributorProvider)(moleculer, loggingService));
|
|
296
304
|
// ── Health routes (hub self + agent forwarding) ──────────────────
|
|
297
305
|
// Registered after app.init() so the AgentRegistryService is wired and
|
|
298
306
|
// the Moleculer broker is ready to forward `$agent.health` calls.
|
|
@@ -358,10 +366,10 @@ async function bootstrap() {
|
|
|
358
366
|
trpcOptions: {
|
|
359
367
|
router: appRouter,
|
|
360
368
|
createContext: ({ req }) => (0, trpc_context_1.createTrpcContext)(req, authService, addonRegistry),
|
|
361
|
-
onError: ({ path, error, }) => {
|
|
369
|
+
onError: ({ path: trpcPath, error, }) => {
|
|
362
370
|
const trpcLogger = app.get(logging_service_1.LoggingService).createLogger('tRPC');
|
|
363
371
|
trpcLogger.warn('tRPC error', {
|
|
364
|
-
meta: { code: error.code, path:
|
|
372
|
+
meta: { code: error.code, path: trpcPath ?? '?', message: error.message },
|
|
365
373
|
});
|
|
366
374
|
if (error.cause)
|
|
367
375
|
trpcLogger.warn('tRPC error cause', {
|
|
@@ -715,11 +723,11 @@ async function bootstrap() {
|
|
|
715
723
|
}
|
|
716
724
|
}
|
|
717
725
|
const qIdx = request.url.indexOf('?');
|
|
718
|
-
const
|
|
726
|
+
const queryString = qIdx >= 0 ? request.url.slice(qIdx) : '';
|
|
719
727
|
// Forward the FULL sub-path INCLUDING the prefix — the addon's facility
|
|
720
728
|
// multiplexes by prefix, so it strips the prefix itself. (`dpMatch.rest`
|
|
721
729
|
// is only used to pick the endpoint, not to rewrite the path.)
|
|
722
|
-
const upstreamPath = `/${subPath}${
|
|
730
|
+
const upstreamPath = `/${subPath}${queryString}`;
|
|
723
731
|
// Take over the socket — `proxyToUpstream` drives the raw response.
|
|
724
732
|
reply.hijack();
|
|
725
733
|
(0, system_1.proxyToUpstream)({
|
|
@@ -838,10 +846,10 @@ async function bootstrap() {
|
|
|
838
846
|
wss,
|
|
839
847
|
router: appRouter,
|
|
840
848
|
createContext: (opts) => (0, trpc_context_1.createWsTrpcContext)(opts, authService, addonRegistry),
|
|
841
|
-
onError: ({ path, error, }) => {
|
|
849
|
+
onError: ({ path: trpcPath, error, }) => {
|
|
842
850
|
const trpcLogger = app.get(logging_service_1.LoggingService).createLogger('tRPC:ws');
|
|
843
851
|
trpcLogger.warn('tRPC error', {
|
|
844
|
-
meta: { code: error.code, path:
|
|
852
|
+
meta: { code: error.code, path: trpcPath ?? '?', message: error.message },
|
|
845
853
|
});
|
|
846
854
|
if (error.cause)
|
|
847
855
|
trpcLogger.warn('tRPC error cause', {
|
|
@@ -899,8 +907,8 @@ async function bootstrap() {
|
|
|
899
907
|
// no static file serving' warn that left the SPA unserved until next
|
|
900
908
|
// restart.
|
|
901
909
|
try {
|
|
902
|
-
const
|
|
903
|
-
const capRegistry =
|
|
910
|
+
const bootAddonRegistry = app.get(addon_registry_service_1.AddonRegistryService);
|
|
911
|
+
const capRegistry = bootAddonRegistry.getCapabilityRegistry();
|
|
904
912
|
let adminUI = capRegistry?.getSingleton('admin-ui');
|
|
905
913
|
// CAMSTACK_SKIP_ADMIN_UI_WAIT — bypass the 60s poll. Used by the
|
|
906
914
|
// e2e harness, which doesn't need the SPA served and spawns hubs
|