@camstack/server 1.2.476 → 1.2.478
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/agent/agent-service.js +27 -71
- package/dist/agent/agent-undeploy.js +255 -0
- package/dist/agent/main.js +13 -0
- package/dist/api/core/cap-providers.js +91 -3
- package/dist/api/trpc/generated-cap-routers.js +8 -199
- package/dist/api/trpc/trpc.router.js +1 -1
- package/dist/core/addon/addon-uninstall.js +259 -0
- package/dist/core/addon-bridge/addon-bridge.service.js +5 -5
- package/dist/core/agent/agent-addon-backfill.js +20 -18
- package/dist/core/agent/agent-registry.service.js +35 -7
- package/dist/server-root/index.js +0 -1
- package/dist/single-copy-cleanup.js +0 -1
- package/package.json +13 -13
- package/dist/api/bridge-addons.router.js +0 -109
|
@@ -281,6 +281,32 @@ class AgentRegistryService {
|
|
|
281
281
|
return;
|
|
282
282
|
await this.historyStore?.prunePackages(nodeId, [packageName]);
|
|
283
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* The operator removed a whole PACKAGE from a node (`addons.uninstallPackage`
|
|
286
|
+
* with a `nodeId`, or `everywhere`). Same intent as
|
|
287
|
+
* {@link noteAddonUndeployed}, keyed by package — which is what an uninstall
|
|
288
|
+
* names, and the only key that still resolves once the hub itself no longer
|
|
289
|
+
* has the package. Emits one `AddonUninstalled` per addon removed, like the
|
|
290
|
+
* stale-addon reconcile does.
|
|
291
|
+
*/
|
|
292
|
+
async notePackageUndeployed(nodeId, packageName, addonIds) {
|
|
293
|
+
if (AGENT_BOOTSTRAP_PACKAGES.has(packageName))
|
|
294
|
+
return;
|
|
295
|
+
await this.historyStore?.prunePackages(nodeId, [packageName]);
|
|
296
|
+
for (const addonId of addonIds) {
|
|
297
|
+
this.eventBus.emit({
|
|
298
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
299
|
+
timestamp: new Date(),
|
|
300
|
+
source: { type: 'core', id: 'agent-registry' },
|
|
301
|
+
category: types_1.EventCategory.AddonUninstalled,
|
|
302
|
+
data: { addonId, agentId: nodeId, packageName, reason: 'operator uninstall' },
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/** The packages a node last declared (its intended set). `[]` with no store. */
|
|
307
|
+
async getRecordedPackages(nodeId) {
|
|
308
|
+
return this.historyStore ? this.historyStore.getPackages(nodeId) : [];
|
|
309
|
+
}
|
|
284
310
|
/** Map an addon declaration id to the package that ships it, via the hub registry. */
|
|
285
311
|
resolvePackageForAddon(addonId) {
|
|
286
312
|
if (!this.addonRegistry)
|
|
@@ -744,9 +770,14 @@ class AgentRegistryService {
|
|
|
744
770
|
retired: this.backfillTracker.retiredFor(agentId),
|
|
745
771
|
hubVersions,
|
|
746
772
|
});
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
773
|
+
// Never a delivery, and said so: an addon the hub no longer installs stays
|
|
774
|
+
// off the node even though the node once declared it (the stale sweep above
|
|
775
|
+
// removes a copy that is still there).
|
|
776
|
+
if (planned.notOnHub.length > 0) {
|
|
777
|
+
console.log(`[agent-registry] Back-fill ${agentId}: NOT delivering ${planned.notOnHub.join(', ')} — ` +
|
|
778
|
+
`in the node's declared roster but not installed on the hub`);
|
|
779
|
+
}
|
|
780
|
+
if (planned.targets.length === 0 && planned.retired.length === 0) {
|
|
750
781
|
return;
|
|
751
782
|
}
|
|
752
783
|
// GATE 1 — the trigger allowlist. A pass that must not install says so and
|
|
@@ -773,7 +804,7 @@ class AgentRegistryService {
|
|
|
773
804
|
const plan = await this.confirmBackfillTargets(agentId, planned);
|
|
774
805
|
if (plan === null)
|
|
775
806
|
return;
|
|
776
|
-
if (plan.targets.length === 0 && plan.retired.length === 0
|
|
807
|
+
if (plan.targets.length === 0 && plan.retired.length === 0) {
|
|
777
808
|
return;
|
|
778
809
|
}
|
|
779
810
|
// Hold the delivery window open for the whole pass, so the reload this
|
|
@@ -813,9 +844,6 @@ class AgentRegistryService {
|
|
|
813
844
|
for (const name of outcome.skipped) {
|
|
814
845
|
console.error(`[agent-registry] Back-fill ${agentId}: ${name} is still missing and RETIRED — the node is degraded until an operator intervenes`);
|
|
815
846
|
}
|
|
816
|
-
for (const name of outcome.unresolvable) {
|
|
817
|
-
console.error(`[agent-registry] Back-fill ${agentId}: ${name} is missing but no version could be resolved (not installed on the hub, no version recorded) — not guessing "latest"`);
|
|
818
|
-
}
|
|
819
847
|
}
|
|
820
848
|
/**
|
|
821
849
|
* Re-read the node's roster and drop every target the second read reports
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.478",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"files": [
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "tsc -p tsconfig.build.json && tsup && node scripts/copy-closure-patches.mjs",
|
|
20
20
|
"postinstall": "node scripts/closure-postinstall.mjs",
|
|
21
|
-
"dev": "npx concurrently -n server,ui -c blue,magenta \"CAMSTACK_RESTART_TOUCH_FILE=src/launcher.ts tsx watch --env-file=.env --ignore=./camstack-data --ignore=../../packages/core/dist --ignore=../../packages/sdk/dist --ignore=../../packages/ui-library/dist
|
|
22
|
-
"serve": "CAMSTACK_RESTART_TOUCH_FILE=src/launcher.ts tsx watch --env-file=.env --ignore=./camstack-data --ignore=../../packages/core/dist --ignore=../../packages/sdk/dist --ignore=../../packages/ui-library/dist
|
|
21
|
+
"dev": "npx concurrently -n server,ui -c blue,magenta \"CAMSTACK_RESTART_TOUCH_FILE=src/launcher.ts tsx watch --env-file=.env --ignore=./camstack-data --ignore=../../packages/core/dist --ignore=../../packages/sdk/dist --ignore=../../packages/ui-library/dist src/launcher.ts\" \"cd ../../packages/addon-admin-ui && npx vite --port 3001\"",
|
|
22
|
+
"serve": "CAMSTACK_RESTART_TOUCH_FILE=src/launcher.ts tsx watch --env-file=.env --ignore=./camstack-data --ignore=../../packages/core/dist --ignore=../../packages/sdk/dist --ignore=../../packages/ui-library/dist src/launcher.ts",
|
|
23
23
|
"serve:once": "tsx --env-file=.env src/launcher.ts",
|
|
24
24
|
"start": "node --env-file=.env dist/launcher.js",
|
|
25
25
|
"typecheck": "tsc --noEmit -p tsconfig.spec.json",
|
|
@@ -37,18 +37,18 @@
|
|
|
37
37
|
]
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
41
|
-
"@camstack/addon-agent-ui": "1.2.
|
|
40
|
+
"@camstack/addon-admin-ui": "1.2.351",
|
|
41
|
+
"@camstack/addon-agent-ui": "1.2.190",
|
|
42
42
|
"@camstack/addon-auth": "1.2.130",
|
|
43
|
-
"@camstack/addon-decoder-nodeav": "1.2.
|
|
43
|
+
"@camstack/addon-decoder-nodeav": "1.2.121",
|
|
44
44
|
"@camstack/addon-notifiers": "1.2.128",
|
|
45
|
-
"@camstack/addon-pipeline": "1.2.
|
|
46
|
-
"@camstack/addon-pipeline-orchestrator": "1.2.
|
|
47
|
-
"@camstack/addon-post-analysis": "1.2.
|
|
48
|
-
"@camstack/sdk": "1.2.
|
|
49
|
-
"@camstack/system": "1.2.
|
|
50
|
-
"@camstack/types": "1.2.
|
|
51
|
-
"@camstack/ui-library": "1.2.
|
|
45
|
+
"@camstack/addon-pipeline": "1.2.344",
|
|
46
|
+
"@camstack/addon-pipeline-orchestrator": "1.2.297",
|
|
47
|
+
"@camstack/addon-post-analysis": "1.2.342",
|
|
48
|
+
"@camstack/sdk": "1.2.126",
|
|
49
|
+
"@camstack/system": "1.2.373",
|
|
50
|
+
"@camstack/types": "1.2.324",
|
|
51
|
+
"@camstack/ui-library": "1.2.248",
|
|
52
52
|
"@fastify/compress": "^9.0.0",
|
|
53
53
|
"@fastify/cookie": "^11.0.2",
|
|
54
54
|
"@fastify/cors": "^11.2.0",
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createBridgeAddonsRouter = createBridgeAddonsRouter;
|
|
4
|
-
const zod_1 = require("zod");
|
|
5
|
-
const server_1 = require("@trpc/server");
|
|
6
|
-
const trpc_middleware_1 = require("./trpc/trpc.middleware");
|
|
7
|
-
function createBridgeAddonsRouter(bridge, addonSearch, addonRegistry, toastService) {
|
|
8
|
-
return (0, trpc_middleware_1.trpcRouter)({
|
|
9
|
-
/** List all addon packages installed in the addons directory */
|
|
10
|
-
listPackages: trpc_middleware_1.protectedProcedure.query(() => {
|
|
11
|
-
const installer = bridge.getInstaller();
|
|
12
|
-
if (!installer)
|
|
13
|
-
return [];
|
|
14
|
-
return installer.listInstalled();
|
|
15
|
-
}),
|
|
16
|
-
/** List all available addons across all loaded packages */
|
|
17
|
-
listAddons: trpc_middleware_1.protectedProcedure.query(() => {
|
|
18
|
-
return bridge.listAvailableAddons().map((id) => {
|
|
19
|
-
const addon = bridge.getLoader().getAddon(id);
|
|
20
|
-
return {
|
|
21
|
-
id,
|
|
22
|
-
packageName: addon?.packageName ?? 'unknown',
|
|
23
|
-
slot: addon?.declaration.slot ?? null,
|
|
24
|
-
};
|
|
25
|
-
});
|
|
26
|
-
}),
|
|
27
|
-
/** Install a community addon package from npm */
|
|
28
|
-
installPackage: trpc_middleware_1.adminProcedure
|
|
29
|
-
.input(zod_1.z.object({ packageName: zod_1.z.string(), version: zod_1.z.string().optional() }))
|
|
30
|
-
.mutation(async ({ input }) => {
|
|
31
|
-
const installer = bridge.getInstaller();
|
|
32
|
-
if (!installer) {
|
|
33
|
-
throw new server_1.TRPCError({
|
|
34
|
-
code: 'PRECONDITION_FAILED',
|
|
35
|
-
message: 'Addon installer not available — bridge may have failed to initialize',
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
await installer.install(input.packageName, input.version);
|
|
39
|
-
await bridge.reloadPackages();
|
|
40
|
-
const result = addonRegistry
|
|
41
|
-
? await addonRegistry.loadNewAddons()
|
|
42
|
-
: { loaded: [], failed: [] };
|
|
43
|
-
toastService?.broadcast({
|
|
44
|
-
title: 'Addon Installed',
|
|
45
|
-
message: `${input.packageName} installed successfully${result.loaded.length ? ` (${result.loaded.join(', ')})` : ''}`,
|
|
46
|
-
severity: result.failed.length ? 'warning' : 'info',
|
|
47
|
-
});
|
|
48
|
-
return { success: true, loaded: result.loaded, failed: result.failed };
|
|
49
|
-
}),
|
|
50
|
-
/** Uninstall a community addon package */
|
|
51
|
-
uninstallPackage: trpc_middleware_1.adminProcedure
|
|
52
|
-
.input(zod_1.z.object({ packageName: zod_1.z.string() }))
|
|
53
|
-
.mutation(async ({ input }) => {
|
|
54
|
-
// Server-side guard: prevent uninstalling required packages.
|
|
55
|
-
// After Phase D bundle merge, the pipeline-related packages
|
|
56
|
-
// (stream-broker, detection-pipeline, motion-wasm, decoders,
|
|
57
|
-
// audio) all live in @camstack/addon-pipeline.
|
|
58
|
-
const REQUIRED = new Set([
|
|
59
|
-
'@camstack/system',
|
|
60
|
-
'@camstack/addon-pipeline',
|
|
61
|
-
'@camstack/addon-pipeline-orchestrator',
|
|
62
|
-
'@camstack/addon-post-analysis',
|
|
63
|
-
'@camstack/addon-admin-ui',
|
|
64
|
-
]);
|
|
65
|
-
if (REQUIRED.has(input.packageName)) {
|
|
66
|
-
throw new server_1.TRPCError({
|
|
67
|
-
code: 'FORBIDDEN',
|
|
68
|
-
message: `Package ${input.packageName} is required and cannot be uninstalled`,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
const installer = bridge.getInstaller();
|
|
72
|
-
if (!installer) {
|
|
73
|
-
throw new server_1.TRPCError({
|
|
74
|
-
code: 'PRECONDITION_FAILED',
|
|
75
|
-
message: 'Addon installer not available — bridge may have failed to initialize',
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
await installer.uninstall(input.packageName);
|
|
79
|
-
await bridge.reloadPackages();
|
|
80
|
-
if (addonRegistry)
|
|
81
|
-
await addonRegistry.loadNewAddons();
|
|
82
|
-
toastService?.broadcast({
|
|
83
|
-
title: 'Addon Uninstalled',
|
|
84
|
-
message: `${input.packageName} has been removed`,
|
|
85
|
-
severity: 'info',
|
|
86
|
-
});
|
|
87
|
-
return { success: true };
|
|
88
|
-
}),
|
|
89
|
-
/** Force reload all addon packages (re-scan directories, re-import modules) */
|
|
90
|
-
reloadPackages: trpc_middleware_1.adminProcedure.mutation(async () => {
|
|
91
|
-
await bridge.reloadPackages();
|
|
92
|
-
return { success: true, message: 'Addon packages reloaded' };
|
|
93
|
-
}),
|
|
94
|
-
/** Search npm for available CamStack addons */
|
|
95
|
-
searchAvailable: trpc_middleware_1.protectedProcedure
|
|
96
|
-
.input(zod_1.z.object({ query: zod_1.z.string().optional() }).optional())
|
|
97
|
-
.query(async ({ input }) => {
|
|
98
|
-
const results = await addonSearch.searchAddons(input?.query);
|
|
99
|
-
// Enrich with install status from locally installed packages
|
|
100
|
-
const installed = bridge.getInstaller()?.listInstalled() ?? [];
|
|
101
|
-
const installedMap = new Map(installed.map((p) => [p.name, p.version]));
|
|
102
|
-
return results.map((r) => ({
|
|
103
|
-
...r,
|
|
104
|
-
installed: installedMap.has(r.name),
|
|
105
|
-
installedVersion: installedMap.get(r.name),
|
|
106
|
-
}));
|
|
107
|
-
}),
|
|
108
|
-
});
|
|
109
|
-
}
|