@camstack/system 1.2.61 → 1.2.63

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.
Files changed (86) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/core-blocks/block-package.d.ts +9 -27
  14. package/dist/builtins/core-blocks/block-supervisor.d.ts +15 -0
  15. package/dist/builtins/core-blocks/core-blocks.addon.js +91 -24
  16. package/dist/builtins/core-blocks/core-blocks.addon.mjs +91 -25
  17. package/dist/builtins/device-manager/device-aggregation.d.ts +49 -2
  18. package/dist/builtins/device-manager/device-bindings-store.d.ts +28 -5
  19. package/dist/builtins/device-manager/device-linked-devices.d.ts +2 -2
  20. package/dist/builtins/device-manager/device-manager.addon.d.ts +3 -45
  21. package/dist/builtins/device-manager/device-manager.addon.js +182 -888
  22. package/dist/builtins/device-manager/device-manager.addon.mjs +182 -888
  23. package/dist/builtins/device-manager/device-meta-actions.d.ts +0 -8
  24. package/dist/builtins/device-manager/device-meta-store.d.ts +1 -28
  25. package/dist/builtins/device-manager/device-meta-types.d.ts +2 -6
  26. package/dist/builtins/device-manager/device-projection.d.ts +1 -2
  27. package/dist/builtins/device-manager/device-provider-context.d.ts +2 -13
  28. package/dist/builtins/device-manager/device-queries.d.ts +1 -1
  29. package/dist/builtins/device-manager/device-state-mirror.d.ts +8 -49
  30. package/dist/builtins/device-manager/device-sync-resolvers.d.ts +0 -11
  31. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  32. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  33. package/dist/builtins/hub-forwarder/index.js +1 -1
  34. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  35. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  36. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  37. package/dist/builtins/local-auth/homeassistant-integration.d.ts +7 -0
  38. package/dist/builtins/local-auth/index.d.ts +1 -0
  39. package/dist/builtins/local-auth/index.js +1 -0
  40. package/dist/builtins/local-auth/index.mjs +2 -2
  41. package/dist/builtins/local-auth/local-auth.addon.js +561 -497
  42. package/dist/builtins/local-auth/local-auth.addon.mjs +580 -517
  43. package/dist/builtins/local-auth/oauth-grants.d.ts +11 -0
  44. package/dist/builtins/local-auth/oauth-session-manager.d.ts +1 -1
  45. package/dist/builtins/local-network/local-network.addon.js +1 -1
  46. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  47. package/dist/builtins/loki-logging/index.js +1 -1
  48. package/dist/builtins/loki-logging/index.mjs +1 -1
  49. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  50. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  51. package/dist/builtins/platform-probe/index.js +1 -1
  52. package/dist/builtins/platform-probe/index.mjs +1 -1
  53. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  54. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  55. package/dist/builtins/snapshot/index.js +319 -20
  56. package/dist/builtins/snapshot/index.mjs +319 -20
  57. package/dist/builtins/snapshot/snapshot-keep-warm.d.ts +86 -0
  58. package/dist/builtins/snapshot/snapshot-media-handler.d.ts +48 -1
  59. package/dist/builtins/snapshot/snapshot.addon.d.ts +65 -1
  60. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  61. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  62. package/dist/builtins/sqlite-storage/retired-settings-keys.d.ts +15 -0
  63. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +45 -5
  64. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +45 -5
  65. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  66. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  67. package/dist/builtins/system-config/system-config.addon.js +1 -1
  68. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  69. package/dist/builtins/winston-logging/index.js +1 -1
  70. package/dist/builtins/winston-logging/index.mjs +1 -1
  71. package/dist/{dist-L6QPJWsr.mjs → dist-BJ2w3uoN.mjs} +1307 -1313
  72. package/dist/{dist-BN681PgO.js → dist-LDrOFrd-.js} +1255 -1273
  73. package/dist/index.d.ts +1 -1
  74. package/dist/index.js +209 -5
  75. package/dist/index.mjs +206 -7
  76. package/dist/kernel/addon-residue.d.ts +49 -0
  77. package/dist/kernel/index.d.ts +2 -0
  78. package/dist/kernel/moleculer/device-cap-proxy.d.ts +2 -2
  79. package/dist/{manifest-python-deps-CX5tGRqf.mjs → manifest-python-deps-CopL1_5C.mjs} +20 -14
  80. package/dist/{manifest-python-deps-d4acYXVV.js → manifest-python-deps-DNfmBu20.js} +20 -14
  81. package/package.json +5 -1
  82. package/dist/builtins/device-manager/device-link-cycle.d.ts +0 -18
  83. package/dist/builtins/device-manager/device-link-overlay.d.ts +0 -33
  84. package/dist/builtins/device-manager/device-link-resolver.d.ts +0 -44
  85. package/dist/main-CVLq8IzA.js +0 -2356
  86. package/dist/main-CzH19GkU.mjs +0 -2353
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_manifest_python_deps = require("./manifest-python-deps-d4acYXVV.js");
2
+ const require_manifest_python_deps = require("./manifest-python-deps-DNfmBu20.js");
3
3
  const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
