@camstack/system 1.2.62 → 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 (83) 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-bindings-store.d.ts +28 -5
  18. package/dist/builtins/device-manager/device-linked-devices.d.ts +2 -2
  19. package/dist/builtins/device-manager/device-manager.addon.d.ts +3 -45
  20. package/dist/builtins/device-manager/device-manager.addon.js +123 -884
  21. package/dist/builtins/device-manager/device-manager.addon.mjs +123 -884
  22. package/dist/builtins/device-manager/device-meta-actions.d.ts +0 -8
  23. package/dist/builtins/device-manager/device-meta-store.d.ts +1 -28
  24. package/dist/builtins/device-manager/device-meta-types.d.ts +2 -6
  25. package/dist/builtins/device-manager/device-projection.d.ts +1 -2
  26. package/dist/builtins/device-manager/device-provider-context.d.ts +2 -13
  27. package/dist/builtins/device-manager/device-queries.d.ts +1 -1
  28. package/dist/builtins/device-manager/device-state-mirror.d.ts +8 -49
  29. package/dist/builtins/device-manager/device-sync-resolvers.d.ts +0 -11
  30. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  31. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  32. package/dist/builtins/hub-forwarder/index.js +1 -1
  33. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  34. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  35. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  36. package/dist/builtins/local-auth/homeassistant-integration.d.ts +7 -0
  37. package/dist/builtins/local-auth/index.d.ts +1 -0
  38. package/dist/builtins/local-auth/index.js +1 -0
  39. package/dist/builtins/local-auth/index.mjs +2 -2
  40. package/dist/builtins/local-auth/local-auth.addon.js +561 -497
  41. package/dist/builtins/local-auth/local-auth.addon.mjs +580 -517
  42. package/dist/builtins/local-auth/oauth-grants.d.ts +11 -0
  43. package/dist/builtins/local-auth/oauth-session-manager.d.ts +1 -1
  44. package/dist/builtins/local-network/local-network.addon.js +1 -1
  45. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  46. package/dist/builtins/loki-logging/index.js +1 -1
  47. package/dist/builtins/loki-logging/index.mjs +1 -1
  48. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  49. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  50. package/dist/builtins/platform-probe/index.js +1 -1
  51. package/dist/builtins/platform-probe/index.mjs +1 -1
  52. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  53. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  54. package/dist/builtins/snapshot/index.js +319 -20
  55. package/dist/builtins/snapshot/index.mjs +319 -20
  56. package/dist/builtins/snapshot/snapshot-keep-warm.d.ts +86 -0
  57. package/dist/builtins/snapshot/snapshot-media-handler.d.ts +48 -1
  58. package/dist/builtins/snapshot/snapshot.addon.d.ts +65 -1
  59. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  60. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  61. package/dist/builtins/sqlite-storage/retired-settings-keys.d.ts +15 -0
  62. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +45 -5
  63. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +45 -5
  64. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  65. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  66. package/dist/builtins/system-config/system-config.addon.js +1 -1
  67. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  68. package/dist/builtins/winston-logging/index.js +1 -1
  69. package/dist/builtins/winston-logging/index.mjs +1 -1
  70. package/dist/{dist-DnKpFaP1.mjs → dist-BJ2w3uoN.mjs} +1188 -1329
  71. package/dist/{dist-DgPUarcs.js → dist-LDrOFrd-.js} +1212 -1365
  72. package/dist/index.d.ts +1 -1
  73. package/dist/index.js +3 -2
  74. package/dist/index.mjs +4 -4
  75. package/dist/kernel/moleculer/device-cap-proxy.d.ts +2 -2
  76. package/dist/{manifest-python-deps-CX5tGRqf.mjs → manifest-python-deps-CopL1_5C.mjs} +20 -14
  77. package/dist/{manifest-python-deps-d4acYXVV.js → manifest-python-deps-DNfmBu20.js} +20 -14
  78. package/package.json +5 -1
  79. package/dist/builtins/device-manager/device-link-cycle.d.ts +0 -18
  80. package/dist/builtins/device-manager/device-link-overlay.d.ts +0 -33
  81. package/dist/builtins/device-manager/device-link-resolver.d.ts +0 -44
  82. package/dist/main-CVLq8IzA.js +0 -2356
  83. 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-DgPUarcs.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-DnKpFaP1.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-DgPUarcs.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-DnKpFaP1.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-DgPUarcs.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-DnKpFaP1.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-DgPUarcs.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-DnKpFaP1.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-DgPUarcs.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-DnKpFaP1.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-DgPUarcs.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-DnKpFaP1.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
  }
@@ -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:
@@ -1,6 +1,5 @@
1
1
  import { DeviceBindingEntry, BaseAddon, ProviderRegistration } from '@camstack/types';
2
2
  import { mergeAggregates } from './device-aggregation-merge.js';
