@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.
- package/dist/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/block-package.d.ts +9 -27
- package/dist/builtins/core-blocks/block-supervisor.d.ts +15 -0
- package/dist/builtins/core-blocks/core-blocks.addon.js +91 -24
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +91 -25
- package/dist/builtins/device-manager/device-aggregation.d.ts +49 -2
- package/dist/builtins/device-manager/device-bindings-store.d.ts +28 -5
- package/dist/builtins/device-manager/device-linked-devices.d.ts +2 -2
- package/dist/builtins/device-manager/device-manager.addon.d.ts +3 -45
- package/dist/builtins/device-manager/device-manager.addon.js +182 -888
- package/dist/builtins/device-manager/device-manager.addon.mjs +182 -888
- package/dist/builtins/device-manager/device-meta-actions.d.ts +0 -8
- package/dist/builtins/device-manager/device-meta-store.d.ts +1 -28
- package/dist/builtins/device-manager/device-meta-types.d.ts +2 -6
- package/dist/builtins/device-manager/device-projection.d.ts +1 -2
- package/dist/builtins/device-manager/device-provider-context.d.ts +2 -13
- package/dist/builtins/device-manager/device-queries.d.ts +1 -1
- package/dist/builtins/device-manager/device-state-mirror.d.ts +8 -49
- package/dist/builtins/device-manager/device-sync-resolvers.d.ts +0 -11
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/homeassistant-integration.d.ts +7 -0
- package/dist/builtins/local-auth/index.d.ts +1 -0
- package/dist/builtins/local-auth/index.js +1 -0
- package/dist/builtins/local-auth/index.mjs +2 -2
- package/dist/builtins/local-auth/local-auth.addon.js +561 -497
- package/dist/builtins/local-auth/local-auth.addon.mjs +580 -517
- package/dist/builtins/local-auth/oauth-grants.d.ts +11 -0
- package/dist/builtins/local-auth/oauth-session-manager.d.ts +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +319 -20
- package/dist/builtins/snapshot/index.mjs +319 -20
- package/dist/builtins/snapshot/snapshot-keep-warm.d.ts +86 -0
- package/dist/builtins/snapshot/snapshot-media-handler.d.ts +48 -1
- package/dist/builtins/snapshot/snapshot.addon.d.ts +65 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/retired-settings-keys.d.ts +15 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +45 -5
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +45 -5
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-L6QPJWsr.mjs → dist-BJ2w3uoN.mjs} +1307 -1313
- package/dist/{dist-BN681PgO.js → dist-LDrOFrd-.js} +1255 -1273
- package/dist/index.d.ts +1 -1
- package/dist/index.js +209 -5
- package/dist/index.mjs +206 -7
- package/dist/kernel/addon-residue.d.ts +49 -0
- package/dist/kernel/index.d.ts +2 -0
- package/dist/kernel/moleculer/device-cap-proxy.d.ts +2 -2
- package/dist/{manifest-python-deps-CX5tGRqf.mjs → manifest-python-deps-CopL1_5C.mjs} +20 -14
- package/dist/{manifest-python-deps-d4acYXVV.js → manifest-python-deps-DNfmBu20.js} +20 -14
- package/package.json +5 -1
- package/dist/builtins/device-manager/device-link-cycle.d.ts +0 -18
- package/dist/builtins/device-manager/device-link-overlay.d.ts +0 -33
- package/dist/builtins/device-manager/device-link-resolver.d.ts +0 -44
- package/dist/main-CVLq8IzA.js +0 -2356
- package/dist/main-CzH19GkU.mjs +0 -2353
package/dist/addon-runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
2
|
-
const require_manifest_python_deps = require("./manifest-python-deps-
|
|
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);
|
package/dist/addon-runner.mjs
CHANGED
|
@@ -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-
|
|
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-
|
|
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 {
|
|
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-
|
|
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 {
|
|
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-
|
|
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 {
|
|
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-
|
|
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 {
|
|
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-
|
|
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,
|
|
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
|
-
*
|
|
3
|
+
* The runner id lives on the CAPABILITY, not here.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
-
|
|
27
|
-
export declare
|
|
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-
|
|
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
|
-
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
/**
|
|
870
|
-
*
|
|
871
|
-
*
|
|
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
|
|
935
|
+
const transform = (await import("esbuild")).transform;
|
|
875
936
|
if (typeof transform !== "function") return null;
|
|
876
|
-
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
/**
|
|
867
|
-
*
|
|
868
|
-
*
|
|
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("
|
|
931
|
+
const transform = (await import("esbuild")).transform;
|
|
872
932
|
if (typeof transform !== "function") return null;
|
|
873
|
-
|
|
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
|
|
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,
|
|
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:
|
|
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:
|