@camstack/server 1.2.75 → 1.2.76

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.
@@ -72,13 +72,18 @@ function seedBuiltinsFromClosure(addonsDir, log = console.log, resolveClosurePkg
72
72
  log(`[Agent] builtins seed: closure copy at ${closureRoot} has no dist — cannot self-heal`);
73
73
  return 'unavailable';
74
74
  }
75
- fs.mkdirSync(target, { recursive: true });
76
- fs.copyFileSync(closurePkgJson, path.join(target, 'package.json'));
77
- fs.cpSync(closureDist, path.join(target, 'dist'), {
78
- recursive: true,
79
- filter: (src) => !src.split(path.sep).includes('node_modules'),
80
- });
75
+ // The WHOLE package, node_modules included. A deps-free copy was tried
76
+ // first and failed live (2026-08-08): the builtins' dist requires native
77
+ // deps (better-sqlite3) that resolve relative to the COPY, so the addon
78
+ // scan logged "Failed to scan" and the guard aborted with "no addon
79
+ // under /data/addons" — while the log right above it said the seed had
80
+ // run. Nested `node_modules` are safe: the scan reads one level of
81
+ // `addonsDir/@scope/*`, never inside a package. (The double-registration
82
+ // the deps-free copy was guarding against was actually the per-capability
83
+ // init loop — fixed by `planInfraBoot` — not nested discovery.)
84
+ fs.mkdirSync(path.dirname(target), { recursive: true });
85
+ fs.cpSync(closureRoot, target, { recursive: true });
81
86
  log(`[Agent] builtins seed: @camstack/system was missing under ${addonsDir} — ` +
82
- `seeded package.json + dist from the running closure (${closureRoot})`);
87
+ `seeded the full package from the running closure (${closureRoot})`);
83
88
  return 'seeded';
84
89
  }
@@ -8258,6 +8258,24 @@ function createCapRouter_streamBroker(getProvider, createRemoteProxy) {
8258
8258
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
8259
8259
  return p.renderPreBufferClip(methodInput);
8260
8260
  }),
8261
+ produceEventMedia: trpc_middleware_js_1.adminProcedure
8262
+ .input(types_103.streamBrokerCapability.methods.produceEventMedia.input.loose())
8263
+ .output(types_103.streamBrokerCapability.methods.produceEventMedia.output)
8264
+ .mutation(async ({ input, ctx }) => {
8265
+ const { nodeId, ...methodInput } = input;
8266
+ const p = resolveProvider('stream-broker', nodeId, () => getProvider(ctx), createRemoteProxy);
8267
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
8268
+ return p.produceEventMedia(methodInput);
8269
+ }),
8270
+ fetchEventMedia: trpc_middleware_js_1.adminProcedure
8271
+ .input(types_103.streamBrokerCapability.methods.fetchEventMedia.input.loose())
8272
+ .output(types_103.streamBrokerCapability.methods.fetchEventMedia.output)
8273
+ .mutation(async ({ input, ctx }) => {
8274
+ const { nodeId, ...methodInput } = input;
8275
+ const p = resolveProvider('stream-broker', nodeId, () => getProvider(ctx), createRemoteProxy);
8276
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
8277
+ return p.fetchEventMedia(methodInput);
8278
+ }),
8261
8279
  listAllCameraStreams: trpc_middleware_js_1.protectedProcedure
8262
8280
  .input(zod_1.z.object({ nodeId: zod_1.z.string().optional() }).optional())
8263
8281
  .output(types_103.streamBrokerCapability.methods.listAllCameraStreams.output)