4
4
  let node_v8 = require("node:v8");
5
5
  node_v8 = require_chunk.__toESM(node_v8);
@@ -1,4 +1,4 @@
1
- import { Ct as resolveAddonClass, I as createUdsLoggerWithControl, L as LocalChildClient, at as setWorkerNativeCapsChangeListener, bt as installManifestNativeDeps, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps } from "./manifest-python-deps-CX5tGRqf.mjs";
1
+ import { Ct as resolveAddonClass, I as createUdsLoggerWithControl, L as LocalChildClient, at as setWorkerNativeCapsChangeListener, bt as installManifestNativeDeps, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps } from "./manifest-python-deps-CopL1_5C.mjs";
2
2
  import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
3
3
  import { register } from "node:module";
4
4
  import * as v8 from "node:v8";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BN681PgO.js");
6
+ const require_dist = require("../../dist-LDrOFrd-.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_fs = require("node:fs");
@@ -1,4 +1,4 @@
1
- import { ft as errMsg, m as addonPagesCapability, pt as BaseAddon, zt as EventCategory } from "../../dist-L6QPJWsr.mjs";
1
+ import { Lt as EventCategory, dt as BaseAddon, h as addonPagesCapability, ut as errMsg } from "../../dist-BJ2w3uoN.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import * as fs from "node:fs";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BN681PgO.js");
6
+ const require_dist = require("../../dist-LDrOFrd-.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_fs = require("node:fs");
@@ -1,4 +1,4 @@
1
- import { ft as errMsg, g as addonWidgetsCapability, pt as BaseAddon, zt as EventCategory } from "../../dist-L6QPJWsr.mjs";
1
+ import { Lt as EventCategory, _ as addonWidgetsCapability, dt as BaseAddon, ut as errMsg } from "../../dist-BJ2w3uoN.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import * as fs from "node:fs";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BN681PgO.js");
6
+ const require_dist = require("../../dist-LDrOFrd-.js");
7
7
  //#region src/builtins/alerts/prune-policy.ts
8
8
  /**
9
9
  * Ids older than `cutoffMs`, oldest first.
@@ -1,4 +1,4 @@
1
- import { Et as createEvent, _ as alertsCapability, ft as errMsg, n as AlertSchema, pt as BaseAddon, zt as EventCategory } from "../../dist-L6QPJWsr.mjs";
1
+ import { Lt as EventCategory, dt as BaseAddon, n as AlertSchema, ut as errMsg, v as alertsCapability, wt as createEvent } from "../../dist-BJ2w3uoN.mjs";
2
2
  //#region src/builtins/alerts/prune-policy.ts
3
3
  /**
4
4
  * Ids older than `cutoffMs`, oldest first.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BN681PgO.js");
6
+ const require_dist = require("../../dist-LDrOFrd-.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs = require("node:fs");
9
9
  let node_fs$1 = require_chunk.__toESM(node_fs, 1);
@@ -1,4 +1,4 @@
1
- import { b as backupCapability, pt as BaseAddon, zt as EventCategory } from "../../dist-L6QPJWsr.mjs";
1
+ import { Lt as EventCategory, b as backupCapability, dt as BaseAddon } from "../../dist-BJ2w3uoN.mjs";
2
2
  import { randomBytes, randomUUID } from "node:crypto";
3
3
  import * as fs from "node:fs";
4
4
  import Kt from "node:fs";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BN681PgO.js");
6
+ const require_dist = require("../../dist-LDrOFrd-.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/console-logging/console-destination.ts
9
9
  var LEVEL_RANK = {
@@ -1,4 +1,4 @@
1
- import { G as logDestinationCapability, pt as BaseAddon } from "../../dist-L6QPJWsr.mjs";
1
+ import { G as logDestinationCapability, dt as BaseAddon } from "../../dist-BJ2w3uoN.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/console-logging/console-destination.ts
4
4
  var LEVEL_RANK = {
@@ -1,34 +1,16 @@
1
+ import { coreBlockAddonId, coreBlockIdFromAddonId } from '@camstack/types';
1
2
  /**
2
- * Turning a stored block into something the addon runner can fork.
3
+ * The runner id lives on the CAPABILITY, not here.
3
4
  *
4
- * The runner already knows how to do everything a block needs — fork a
5
- * process, build the full UDS `ctx`, redirect host-provided imports, and hand
6
- * the whole thing to `CrashSupervisor` (D6). What it does NOT know how to do is
7
- * load code out of a database row: `addon-runner` reads a package.json off
8
- * disk. So a block is MATERIALISED as a minimal addon package and spawned like
9
- * any other addon. No second runner, no second supervisor, no `node:vm`.
10
- *
11
- * Two files are generated per block:
12
- *
13
- * `dist/block.js` — the author's TypeScript, transpiled.
14
- * `dist/index.js` — a generated wrapper that adapts the author's contract
15
- * (`export default (ctx) => disposer`) to `ICamstackAddon`.
16
- *
17
- * The wrapper exists so the author never writes `super({})`, which is the trap
18
- * that killed `CoreBlocksAddon` in production while the build stayed green and
19
- * the topology reported it running.
5
+ * It stopped being an implementation detail the day the operator surface
6
+ * started reading a block's logs by asking `logs.query` for
7
+ * `addonId = core-block-<id>` with no cap of its own. A second copy of the
8
+ * string would drift into a logs pane that is silently always empty, so these
9
+ * are re-exports under the names this package already used.
20
10
  */