3
- import { DependentLink, ResolvedTargetLink } from './device-link-overlay.js';
4
3
  import { ContributionShape } from './device-meta-types.js';
5
4
  export { mergeAggregates };
6
5
  export declare class DeviceManagerAddon extends BaseAddon {
@@ -49,53 +48,14 @@ export declare class DeviceManagerAddon extends BaseAddon {
49
48
  * the D3 re-handshake after device restore is the reliable replacement.
50
49
  */
51
50
  private remoteNativeCaps;
52
- /** Device ids that currently have at least one `deviceLinks` entry. O(1) gate
53
- * consulted by `resolveLinkedStatus` so the cap-mount overlay is a no-op for
54
- * the vast majority of devices that have no links. Rebuilt on restore. */
55
- private readonly devicesWithLinks;
56
- /** Whether a device currently has cross-device links. */
57
- devicesWithLinksHas(deviceId: number): boolean;
58
- /** `${targetDeviceId}:${targetCap}` → resolved links feeding that cap's slice. */
59
- private linkTargets;
60
- /** `${sourceDeviceId}:${sourceCap}` → targets to recompute when that source changes. */
61
- private linkDependents;
62
- /** Expected source `stableId`s (`${container}-${sourceKey}` for sibling
63
- * sources, the full `sourceStableId` for global sources) across all links,
64
- * resolved or not — gates the `registerDevice` rebuild so only a registering
65
- * device that IS a link source triggers a reindex (not every boot restore). */
66
- private expectedSourceStableIds;
67
- /** In-flight `${deviceId}:${cap}` pairs of `resolveLinkedStatus` — the P2e
68
- * defensive re-entrancy guard bounding link cycles at resolve time. Mutated
69
- * in place (never reassigned), so the host exposes a direct reference. */
70
- private readonly linkResolveInFlight;
71
- /** Test/diagnostic accessors. */
72
- linkTargetKeys(): string[];
73
- linkDependentsKeys(): string[];
74
- /** Replace the cross-device link reverse-index maps in one atomic swap.
75
- * Sole writer of `linkTargets` / `linkDependents` / `expectedSourceStableIds`;
76
- * called by `DeviceMetaStore.rebuildLinkDependents` via the `LinkIndexHost`. */
77
- setLinkIndexes(targets: Map<string, ResolvedTargetLink[]>, dependents: Map<string, DependentLink[]>, expectedSourceStableIds: Set<string>): void;
78
- /** Build the `LinkIndexHost` the meta store mutates — exposes this addon's
79
- * `devicesWithLinks` gate (read) + `setLinkIndexes` (write) without leaking
80
- * the private maps. */
81
- private get linkIndexHost();
82
51
  /** The runtime-state mirror, asserted present (constructed in onInitialize). */
83
52
  private get stateMirror();
84
- /** Build the `LinkOverlayHost` the state mirror reads when overlaying linked
85
- * values — exposes the link reverse-index maps + registry via LIVE thunks
86
- * (the maps are reassigned on every `rebuildLinkDependents`). Arrow thunks
87
- * capture `this` lexically so the getters never go stale. */
88
- private get linkOverlayHost();
89
53
  /** Build the `ProviderHost` the extracted provider-method modules
90
54
  * (`device-meta-actions`, `device-queries`) reach — exposes the addon's
91
55
  * private state/methods they need without leaking the class internals.
92
56
  *
93
- * `expectedSourceStableIds` is exposed via a LIVE thunk: the field is
94
- * reassigned to a fresh Set on every `rebuildLinkDependents`, so a captured
95
- * reference would go stale. `devicesWithLinks` / `remoteNativeCaps` are
96
- * mutated in place (never reassigned) so a direct reference preserves the
97
- * original live semantics. The overlay-emit guard now lives in the state
98
- * mirror — pruning is routed through `dropDeviceOverlays`. */
57
+ * `remoteNativeCaps` is mutated in place (never reassigned) so a direct
58
+ * reference preserves the live semantics. */
99
59
  private get providerHost();
100
60
  /** Wait for a device-provider by addonId, returning null on timeout. */
101
61
  private waitDeviceProvider;
@@ -109,9 +69,7 @@ export declare class DeviceManagerAddon extends BaseAddon {
109
69
  private discoveryProviderLabel;
110
70
  /** Provider ids that report `supportsDiscovery() === true` (probed in parallel). */
111
71
  private listDiscoveryCapableProviderIds;
112
- /** Build the dependency context the extracted binding resolvers consume.
113
- * `devicesWithLinks` is mutated in place (never reassigned) so the direct
114
- * reference stays live — it gates the virtual `linked` binding step. */
72
+ /** Build the dependency context the extracted binding resolvers consume. */
115
73
  private get bindingsDeps();
116
74
  getBindings(input: {
117
75
  deviceId: number;