@@ -3081,8 +3081,29 @@ class AddonRegistryService {
3081
3081
  return;
3082
3082
  let catalog;
3083
3083
  try {
3084
- // Cache-bust so a hot-updated bundle is re-read instead of served
3085
- // from Node's ESM module cache.
3084
+ // ── The query string does NOT bust a CommonJS bundle ──────────────
3085
+ //
3086
+ // Every addon here builds to CJS, and Node's ESM loader serves a CJS
3087
+ // module out of the REQUIRE cache, which is keyed by the resolved
3088
+ // filename with the query stripped. So `?t=<now>` below busts nothing
3089
+ // for them: this function re-registered the catalog captured at hub boot
3090
+ // on every restart, forever.
3091
+ //
3092
+ // The consequence was invisible and total: an EXISTING action kept
3093
+ // working, so nothing looked broken, while a NEWLY added one could never
3094
+ // become reachable without restarting the whole hub process — which
3095
+ // quietly falsifies the reason bridge actions exist ("no codegen, no
3096
+ // republish, no train"). Found 2026-08-08, deploying `nc.injectTestEvent`:
3097
+ // the deploy succeeded, the restart logged "custom actions registered",
3098
+ // and the action 404'd.
3099
+ //
3100
+ // Dropping the addon's own modules from the require cache is what
3101
+ // actually re-reads the bundle. Scoped to the addon directory so no other
3102
+ // addon's (or the hub's own) modules are evicted, and best-effort: a
3103
+ // cache that cannot be walked leaves the previous behaviour rather than
3104
+ // failing the registration.
3105
+ purgeRequireCacheUnder(path.dirname(entryPath));
3106
+ // Kept for a genuinely ESM addon entry, where it IS the mechanism.
3086
3107
  const cacheBustedUrl = `${(0, node_url_1.pathToFileURL)(entryPath).href}?t=${Date.now()}`;
3087
3108
  // A plain `await import()` here is downleveled by tsc (the backend builds
3088
3109
  // with `module: CommonJS`) into a `require()`-based shim. `require()` then
@@ -3136,3 +3157,32 @@ class AddonRegistryService {
3136
3157
  }
3137
3158
  }
3138
3159
  exports.AddonRegistryService = AddonRegistryService;
3160
+ /**
3161
+ * Drop every `require`-cached module that lives under `dir`.
3162
+ *
3163
+ * Node's ESM loader serves a CommonJS module from the require cache, keyed by
3164
+ * the resolved filename — the `?t=` query an ESM import uses to force a re-read
3165
+ * is stripped before that lookup and therefore does nothing. Since every addon
3166
+ * bundle here is CJS, evicting the addon's own entries is what makes a
3167
+ * hot-updated bundle actually load.
3168
+ *
3169
+ * Scoped to one directory on purpose: a blanket cache clear would evict the
3170
+ * hub's own modules and every other addon's, turning a catalog refresh into a
3171
+ * process-wide reload. Best-effort — a cache that cannot be walked leaves the
3172
+ * previous (stale) behaviour rather than failing the caller.
3173
+ */
3174
+ function purgeRequireCacheUnder(dir) {
3175
+ try {
3176
+ const cache = require.cache;
3177
+ if (cache === undefined)
3178
+ return;
3179
+ const prefix = dir.endsWith(path.sep) ? dir : `${dir}${path.sep}`;
3180
+ for (const key of Object.keys(cache)) {
3181
+ if (key.startsWith(prefix))
3182
+ delete cache[key];
3183
+ }
3184
+ }
3185
+ catch {
3186
+ // Non-CJS host, or a frozen cache. The import below still runs.
3187
+ }
3188
+ }
package/dist/launcher.js CHANGED
@@ -278,7 +278,30 @@ async function launch() {
278
278
  // @camstack/system is imported DYNAMICALLY here — AFTER the active framework
279
279
  // dir + NODE_PATH are resolved above — so the correct framework copy is what
280
280
  // gets loaded. Never import it at module top.
281
- const { AddonInstaller, bootstrapSchema, detectWorkspacePackagesDir } = await Promise.resolve().then(() => __importStar(require('@camstack/system')));
281
+ const { AddonInstaller, bootstrapSchema, detectWorkspacePackagesDir, quarantineAddonResidue } = await Promise.resolve().then(() => __importStar(require('@camstack/system')));
282
+ // Residue quarantine — FIRST thing that touches the addon root, before the
283
+ // bootstrap seed writes into it and long before any loader scans it.
284
+ //
285
+ // A directory under `addons/@camstack` whose name is not the package it
286
+ // declares is not an archive: the loader, the agent's boot scan and the
287
+ // install manifest all key on the `package.json` inside, so a rename
288
+ // INSTALLS. On 2026-08-07/08 that shipped `@camstack/system 1.2.3` as the
289
+ // reported version on a node running a 1.2.61 closure, and kept a 17-day-old
290
+ // `better_sqlite3.node` mapped into the live process. Moved, never deleted —
291
+ // the cleanup that deleted one of these gutted an agent the same evening.
292
+ //
293
+ // Guarded with a typeof check for the same reason `reconcileManifest` is: a
294
+ // system-only framework update can swap in a build that predates this.
295
+ if (typeof quarantineAddonResidue === 'function') {
296
+ const residue = quarantineAddonResidue(addonsDir, (msg) => console.log(msg));
297
+ if (residue.quarantined.length > 0 || residue.failed.length > 0) {
298
+ console.log(`[launcher] Addon residue — quarantined ${residue.quarantined.length}, ` +
299
+ `failed ${residue.failed.length}`);
300
+ }
301
+ }
302
+ else {
303
+ console.warn('[launcher] quarantineAddonResidue unavailable — skipping residue quarantine');
304
+ }
282
305
  // Install source resolution:
283
306
  // 1. CAMSTACK_BUNDLED_ADDONS_DIR — set by Electron-packaged builds
284
307
  // to <resourcesPath>/addons. Pre-built addons ship with the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.75",
3
+ "version": "1.2.76",
4
4
  "private": false,
5
5
  "files": [
6
6
  "dist",
@@ -33,19 +33,19 @@
33
33
  ]
34
34
  },
35
35
  "dependencies": {
36
- "@camstack/addon-admin-ui": "1.2.35",
36
+ "@camstack/addon-admin-ui": "1.2.36",
37
37
  "@camstack/addon-agent-ui": "1.2.10",
38
38
  "@camstack/addon-auth": "1.2.11",
39
39
  "@camstack/addon-decoder-nodeav": "1.2.9",
40
40
  "@camstack/addon-notifiers": "1.2.13",
41
- "@camstack/addon-pipeline": "1.2.46",
42
- "@camstack/addon-pipeline-orchestrator": "1.2.29",
43
- "@camstack/addon-post-analysis": "1.2.51",
41
+ "@camstack/addon-pipeline": "1.2.47",
42
+ "@camstack/addon-pipeline-orchestrator": "1.2.30",
43
+ "@camstack/addon-post-analysis": "1.2.52",
44
44
  "@camstack/sdk": "1.2.10",
45
45
  "@camstack/shm-ring": "1.1.9",
46
- "@camstack/system": "1.2.61",
47
- "@camstack/types": "1.2.45",
48
- "@camstack/ui-library": "1.2.33",
46
+ "@camstack/system": "1.2.62",
47
+ "@camstack/types": "1.2.46",
48
+ "@camstack/ui-library": "1.2.34",
49
49
  "@fastify/compress": "^9.0.0",
50
50
  "@fastify/cookie": "^11.0.2",
51
51
  "@fastify/cors": "^11.2.0",