21
- /** Prefix for the generated addon id AND the runner id — one addon, one
22
- * process, so the two are the same string (D2). Grep-able on purpose: an
23
- * operator reading `$process.list` must be able to tell a block runner from
24
- * an addon runner at a glance. */
25
11
  export declare const BLOCK_ADDON_PREFIX = "core-block-";
26
- /** The generated addon/runner id for a block. */
27
- export declare function blockAddonId(blockId: string): string;
28
- /** The block id behind a generated addon/runner id, or null when the id
29
- * belongs to something else. The inverse of {@link blockAddonId} — used to
30
- * read the live process list back into block terms. */
31
- export declare function blockIdFromAddonId(addonId: string): string | null;
12
+ export declare const blockAddonId: typeof coreBlockAddonId;
13
+ export declare const blockIdFromAddonId: typeof coreBlockIdFromAddonId;
32
14
  export interface MaterializeBlockInput {
33
15
  /** Root under which every block package lives — one directory per block. */
34
16
  readonly root: string;
@@ -65,6 +65,21 @@ export declare class BlockSupervisor {
65
65
  * next one, and the reason belongs in the log, not in a rejected promise
66
66
  * nobody awaits. */
67
67
  converge(): Promise<void>;
68
+ /**
69
+ * Stop one block's runner and let the pass that follows bring it back.
70
+ *
71
+ * The same path a code change already takes (see `stale` in `reconcile`),
72
+ * exposed on purpose. It writes NO state of its own: forgetting the
73
+ * materialised `updatedAt` is what makes the next pass treat the block as
74
+ * needing a launch, and that pass is still the authority — so a restart that
75
+ * raced a converge ends with one runner, not two.
76
+ *
77
+ * Queued on the same chain as `converge()`: a restart that interleaved with a
78
+ * timer tick could stop a runner the tick had just decided was healthy and
79
+ * then never re-launch it.
80
+ */
81
+ restart(blockId: string): Promise<void>;
82
+ private runRestart;
68
83
  private runPass;
69
84
  private reconcile;
70
85
  /** Compile, write the package, spawn. Each failure is reported ON the block
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-BN681PgO.js");
3
+ const require_dist = require("../../dist-LDrOFrd-.js");
4
4
  let node_crypto = require("node:crypto");
5
5
  let node_path = require("node:path");
6
6
  node_path = require_chunk.__toESM(node_path);
@@ -27,23 +27,18 @@ node_fs_promises = require_chunk.__toESM(node_fs_promises);
27
27
  * that killed `CoreBlocksAddon` in production while the build stayed green and
28
28
  * the topology reported it running.
29
29
  */
30
- /** Prefix for the generated addon id AND the runner id — one addon, one
31
- * process, so the two are the same string (D2). Grep-able on purpose: an
32
- * operator reading `$process.list` must be able to tell a block runner from
33
- * an addon runner at a glance. */
34
- var BLOCK_ADDON_PREFIX = "core-block-";
35
- /** The generated addon/runner id for a block. */
36
- function blockAddonId(blockId) {
37
- return `${BLOCK_ADDON_PREFIX}${blockId}`;
38
- }
39
- /** The block id behind a generated addon/runner id, or null when the id
40
- * belongs to something else. The inverse of {@link blockAddonId} — used to
41
- * read the live process list back into block terms. */
42
- function blockIdFromAddonId(addonId) {
43
- if (!addonId.startsWith("core-block-")) return null;
44
- const rest = addonId.slice(11);
45
- return rest.length > 0 ? rest : null;
46
- }
30
+ /**
31
+ * The runner id lives on the CAPABILITY, not here.
32
+ *
33
+ * It stopped being an implementation detail the day the operator surface
34
+ * started reading a block's logs by asking `logs.query` for
35
+ * `addonId = core-block-<id>` with no cap of its own. A second copy of the
36
+ * string would drift into a logs pane that is silently always empty, so these
37
+ * are re-exports under the names this package already used.
38
+ */
39
+ var BLOCK_ADDON_PREFIX = require_dist.CORE_BLOCK_ADDON_PREFIX;
40
+ var blockAddonId = require_dist.coreBlockAddonId;
41
+ var blockIdFromAddonId = require_dist.coreBlockIdFromAddonId;
47
42
  /**
48
43
  * The wrapper module, generated verbatim.
49
44
  *
@@ -207,6 +202,36 @@ var BlockSupervisor = class {
207
202
  this.inFlight = this.inFlight.then(() => this.runPass(), () => this.runPass());
208
203
  return this.inFlight;
209
204
  }
205
+ /**
206
+ * Stop one block's runner and let the pass that follows bring it back.
207
+ *
208
+ * The same path a code change already takes (see `stale` in `reconcile`),
209
+ * exposed on purpose. It writes NO state of its own: forgetting the
210
+ * materialised `updatedAt` is what makes the next pass treat the block as
211
+ * needing a launch, and that pass is still the authority — so a restart that
212
+ * raced a converge ends with one runner, not two.
213
+ *
214
+ * Queued on the same chain as `converge()`: a restart that interleaved with a
215
+ * timer tick could stop a runner the tick had just decided was healthy and
216
+ * then never re-launch it.
217
+ */
218
+ restart(blockId) {
219
+ this.inFlight = this.inFlight.then(() => this.runRestart(blockId), () => this.runRestart(blockId));
220
+ return this.inFlight;
221
+ }
222
+ async runRestart(blockId) {
223
+ try {
224
+ await this.deps.host.stop(blockAddonId(blockId));
225
+ this.deps.logger.info("core block runner stopped for restart", { meta: { blockId } });
226
+ } catch (err) {
227
+ this.deps.logger.warn("could not stop the core block runner before restart", { meta: {
228
+ blockId,
229
+ error: err instanceof Error ? err.message : String(err)
230
+ } });
231
+ }
232
+ this.materialized.delete(blockId);
233
+ await this.runPass();
234
+ }
210
235
  async runPass() {
211
236
  try {
212
237
  await this.reconcile();
@@ -821,6 +846,27 @@ var CoreBlocksAddon = class extends require_dist.BaseAddon {
821
846
  this.convergeSoon();
822
847
  return { block: updated };
823
848
  },
849
+ /**
850
+ * Awaited, unlike every other write here.
851
+ *
852
+ * `create`/`update` deliberately do not wait — the write is already
853
+ * durable and converging forks a process. A restart has no durable half:
854
+ * the whole thing IS the converge, so returning before it ran would hand
855
+ * the operator the old status and read as "nothing happened".
856
+ */
857
+ restart: async ({ blockId }) => {
858
+ const store = this.requireStore();
859
+ const block = store.get(blockId);
860
+ if (block === null) throw new Error(`core-blocks: no block "${blockId}"`);
861
+ if (!block.enabled) throw new Error(`core-blocks: "${block.name}" is disabled — enable it instead of restarting it`);
862
+ if (this.supervisor === null) throw new Error("core-blocks: no block supervisor runs on this node — nothing here can restart a block");
863
+ this.ctx.logger.info("core block restart requested", { meta: {
864
+ blockId,
865
+ name: block.name
866
+ } });
867
+ await this.supervisor.restart(blockId);
868
+ return { block: store.get(blockId) ?? block };
869
+ },
824
870
  compile: async ({ code }) => {
825
871
  if (this.transpiler === null) return {
826
872
  ok: false,
@@ -866,16 +912,37 @@ function createBrokerProcessHost(broker) {
866
912
  }
867
913
  };
868
914
  }
869
- /** Load esbuild lazily. Returns null — never throws — so a node without it
870
- * still boots, with the compile gate refusing writes rather than the node
871
- * refusing to start. */
915
+ /**
916
+ * Load esbuild lazily, and PROVE IT WORKS before trusting it.
917
+ *
918
+ * Returns null — never throws — so a node without a usable compiler still
919
+ * boots, with the compile gate refusing writes rather than the node refusing
920
+ * to start.
921
+ *
922
+ * The probe is not defensive decoration. On the live hub, 2026-08-08, the
923
+ * import succeeded and `transform()` threw *"The esbuild JavaScript API cannot
924
+ * be bundled"* (it had been inlined into `@camstack/system`'s dist, so it could
925
+ * no longer find its own native binary). Importing successfully was therefore
926
+ * NOT evidence the compiler worked — and because the failure arrived at
927
+ * transform time, every save came back as
928
+ * `core-blocks: the code does not compile: …`, which blames the author for a
929
+ * broken node. Nobody could have written source that passed. One transform at
930
+ * boot converts that into the honest answer the rest of this file already
931
+ * gives: the compiler is unavailable here.
932
+ */
872
933
  async function loadTranspiler(logger) {
873
934
  try {
874
- const transform = (await Promise.resolve().then(() => /* @__PURE__ */ require_chunk.__toESM(require("../../main-CVLq8IzA.js").default))).transform;
935
+ const transform = (await import("esbuild")).transform;
875
936
  if (typeof transform !== "function") return null;
876
- return { transform };
937
+ const transpiler = { transform };
938
+ await transpiler.transform("export default 0", {
939
+ loader: "ts",
940
+ format: "esm",
941
+ target: "node22"
942
+ });
943
+ return transpiler;
877
944
  } catch (err) {
878
- logger.warn("core-blocks: esbuild unavailable — code cannot be compiled on this node", { meta: { error: err instanceof Error ? err.message : String(err) } });
945
+ logger.warn("core-blocks: esbuild unusable — code cannot be compiled on this node", { meta: { error: err instanceof Error ? err.message : String(err) } });
879
946
  return null;
880
947
  }
881
948
  }
@@ -1,5 +1,4 @@
1
- import { o as __toESM } from "../../chunk-CNf5ZN-e.mjs";
2
- import { C as coreBlocksCapability, o as CoreBlockSchema, pt as BaseAddon } from "../../dist-L6QPJWsr.mjs";
1
+ import { C as coreBlockAddonId, T as coreBlocksCapability, dt as BaseAddon, o as CORE_BLOCK_ADDON_PREFIX, s as CoreBlockSchema, w as coreBlockIdFromAddonId } from "../../dist-BJ2w3uoN.mjs";
3
2
  import { randomUUID } from "node:crypto";
4
3
  import * as path$1 from "node:path";
5
4
  import * as fs from "node:fs/promises";
@@ -24,23 +23,18 @@ import * as fs from "node:fs/promises";
24
23
  * that killed `CoreBlocksAddon` in production while the build stayed green and
25
24
  * the topology reported it running.
26
25
  */
27
- /** Prefix for the generated addon id AND the runner id — one addon, one
28
- * process, so the two are the same string (D2). Grep-able on purpose: an
29
- * operator reading `$process.list` must be able to tell a block runner from
30
- * an addon runner at a glance. */
31
- var BLOCK_ADDON_PREFIX = "core-block-";
32
- /** The generated addon/runner id for a block. */
33
- function blockAddonId(blockId) {
34
- return `${BLOCK_ADDON_PREFIX}${blockId}`;
35
- }
36
- /** The block id behind a generated addon/runner id, or null when the id
37
- * belongs to something else. The inverse of {@link blockAddonId} used to
38
- * read the live process list back into block terms. */
39
- function blockIdFromAddonId(addonId) {
40
- if (!addonId.startsWith("core-block-")) return null;
41
- const rest = addonId.slice(11);
42
- return rest.length > 0 ? rest : null;
43
- }
26
+ /**
27
+ * The runner id lives on the CAPABILITY, not here.
28
+ *
29
+ * It stopped being an implementation detail the day the operator surface
30
+ * started reading a block's logs by asking `logs.query` for
31
+ * `addonId = core-block-<id>` with no cap of its own. A second copy of the
32
+ * string would drift into a logs pane that is silently always empty, so these
33
+ * are re-exports under the names this package already used.
34
+ */
35
+ var BLOCK_ADDON_PREFIX = CORE_BLOCK_ADDON_PREFIX;
36
+ var blockAddonId = coreBlockAddonId;
37
+ var blockIdFromAddonId = coreBlockIdFromAddonId;
44
38
  /**
45
39
  * The wrapper module, generated verbatim.
46
40
  *
@@ -204,6 +198,36 @@ var BlockSupervisor = class {
204
198
  this.inFlight = this.inFlight.then(() => this.runPass(), () => this.runPass());
205
199
  return this.inFlight;
206
200
  }
201
+ /**
202
+ * Stop one block's runner and let the pass that follows bring it back.
203
+ *
204
+ * The same path a code change already takes (see `stale` in `reconcile`),
205
+ * exposed on purpose. It writes NO state of its own: forgetting the
206
+ * materialised `updatedAt` is what makes the next pass treat the block as
207
+ * needing a launch, and that pass is still the authority — so a restart that
208
+ * raced a converge ends with one runner, not two.
209
+ *
210
+ * Queued on the same chain as `converge()`: a restart that interleaved with a
211
+ * timer tick could stop a runner the tick had just decided was healthy and
212
+ * then never re-launch it.
213
+ */
214
+ restart(blockId) {
215
+ this.inFlight = this.inFlight.then(() => this.runRestart(blockId), () => this.runRestart(blockId));
216
+ return this.inFlight;
217
+ }
218
+ async runRestart(blockId) {
219
+ try {
220
+ await this.deps.host.stop(blockAddonId(blockId));
221
+ this.deps.logger.info("core block runner stopped for restart", { meta: { blockId } });
222
+ } catch (err) {
223
+ this.deps.logger.warn("could not stop the core block runner before restart", { meta: {
224
+ blockId,
225
+ error: err instanceof Error ? err.message : String(err)
226
+ } });
227
+ }
228
+ this.materialized.delete(blockId);
229
+ await this.runPass();
230
+ }
207
231
  async runPass() {
208
232
  try {
209
233
  await this.reconcile();
@@ -818,6 +842,27 @@ var CoreBlocksAddon = class extends BaseAddon {
818
842
  this.convergeSoon();
819
843
  return { block: updated };
820
844
  },
845
+ /**
846
+ * Awaited, unlike every other write here.
847
+ *
848
+ * `create`/`update` deliberately do not wait — the write is already
849
+ * durable and converging forks a process. A restart has no durable half:
850
+ * the whole thing IS the converge, so returning before it ran would hand
851
+ * the operator the old status and read as "nothing happened".
852
+ */
853
+ restart: async ({ blockId }) => {
854
+ const store = this.requireStore();
855
+ const block = store.get(blockId);
856
+ if (block === null) throw new Error(`core-blocks: no block "${blockId}"`);
857
+ if (!block.enabled) throw new Error(`core-blocks: "${block.name}" is disabled — enable it instead of restarting it`);
858
+ if (this.supervisor === null) throw new Error("core-blocks: no block supervisor runs on this node — nothing here can restart a block");
859
+ this.ctx.logger.info("core block restart requested", { meta: {
860
+ blockId,
861
+ name: block.name
862
+ } });
863
+ await this.supervisor.restart(blockId);
864
+ return { block: store.get(blockId) ?? block };
865
+ },
821
866
  compile: async ({ code }) => {
822
867
  if (this.transpiler === null) return {
823
868
  ok: false,
@@ -863,16 +908,37 @@ function createBrokerProcessHost(broker) {
863
908
  }
864
909
  };
865
910
  }
866
- /** Load esbuild lazily. Returns null — never throws — so a node without it
867
- * still boots, with the compile gate refusing writes rather than the node
868
- * refusing to start. */
911
+ /**
912
+ * Load esbuild lazily, and PROVE IT WORKS before trusting it.
913
+ *
914
+ * Returns null — never throws — so a node without a usable compiler still
915
+ * boots, with the compile gate refusing writes rather than the node refusing
916
+ * to start.
917
+ *
918
+ * The probe is not defensive decoration. On the live hub, 2026-08-08, the
919
+ * import succeeded and `transform()` threw *"The esbuild JavaScript API cannot
920
+ * be bundled"* (it had been inlined into `@camstack/system`'s dist, so it could
921
+ * no longer find its own native binary). Importing successfully was therefore
922
+ * NOT evidence the compiler worked — and because the failure arrived at
923
+ * transform time, every save came back as
924
+ * `core-blocks: the code does not compile: …`, which blames the author for a
925
+ * broken node. Nobody could have written source that passed. One transform at
926
+ * boot converts that into the honest answer the rest of this file already
927
+ * gives: the compiler is unavailable here.
928
+ */
869
929
  async function loadTranspiler(logger) {
870
930
  try {
871
- const transform = (await import("../../main-CzH19GkU.mjs").then((m) => /* @__PURE__ */ __toESM(m.default))).transform;
931
+ const transform = (await import("esbuild")).transform;
872
932
  if (typeof transform !== "function") return null;
873
- return { transform };
933
+ const transpiler = { transform };
934
+ await transpiler.transform("export default 0", {
935
+ loader: "ts",
936
+ format: "esm",
937
+ target: "node22"
938
+ });
939
+ return transpiler;
874
940
  } catch (err) {
875
- logger.warn("core-blocks: esbuild unavailable — code cannot be compiled on this node", { meta: { error: err instanceof Error ? err.message : String(err) } });
941
+ logger.warn("core-blocks: esbuild unusable — code cannot be compiled on this node", { meta: { error: err instanceof Error ? err.message : String(err) } });
876
942
  return null;
877
943
  }
878
944
  }
@@ -88,6 +88,36 @@ export declare function resolveNativeDeviceOwner(deps: AggregationDeps, deviceId
88
88
  addonId: string;
89
89
  nodeId: string;
90
90
  } | null;
91
+ /**
92
+ * How long ONE cap's `getStatus` may take before the aggregate gives up on it
93
+ * and reports `null`.
94
+ *
95
+ * A cap call for a device owned by another node crosses the cluster, where the
96
+ * transport's own ceiling is `REMOTE_CALL_TIMEOUT_MS` — SIXTY seconds. That
97
+ * ceiling is right for a deliberate operator action; it is catastrophic here,
98
+ * because this aggregate is what a camera LIST asks for on every boot, and it
99
+ * has no answer to give until the slowest cap replies.
100
+ *
101
+ * The failure this bounds is not a node that is DOWN — `classifyCapRoute`
102
+ * rejects those immediately with `node-offline`, which the catch below turns
103
+ * into `null` in microseconds. It is a node that is FLAPPING: a crash-looping
104
+ * agent re-registers every few seconds, so the registry believes it is online,
105
+ * the call is routed, and the peer dies mid-flight. Then nothing throws and
106
+ * nothing resolves. On 2026-08-08 little-unraid did exactly that for 22
107
+ * minutes and the viewer showed a loading skeleton the whole time.
108
+ *
109
+ * 3s is chosen against what a HEALTHY remote cap costs (a same-LAN cap call is
110
+ * single-digit ms; the slowest observed live is ~300ms) — so this cannot cut
111
+ * off a working node — and against what a human waits for a camera list.
112
+ */
113
+ export declare const STATUS_CALL_DEADLINE_MS = 3000;
114
+ /** Per-call bound for a status aggregation. Tests pass a small value; nothing
115
+ * in production overrides the default. */
116
+ export interface StatusAggregateOptions {
117
+ /** Deadline for a single cap's `getStatus`. Defaults to
118
+ * {@link STATUS_CALL_DEADLINE_MS}. */
119
+ readonly deadlineMs?: number;
120
+ }
91
121
  /**
92
122
  * Aggregate `status` across every registered cap for a device.
93
123
  *
@@ -99,6 +129,10 @@ export declare function resolveNativeDeviceOwner(deps: AggregationDeps, deviceId
99
129
  * overall aggregate stays usable — a single misbehaving provider
100
130
  * must not blank out a device's entire status view.
101
131
  *
132
+ * A provider that never answers is treated exactly like one that throws: it
133
+ * costs its own cap and nothing else. See {@link STATUS_CALL_DEADLINE_MS} for
134
+ * why a bound is needed at all when the transport already has one.
135
+ *
102
136
  * Returned shape is `Record<capName, unknown | null>`; the client-
103
137
  * side hook tightens this to `CapStatusTypeMap` via the generated
104
138
  * `cap-status-types.ts`.
@@ -106,7 +140,7 @@ export declare function resolveNativeDeviceOwner(deps: AggregationDeps, deviceId
106
140
  export declare function getDeviceStatusAggregate(deps: AggregationDeps, input: {
107
141
  readonly deviceId: number;
108
142
  readonly caps?: readonly string[];
109
- }): Promise<Record<string, unknown | null>>;
143
+ }, options?: StatusAggregateOptions): Promise<Record<string, unknown | null>>;
110
144
  /**
111
145
  * `getDeviceStatusAggregate` for a LIST of devices, in one round-trip.
112
146
  *
@@ -120,11 +154,24 @@ export declare function getDeviceStatusAggregate(deps: AggregationDeps, input: {
120
154
  * A device whose whole aggregation fails still gets an ENTRY (all caps null)
121
155
  * rather than being omitted: a caller that reads a missing key as "not
122
156
  * answered yet" would keep the previous badge on screen forever.
157
+ *
158
+ * ## The response is BOUNDED, and that is load-bearing
159
+ *
160
+ * The windows are walked SEQUENTIALLY, so before the per-cap deadline existed a
161
+ * single unanswerable device did not merely cost its own window — it held every
162
+ * later window behind it, and the call never returned at all. That is the whole
163
+ * mechanism behind "one node offline and the app loads nothing": this is the
164
+ * one call a camera list makes for every card, the client has no timeout of its
165
+ * own, and a tRPC query that never settles is `isLoading` forever.
166
+ *
167
+ * The ceiling is now `ceil(devices / STATUS_BATCH_WINDOW) × STATUS_CALL_DEADLINE_MS`
168
+ * in the worst case (a dead device in every window), and unchanged when every
169
+ * node is healthy. It is never unbounded.
123
170
  */
124
171
  export declare function getDeviceStatusAggregateBatch(deps: AggregationDeps, input: {
125
172
  readonly deviceIds: readonly number[];
126
173
  readonly caps?: readonly string[];
127
- }): Promise<Record<string, Record<string, unknown | null>>>;
174
+ }, options?: StatusAggregateOptions): Promise<Record<string, Record<string, unknown | null>>>;
128
175
  /**
129
176
  * Return the driver-specific device-settings contribution. Hub-local
130
177
  * devices call `getSettingsUISchema()` directly; forked-worker devices
@@ -1,5 +1,5 @@
1
- import { AddonContext, DeviceBindingEntry, ICapabilityRegistry } from '@camstack/types';
2
- import { DeviceBindingsStore } from './device-meta-types.js';
1
+ import { AddonContext, CapabilityDefinition, DeviceBindingEntry, ICapabilityRegistry } from '@camstack/types';
2
+ import { AddonStore, DeviceBindingsStore } from './device-meta-types.js';
3
3
  /** Push-fed remote native-cap cache shape (numeric deviceId → capName → owner). */
4
4
  export type RemoteNativeCaps = Map<number, Map<string, {
5
5
  addonId: string;
@@ -14,9 +14,6 @@ export interface BindingsDeps {
14
14
  readonly ctx: AddonContext;
15
15
  readonly capabilityRegistry: ICapabilityRegistry | undefined;
16
16
  readonly remoteNativeCaps: RemoteNativeCaps;
17
- /** O(1) gate over devices that have `deviceLinks` (the addon's live set).
18
- * Optional: when absent, getBindings adds no virtual `linked` entries. */
19
- readonly devicesWithLinks?: ReadonlySet<number>;
20
17
  }
21
18
  /**
22
19
  * Wire the push-fed cross-process native-cap cache (`remoteNativeCaps`).
@@ -72,6 +69,32 @@ export declare function resolveRemoteNativeCapFromRegistry(deps: BindingsDeps, c
72
69
  addonId: string;
73
70
  nodeId: string;
74
71
  } | null;
72
+ /**
73
+ * Resolve the device's declared TYPE (`'camera'`, `'event-emitter'`, …), or
74
+ * `undefined` when it cannot be established.
75
+ *
76
+ * The PERSISTED meta is the authority: `ctx.kernel.deviceRegistry` is hub-only
77
+ * and has been observed empty in the very process that answers `getBindings`
78
+ * correctly (see the note on `getAllBindings`). The registry is consulted only
79
+ * as a secondary source, for a device constructed but not yet persisted.
80
+ *
81
+ * `undefined` is a first-class answer and callers must treat it as "no
82
+ * filtering" — an absent row must never be the reason a device loses bindings.
83
+ */
84
+ export declare function resolveDeviceType(deps: BindingsDeps, rawStore: AddonStore, deviceId: number): string | undefined;
85
+ /**
86
+ * Does a capability apply to a device of type `deviceType`?
87
+ *
88
+ * The cap's `deviceTypes` is the ONLY declaration consulted (D4: behavioural
89
+ * cap metadata lives in the `*.cap.ts`, never in a manifest). Two deliberate
90
+ * fail-open cases:
91
+ *
92
+ * - a cap that declares no `deviceTypes` (or an empty list) applies to every
93
+ * device — the pre-existing, back-compatible semantics;
94
+ * - an UNKNOWN `deviceType` never filters, so a missing/failed meta lookup
95
+ * changes nothing (D49: a read that fails must not destroy work).
96
+ */
97
+ export declare function capAppliesToDeviceType(def: CapabilityDefinition | undefined, deviceType: string | undefined): boolean;
75
98
  export declare function getBindings(deps: BindingsDeps, input: {
76
99
  deviceId: number;
77
100
  }): Promise<{
@@ -1,4 +1,4 @@
1
- import { ConfigUISchemaWithValues, DeviceLinkMode, IDeviceManagerProvider } from '@camstack/types';
1
+ import { ConfigUISchemaWithValues, IDeviceManagerProvider, LinkedDevicesMode } from '@camstack/types';
2
2
  import { ProviderContext } from './device-provider-context.js';
3
3
  /** Reserved device-config keys (cf. `_profileMap`). */
4
4
  export declare const LINKED_MODE_CONFIG_KEY = "_linkedDevicesMode";
@@ -9,7 +9,7 @@ export declare const LINKED_IDS_CONFIG_KEY = "_linkedDeviceIds";
9
9
  * auto-materialized sensor snapshots the operator never asked for). */
10
10
  export declare const LINKED_TRACKED_IDS_CONFIG_KEY = "_linkedDeviceTrackedIds";
11
11
  export interface LinkedDevicesConfig {
12
- readonly mode: DeviceLinkMode;
12
+ readonly mode: LinkedDevicesMode;
13
13
  readonly manualIds: readonly number[];
14
14
  }
15
15
  /** Parse the two reserved keys out of a device-config blob. Tolerant: