@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.
@@ -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
- if (planned.targets.length === 0 &&
748
- planned.retired.length === 0 &&
749
- planned.unresolvable.length === 0) {
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 && plan.unresolvable.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
@@ -961,7 +961,6 @@ var require_dist = __commonJS({
961
961
  var import_node_url = require("url");
962
962
  var HOST_EXTERNAL_SPECIFIERS = [
963
963
  "@camstack/system",
964
- "@camstack/shm-ring",
965
964
  "@camstack/types",
966
965
  "@camstack/sdk",
967
966
  "zod",
@@ -189,7 +189,6 @@ exports.CLOSURE_PROVIDED_PACKAGES = [
189
189
  '@camstack/system',
190
190
  '@camstack/types',
191
191
  '@camstack/sdk',
192
- '@camstack/shm-ring',
193
192
  '@camstack/ui-library',
194
193
  ];
195
194
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.476",
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 --ignore=../../packages/shm-ring/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 --ignore=../../packages/shm-ring/dist src/launcher.ts",
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.348",
41
- "@camstack/addon-agent-ui": "1.2.187",
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.119",
43
+ "@camstack/addon-decoder-nodeav": "1.2.121",
44
44
  "@camstack/addon-notifiers": "1.2.128",
45
- "@camstack/addon-pipeline": "1.2.341",
46
- "@camstack/addon-pipeline-orchestrator": "1.2.294",
47
- "@camstack/addon-post-analysis": "1.2.340",
48
- "@camstack/sdk": "1.2.124",
49
- "@camstack/system": "1.2.370",
50
- "@camstack/types": "1.2.321",
51
- "@camstack/ui-library": "1.2.245",
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
- }