@camstack/system 1.2.485 → 1.2.487
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/{agent-https-DBP7P-ee.js → agent-https-0EEnqeUL.js} +1 -1
- package/dist/{agent-https-_EXvi6F8.mjs → agent-https-C24nO-la.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/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
- package/dist/builtins/calendars/calendars.addon.js +1 -1
- package/dist/builtins/calendars/calendars.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/composer/actuator-dispatch.d.ts +13 -0
- package/dist/builtins/composer/actuator-groups.d.ts +54 -0
- package/dist/builtins/composer/actuator-invoke.d.ts +11 -0
- package/dist/builtins/composer/composed-device.d.ts +13 -24
- package/dist/builtins/composer/composer.addon.js +464 -18
- package/dist/builtins/composer/composer.addon.mjs +464 -18
- package/dist/builtins/composer/composer.d.ts +11 -1
- package/dist/builtins/composer/composition-runtime.d.ts +2 -0
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/composition-sources.d.ts +3 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +53 -28
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +53 -28
- package/dist/builtins/device-history/device-history.addon.js +2 -2
- package/dist/builtins/device-history/device-history.addon.mjs +2 -2
- package/dist/builtins/device-manager/device-manager.addon.js +1 -1
- package/dist/builtins/device-manager/device-manager.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/keychain/index.js +3 -3
- package/dist/builtins/keychain/index.mjs +3 -3
- 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/local-auth.addon.js +2 -2
- package/dist/builtins/local-auth/local-auth.addon.mjs +2 -2
- package/dist/builtins/local-network/local-network.addon.js +2 -2
- package/dist/builtins/local-network/local-network.addon.mjs +2 -2
- 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/record-entries/index.js +1 -1
- package/dist/builtins/record-entries/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 +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -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/index.js +1 -1
- package/dist/builtins/sqlite-storage/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +2 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
- 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 +3 -3
- package/dist/builtins/system-config/system-config.addon.mjs +284 -284
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{composition-sources-BnZpIA8u.mjs → composition-sources-BA2e5vh_.mjs} +4 -2
- package/dist/{composition-sources-BSQneZV9.js → composition-sources-BulDAgW2.js} +4 -2
- package/dist/{constants-CQfTeCnB.js → constants-BntdWx4k.js} +1 -1
- package/dist/{constants-7D7xjw0h.mjs → constants-DZKo4kM2.mjs} +1 -1
- package/dist/{device-ca-D_4dI5gv.js → device-ca-BuRg4V4x.js} +1 -1
- package/dist/{device-ca-Bzd6UO_c.mjs → device-ca-DXm8IH3p.mjs} +1 -1
- package/dist/{dist-xCNJAQMh.js → dist-CrsVjBbk.js} +1127 -26
- package/dist/{dist-Df6c0RX_.mjs → dist-Dfwxsc_Y.mjs} +1062 -27
- package/dist/file-host-worker.js +1 -1
- package/dist/file-host-worker.mjs +1 -1
- package/dist/{hub-mdns-ciao-D7GMnEz0.mjs → hub-mdns-ciao-BbI8MNtQ.mjs} +2 -2
- package/dist/{hub-mdns-ciao-DRfmLR_Z.js → hub-mdns-ciao-CLroVKhP.js} +2 -2
- package/dist/index.js +7 -7
- package/dist/index.mjs +7 -7
- package/dist/{restore-archive-Co70oRya.mjs → restore-archive-BB5QxUIe.mjs} +82 -82
- package/dist/{restore-archive-DA7N6ZZA.js → restore-archive-CPoja7N3.js} +1 -1
- package/dist/{retention-DNDOPW7Z.js → retention-DgA4qLBj.js} +1 -1
- package/dist/{retention-DfCW-dZS.mjs → retention-Dz5t6cWO.mjs} +1 -1
- package/dist/{wal-maintenance-71-j48Ci.mjs → wal-maintenance-BJi3_DBN.mjs} +0 -0
- package/dist/{wal-maintenance-CA0L6WZ9.js → wal-maintenance-CL9hrbnn.js} +0 -0
- package/package.json +1 -1
|
@@ -3,22 +3,22 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
7
|
-
const require_composition_sources = require("../../composition-sources-
|
|
8
|
-
const require_retention = require("../../retention-
|
|
6
|
+
const require_dist = require("../../dist-CrsVjBbk.js");
|
|
7
|
+
const require_composition_sources = require("../../composition-sources-BulDAgW2.js");
|
|
8
|
+
const require_retention = require("../../retention-DgA4qLBj.js");
|
|
9
9
|
let zod = require("zod");
|
|
10
10
|
//#region src/builtins/shared/device-state-changed.ts
|
|
11
11
|
/** Structural narrowing helper for untrusted bus payloads. */
|
|
12
|
-
function isRecord$
|
|
12
|
+
function isRecord$3(value) {
|
|
13
13
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
14
|
}
|
|
15
15
|
function parseDeviceStateChanged(data) {
|
|
16
|
-
if (!isRecord$
|
|
16
|
+
if (!isRecord$3(data)) return null;
|
|
17
17
|
const deviceId = data["deviceId"];
|
|
18
18
|
const capName = data["capName"];
|
|
19
19
|
const slice = data["slice"];
|
|
20
20
|
if (typeof deviceId !== "number" || typeof capName !== "string") return null;
|
|
21
|
-
if (!isRecord$
|
|
21
|
+
if (!isRecord$3(slice)) return null;
|
|
22
22
|
return {
|
|
23
23
|
deviceId,
|
|
24
24
|
capName,
|
|
@@ -26,15 +26,51 @@ function parseDeviceStateChanged(data) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
//#endregion
|
|
29
|
+
//#region src/builtins/composer/actuator-invoke.ts
|
|
30
|
+
/**
|
|
31
|
+
* Turn one fan-out call into the member's existing cap method. A bad argument
|
|
32
|
+
* fails that member only — the caller still attempts the rest.
|
|
33
|
+
*/
|
|
34
|
+
async function invokeActuatorCall(api, call) {
|
|
35
|
+
if (call.cap === "switch" && call.method === "setState") {
|
|
36
|
+
const on = call.args.on;
|
|
37
|
+
if (typeof on !== "boolean") throw new Error("switch.setState needs a boolean on");
|
|
38
|
+
await api.switchSetState(call.deviceId, on);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (call.cap === "brightness" && call.method === "setBrightness") {
|
|
42
|
+
const percentage = call.args.percentage;
|
|
43
|
+
if (typeof percentage !== "number" || !Number.isFinite(percentage)) throw new Error("brightness.setBrightness needs a finite percentage");
|
|
44
|
+
await api.setBrightness(call.deviceId, percentage);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (call.cap === "color" && call.method === "setColor") {
|
|
48
|
+
const color = require_dist.parseColorInput(call.args.color);
|
|
49
|
+
if (color === null) throw new Error("color.setColor needs a colour mode");
|
|
50
|
+
await api.setColor(call.deviceId, color);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`no fan-out for ${call.cap}.${call.method}`);
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/builtins/composer/actuator-dispatch.ts
|
|
57
|
+
var dispatcher = null;
|
|
58
|
+
function setActuatorDispatcher(next) {
|
|
59
|
+
dispatcher = next;
|
|
60
|
+
}
|
|
61
|
+
function actuatorDispatcher() {
|
|
62
|
+
return dispatcher;
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
29
65
|
//#region src/builtins/composer/composed-device.ts
|
|
30
66
|
/**
|
|
31
67
|
* A composed device: an ordinary `BaseDevice` whose caps are served by the
|
|
32
68
|
* composer (D659). Every consumer (exporters, NC, UI, HomeKit) reads it
|
|
33
69
|
* through its bindings, unchanged.
|
|
34
70
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
71
|
+
* A reading group serves no command. An actuator helper's `switch` (and, on
|
|
72
|
+
* a light, `brightness` / `color` when every member binds them) fans the
|
|
73
|
+
* call out through `actuatorDispatcher` (D944). `getStatus`
|
|
38
74
|
* (R4) is the kernel's: `registerNativeCap` installs a default that reads the
|
|
39
75
|
* device's runtime slice for any cap declaring both `status` and
|
|
40
76
|
* `runtimeState` (`device-cap-proxy.ts`, "Default `getStatus`"), on the
|
|
@@ -50,13 +86,112 @@ function parseDeviceStateChanged(data) {
|
|
|
50
86
|
* declaration is the authority for what this device is.
|
|
51
87
|
*/
|
|
52
88
|
var COMPOSED_DEVICE_CONFIG_SCHEMA = zod.z.object({});
|
|
89
|
+
function priorResults(slice) {
|
|
90
|
+
if (!isRecord$2(slice) || !Array.isArray(slice.results)) return [];
|
|
91
|
+
return slice.results.flatMap((row) => {
|
|
92
|
+
const parsed = require_dist.GroupCommandResultSchema.safeParse(row);
|
|
93
|
+
return parsed.success ? [parsed.data] : [];
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function isRecord$2(value) {
|
|
97
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
98
|
+
}
|
|
99
|
+
/** Unknown is a throw, never `false` (D393). */
|
|
100
|
+
function sliceBoolean(slice, field) {
|
|
101
|
+
if (isRecord$2(slice)) {
|
|
102
|
+
const value = slice[field];
|
|
103
|
+
if (typeof value === "boolean") return value;
|
|
104
|
+
}
|
|
105
|
+
throw new Error(`composed ${field} is unknown — not a stand-in value`);
|
|
106
|
+
}
|
|
53
107
|
var ComposedDevice = class extends require_dist.BaseDevice {
|
|
54
|
-
|
|
108
|
+
sharedFeatures = [];
|
|
109
|
+
extraCaps = /* @__PURE__ */ new Set();
|
|
110
|
+
get features() {
|
|
111
|
+
return this.sharedFeatures;
|
|
112
|
+
}
|
|
55
113
|
constructor(ctx, type, caps) {
|
|
56
114
|
super(ctx, COMPOSED_DEVICE_CONFIG_SCHEMA, { type });
|
|
57
|
-
for (const cap of caps)
|
|
115
|
+
for (const cap of caps) {
|
|
116
|
+
if (cap.name === require_dist.motionCapability.name) {
|
|
117
|
+
const read = () => this.runtimeState.getCapState(require_dist.motionCapability.name);
|
|
118
|
+
ctx.registerNativeCap(require_dist.motionCapability, { isDetected: async () => sliceBoolean(read(), "detected") });
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (cap.name === require_dist.switchCapability.name) {
|
|
122
|
+
ctx.registerNativeCap(require_dist.switchCapability, { setState: async (input) => {
|
|
123
|
+
await this.dispatch("switch", "setState", { on: input.on });
|
|
124
|
+
} });
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
ctx.registerNativeCap(cap, {});
|
|
128
|
+
}
|
|
129
|
+
if (type === require_dist.DeviceType.Switch || type === require_dist.DeviceType.Light) ctx.registerNativeCap(require_dist.groupCommandsCapability, {});
|
|
58
130
|
this.markOnline(false);
|
|
59
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Register or drop brightness and colour, and publish the shared colour
|
|
134
|
+
* modes. Switch stays: it is the active state, registered with the device.
|
|
135
|
+
*/
|
|
136
|
+
syncShared(caps, modes) {
|
|
137
|
+
this.ensureExtra(require_dist.brightnessCapability.name, caps.includes(require_dist.brightnessCapability.name), () => {
|
|
138
|
+
this.ctx.registerNativeCap(require_dist.brightnessCapability, { setBrightness: async (input) => {
|
|
139
|
+
await this.dispatch("brightness", "setBrightness", { percentage: input.percentage });
|
|
140
|
+
} });
|
|
141
|
+
});
|
|
142
|
+
this.ensureExtra(require_dist.colorCapability.name, caps.includes(require_dist.colorCapability.name), () => {
|
|
143
|
+
this.ctx.registerNativeCap(require_dist.colorCapability, { setColor: async (input) => {
|
|
144
|
+
await this.dispatch("color", "setColor", { color: input.color });
|
|
145
|
+
} });
|
|
146
|
+
});
|
|
147
|
+
this.sharedFeatures = [...modes];
|
|
148
|
+
this.refreshFeatures();
|
|
149
|
+
}
|
|
150
|
+
/** Keep each member's latest attempt. A later command replaces that member only. */
|
|
151
|
+
recordCommands(incoming) {
|
|
152
|
+
const prior = priorResults(this.runtimeState.getCapState(require_dist.groupCommandsCapability.name));
|
|
153
|
+
const byDevice = new Map(prior.map((row) => [row.deviceId, row]));
|
|
154
|
+
for (const row of incoming) byDevice.set(row.deviceId, row);
|
|
155
|
+
try {
|
|
156
|
+
this.runtimeState.setCapState(require_dist.groupCommandsCapability.name, { results: [...byDevice.values()] });
|
|
157
|
+
} catch (err) {
|
|
158
|
+
this.ctx.logger.error("actuator group could not store command results", {
|
|
159
|
+
tags: { deviceId: this.id },
|
|
160
|
+
meta: { error: require_dist.errMsg(err) }
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
ensureExtra(capName, want, register) {
|
|
165
|
+
const has = this.extraCaps.has(capName);
|
|
166
|
+
if (want && !has) {
|
|
167
|
+
register();
|
|
168
|
+
this.extraCaps = new Set([...this.extraCaps, capName]);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (!want && has) {
|
|
172
|
+
this.ctx.unregisterNativeCap?.(capName);
|
|
173
|
+
this.extraCaps = new Set([...this.extraCaps].filter((name) => name !== capName));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async dispatch(cap, method, args) {
|
|
177
|
+
const run = actuatorDispatcher();
|
|
178
|
+
if (run === null) {
|
|
179
|
+
this.ctx.logger.warn("actuator group command was not sent — fan-out is not attached", {
|
|
180
|
+
tags: { deviceId: this.id },
|
|
181
|
+
meta: {
|
|
182
|
+
cap,
|
|
183
|
+
method
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
throw new Error("actuator fan-out is not attached");
|
|
187
|
+
}
|
|
188
|
+
await run({
|
|
189
|
+
groupDeviceId: this.id,
|
|
190
|
+
cap,
|
|
191
|
+
method,
|
|
192
|
+
args
|
|
193
|
+
});
|
|
194
|
+
}
|
|
60
195
|
seed(capName, slice) {
|
|
61
196
|
try {
|
|
62
197
|
this.runtimeState.setCapState(capName, { ...slice });
|
|
@@ -226,6 +361,223 @@ var ClaimGate = class {
|
|
|
226
361
|
}
|
|
227
362
|
};
|
|
228
363
|
//#endregion
|
|
364
|
+
//#region src/builtins/composer/actuator-groups.ts
|
|
365
|
+
/**
|
|
366
|
+
* Live actuator helpers (D944). State is the aggregate the composer already
|
|
367
|
+
* runs. Commands are fanned out here: every member that binds the cap,
|
|
368
|
+
* including one that is offline, and a failure on one does not cancel the rest.
|
|
369
|
+
* A light's brightness and colour are registered only while every member binds
|
|
370
|
+
* them, and recomputed when a member's bindings change.
|
|
371
|
+
*/
|
|
372
|
+
function isActuatorGroupDevice(device) {
|
|
373
|
+
return "syncShared" in device && "recordCommands" in device && "id" in device;
|
|
374
|
+
}
|
|
375
|
+
var ActuatorGroups = class {
|
|
376
|
+
deps;
|
|
377
|
+
groups = /* @__PURE__ */ new Map();
|
|
378
|
+
bound;
|
|
379
|
+
constructor(deps) {
|
|
380
|
+
this.deps = deps;
|
|
381
|
+
this.bound = (input) => this.command(input);
|
|
382
|
+
setActuatorDispatcher(this.bound);
|
|
383
|
+
}
|
|
384
|
+
shutdown() {
|
|
385
|
+
setActuatorDispatcher(null);
|
|
386
|
+
}
|
|
387
|
+
async sync(blocks) {
|
|
388
|
+
const next = /* @__PURE__ */ new Map();
|
|
389
|
+
for (const block of blocks) {
|
|
390
|
+
const remembered = await this.remember(block);
|
|
391
|
+
if (remembered !== null) next.set(block.id, remembered);
|
|
392
|
+
}
|
|
393
|
+
this.groups = next;
|
|
394
|
+
}
|
|
395
|
+
async onMemberBindings(deviceId) {
|
|
396
|
+
const hit = [...this.groups.values()].filter((group) => group.fanOut.some((member) => member.deviceId === deviceId));
|
|
397
|
+
if (hit.length === 0) return;
|
|
398
|
+
const next = new Map(this.groups);
|
|
399
|
+
for (const group of hit) {
|
|
400
|
+
const block = {
|
|
401
|
+
id: group.blockId,
|
|
402
|
+
helper: {
|
|
403
|
+
kind: "group",
|
|
404
|
+
templateId: group.templateId,
|
|
405
|
+
aggregate: "any",
|
|
406
|
+
active: "at-least-one",
|
|
407
|
+
members: group.members.map(copyMember)
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
const remembered = await this.remember(block);
|
|
411
|
+
if (remembered !== null) next.set(group.blockId, remembered);
|
|
412
|
+
}
|
|
413
|
+
this.groups = next;
|
|
414
|
+
}
|
|
415
|
+
async remember(block) {
|
|
416
|
+
const spec = block.helper;
|
|
417
|
+
if (spec === void 0) return null;
|
|
418
|
+
const template = require_dist.groupTemplate(spec.templateId);
|
|
419
|
+
if (template === null || template.family !== "actuator") return null;
|
|
420
|
+
if (template.id !== "switch" && template.id !== "light") return null;
|
|
421
|
+
const device = this.deps.deviceOf(block.id);
|
|
422
|
+
if (device === null) return null;
|
|
423
|
+
const viewed = await this.viewMembers(spec.members, device.id);
|
|
424
|
+
const shared = sharedOf(template.id, viewed);
|
|
425
|
+
const previous = this.groups.get(block.id)?.shared;
|
|
426
|
+
if (template.id === "light" && previous !== void 0) this.logCapChange(device.id, previous, shared, viewed);
|
|
427
|
+
const modes = template.id === "light" && shared.includes("color") ? require_dist.sharedColorModes(viewed.featureLists) : [];
|
|
428
|
+
device.syncShared(shared, modes);
|
|
429
|
+
return {
|
|
430
|
+
blockId: block.id,
|
|
431
|
+
templateId: template.id,
|
|
432
|
+
groupDeviceId: device.id,
|
|
433
|
+
members: spec.members.map(copyMember),
|
|
434
|
+
fanOut: viewed.fanOut,
|
|
435
|
+
shared
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
async viewMembers(members, groupDeviceId) {
|
|
439
|
+
const fanOut = [];
|
|
440
|
+
const capLists = [];
|
|
441
|
+
const featureLists = [];
|
|
442
|
+
let complete = true;
|
|
443
|
+
for (const member of members) {
|
|
444
|
+
const resolved = this.deps.resolve(member.source);
|
|
445
|
+
if (resolved.kind !== "found") {
|
|
446
|
+
complete = false;
|
|
447
|
+
this.deps.logger.warn("actuator group left a member out of shared commands — its device was not resolved", {
|
|
448
|
+
tags: { deviceId: groupDeviceId },
|
|
449
|
+
meta: { source: `${member.source.addonId}/${member.source.stableId}` }
|
|
450
|
+
});
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
const read = await this.deps.readMember(resolved.deviceId);
|
|
454
|
+
if (read.kind !== "ready") {
|
|
455
|
+
complete = false;
|
|
456
|
+
fanOut.push({
|
|
457
|
+
deviceId: resolved.deviceId,
|
|
458
|
+
caps: [],
|
|
459
|
+
online: true
|
|
460
|
+
});
|
|
461
|
+
this.deps.logger.warn("actuator group left a member out of shared commands — its capabilities could not be read", {
|
|
462
|
+
tags: { deviceId: resolved.deviceId },
|
|
463
|
+
meta: {
|
|
464
|
+
groupDeviceId,
|
|
465
|
+
error: read.error
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
capLists.push(read.caps);
|
|
471
|
+
featureLists.push(read.features);
|
|
472
|
+
fanOut.push({
|
|
473
|
+
deviceId: resolved.deviceId,
|
|
474
|
+
caps: read.caps,
|
|
475
|
+
online: true
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
fanOut,
|
|
480
|
+
capLists: complete ? capLists : null,
|
|
481
|
+
featureLists
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
logCapChange(groupDeviceId, previous, shared, viewed) {
|
|
485
|
+
for (const cap of ["brightness", "color"]) {
|
|
486
|
+
const had = previous.includes(cap);
|
|
487
|
+
const has = shared.includes(cap);
|
|
488
|
+
if (had === has) continue;
|
|
489
|
+
if (has) {
|
|
490
|
+
this.deps.logger.info("light group gained a shared capability", {
|
|
491
|
+
tags: { deviceId: groupDeviceId },
|
|
492
|
+
meta: { cap }
|
|
493
|
+
});
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
this.deps.logger.warn("light group dropped a capability a member does not bind", {
|
|
497
|
+
tags: { deviceId: groupDeviceId },
|
|
498
|
+
meta: { cap }
|
|
499
|
+
});
|
|
500
|
+
for (const member of viewed.fanOut) {
|
|
501
|
+
if (member.caps.includes(cap)) continue;
|
|
502
|
+
this.deps.logger.warn("light group dropped a capability this member does not bind", {
|
|
503
|
+
tags: { deviceId: member.deviceId },
|
|
504
|
+
meta: {
|
|
505
|
+
groupDeviceId,
|
|
506
|
+
cap
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
async command(input) {
|
|
513
|
+
const group = [...this.groups.values()].find((item) => item.groupDeviceId === input.groupDeviceId);
|
|
514
|
+
if (group === void 0) {
|
|
515
|
+
this.deps.logger.warn("actuator command dropped — this device is not a live helper group", {
|
|
516
|
+
tags: { deviceId: input.groupDeviceId },
|
|
517
|
+
meta: {
|
|
518
|
+
cap: input.cap,
|
|
519
|
+
method: input.method
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
throw new Error("actuator group is not live");
|
|
523
|
+
}
|
|
524
|
+
if (!group.shared.includes(input.cap)) {
|
|
525
|
+
this.deps.logger.warn("actuator command dropped — the group does not share that capability", {
|
|
526
|
+
tags: { deviceId: input.groupDeviceId },
|
|
527
|
+
meta: {
|
|
528
|
+
cap: input.cap,
|
|
529
|
+
method: input.method
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
throw new Error(`group does not share ${input.cap}`);
|
|
533
|
+
}
|
|
534
|
+
const results = await require_dist.runFanOut(require_dist.fanOutTargets(group.fanOut, input.cap, input.method, input.args), (call) => this.deps.invoke(call));
|
|
535
|
+
const failed = results.flatMap((result) => result.ok ? [] : [result]);
|
|
536
|
+
for (const result of failed) this.deps.logger.warn("actuator group command failed on a member — the others were still attempted", {
|
|
537
|
+
tags: { deviceId: result.deviceId },
|
|
538
|
+
meta: {
|
|
539
|
+
groupDeviceId: input.groupDeviceId,
|
|
540
|
+
cap: input.cap,
|
|
541
|
+
method: input.method,
|
|
542
|
+
error: result.error
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
if (failed.length > 0) this.deps.logger.warn("actuator group command finished with member failures", {
|
|
546
|
+
tags: { deviceId: input.groupDeviceId },
|
|
547
|
+
meta: {
|
|
548
|
+
failed: failed.length,
|
|
549
|
+
attempted: results.length,
|
|
550
|
+
cap: input.cap
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
const at = this.deps.now();
|
|
554
|
+
this.deps.deviceOf(group.blockId)?.recordCommands(results.map((result) => ({
|
|
555
|
+
deviceId: result.deviceId,
|
|
556
|
+
cap: input.cap,
|
|
557
|
+
method: input.method,
|
|
558
|
+
ok: result.ok,
|
|
559
|
+
...result.ok ? {} : { error: result.error },
|
|
560
|
+
at
|
|
561
|
+
})));
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
function sharedOf(templateId, viewed) {
|
|
565
|
+
if (templateId === "switch") return ["switch"];
|
|
566
|
+
if (viewed.capLists === null) return ["switch"];
|
|
567
|
+
const shared = require_dist.sharedLightCaps(viewed.capLists);
|
|
568
|
+
return shared.includes("switch") ? shared : ["switch", ...shared];
|
|
569
|
+
}
|
|
570
|
+
function copyMember(member) {
|
|
571
|
+
return {
|
|
572
|
+
source: {
|
|
573
|
+
addonId: member.source.addonId,
|
|
574
|
+
stableId: member.source.stableId
|
|
575
|
+
},
|
|
576
|
+
cap: member.cap,
|
|
577
|
+
fieldPath: member.fieldPath
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
//#endregion
|
|
229
581
|
//#region src/builtins/composer/composed-device-set.ts
|
|
230
582
|
/**
|
|
231
583
|
* The composed devices the composer DECLARED (D659): one row per `new`-target
|
|
@@ -1295,7 +1647,9 @@ function writable(record) {
|
|
|
1295
1647
|
switch (record.plan.unavailable) {
|
|
1296
1648
|
case "null": return null;
|
|
1297
1649
|
case "unknown": return "unknown";
|
|
1298
|
-
|
|
1650
|
+
case "omit":
|
|
1651
|
+
case "offline":
|
|
1652
|
+
case "release": return;
|
|
1299
1653
|
}
|
|
1300
1654
|
}
|
|
1301
1655
|
function errorText(error) {
|
|
@@ -2018,7 +2372,16 @@ var CompositionRuntime = class {
|
|
|
2018
2372
|
this.seeded = new Set([...this.seeded, cap]);
|
|
2019
2373
|
this.markWritten(cap, entries);
|
|
2020
2374
|
}
|
|
2375
|
+
/** An omitted optional reading must leave the slice, not keep the previous number (D944). */
|
|
2376
|
+
clearsOmitted(cap) {
|
|
2377
|
+
return this.recordsOf(cap).some((record) => record.plan.unavailable === "omit" && record.outcome?.state === "unavailable");
|
|
2378
|
+
}
|
|
2021
2379
|
patchCap(cap, touched) {
|
|
2380
|
+
if (this.clearsOmitted(cap)) {
|
|
2381
|
+
const entries = this.entriesFor(cap, this.recordsOf(cap));
|
|
2382
|
+
if (this.write(cap, "seed", entries)) this.markWritten(cap, entries);
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2022
2385
|
const entries = this.entriesFor(cap, this.recordsOf(cap).filter((r) => touched.has(fieldKey(r.plan)) && (!r.hasWritten || writable(r) !== r.written)));
|
|
2023
2386
|
if (entries.length === 0) return;
|
|
2024
2387
|
if (this.write(cap, "patch", entries)) this.markWritten(cap, entries);
|
|
@@ -3246,6 +3609,7 @@ var Composer = class {
|
|
|
3246
3609
|
claimGate;
|
|
3247
3610
|
targets;
|
|
3248
3611
|
grafts;
|
|
3612
|
+
actuators;
|
|
3249
3613
|
/** The rows the composer owns, read ONCE before its first withdrawal decision. */
|
|
3250
3614
|
rowSeed;
|
|
3251
3615
|
/**
|
|
@@ -3277,6 +3641,7 @@ var Composer = class {
|
|
|
3277
3641
|
this.grafts.describing(deviceId);
|
|
3278
3642
|
return deps.describeTarget(deviceId);
|
|
3279
3643
|
});
|
|
3644
|
+
this.actuators = this.attachActuators(deps);
|
|
3280
3645
|
this.waits = new ApplyWaits(deps.logger);
|
|
3281
3646
|
this.rowSeed = composedRowSeed({
|
|
3282
3647
|
logger: deps.logger,
|
|
@@ -3328,9 +3693,33 @@ var Composer = class {
|
|
|
3328
3693
|
this.targets.invalidate(data.deviceId);
|
|
3329
3694
|
this.grafts.onBindingsChanged(data);
|
|
3330
3695
|
this.dispatch(await this.deps.tracker.onBindingsChanged(data.deviceId, data.capName, data.reason));
|
|
3696
|
+
await this.chained(() => this.actuators?.onMemberBindings(data.deviceId));
|
|
3331
3697
|
}
|
|
3332
3698
|
shutdown() {
|
|
3333
3699
|
for (const entry of this.live.values()) entry.runtime?.stop();
|
|
3700
|
+
this.actuators?.shutdown();
|
|
3701
|
+
}
|
|
3702
|
+
attachActuators(deps) {
|
|
3703
|
+
const read = deps.readActuatorMember;
|
|
3704
|
+
const invoke = deps.invokeActuator;
|
|
3705
|
+
if (read === void 0 || invoke === void 0) return null;
|
|
3706
|
+
return new ActuatorGroups({
|
|
3707
|
+
logger: deps.logger.child("actuators"),
|
|
3708
|
+
now: deps.now,
|
|
3709
|
+
resolve: (ref) => {
|
|
3710
|
+
const found = deps.tracker.resolution(ref);
|
|
3711
|
+
return found.kind === "found" ? {
|
|
3712
|
+
kind: "found",
|
|
3713
|
+
deviceId: found.deviceId
|
|
3714
|
+
} : { kind: "other" };
|
|
3715
|
+
},
|
|
3716
|
+
readMember: read,
|
|
3717
|
+
deviceOf: (blockId) => {
|
|
3718
|
+
const device = this.composed.get(require_dist.composedDeviceStableId(blockId));
|
|
3719
|
+
return device !== null && isActuatorGroupDevice(device) ? device : null;
|
|
3720
|
+
},
|
|
3721
|
+
invoke
|
|
3722
|
+
});
|
|
3334
3723
|
}
|
|
3335
3724
|
/** Run `work` after every apply already queued, whether that settled or failed. */
|
|
3336
3725
|
chained(work) {
|
|
@@ -3364,6 +3753,10 @@ var Composer = class {
|
|
|
3364
3753
|
await Promise.all(rebuilt.stopped.map((runtime) => runtime.quiesce()));
|
|
3365
3754
|
const unreleasedClaims = await this.releaseConfirmed(releasing);
|
|
3366
3755
|
await this.syncSources(rebuilt);
|
|
3756
|
+
await this.actuators?.sync(input.blocks.map((block) => ({
|
|
3757
|
+
id: block.id,
|
|
3758
|
+
helper: block.composition.helper
|
|
3759
|
+
})));
|
|
3367
3760
|
await this.reconcileClaims();
|
|
3368
3761
|
return {
|
|
3369
3762
|
blocks: planned.map((p) => this.verdict(p.block.id)),
|
|
@@ -3746,8 +4139,21 @@ var NodeDeadlineTimer = class {
|
|
|
3746
4139
|
function unanswered(def) {
|
|
3747
4140
|
return Object.entries(def.methods).filter(([name, m]) => m.systemOnly !== true && def.composedMethods?.[name] === void 0).map(([name]) => name);
|
|
3748
4141
|
}
|
|
3749
|
-
function
|
|
4142
|
+
function isSliceRecord(value) {
|
|
4143
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4144
|
+
}
|
|
4145
|
+
/** A slice field that is present. Unknown throws — it is not false or 0 (D393). */
|
|
4146
|
+
function sliceField(slice, field) {
|
|
4147
|
+
const value = slice?.[field];
|
|
4148
|
+
if (typeof value === "boolean" || typeof value === "number" || typeof value === "string") return value;
|
|
4149
|
+
throw new Error(`composed ${field} is unknown — not a stand-in value`);
|
|
4150
|
+
}
|
|
4151
|
+
function composedMethod(answer, readSlice) {
|
|
3750
4152
|
if (answer.kind === "answer") return async () => answer.value;
|
|
4153
|
+
if (answer.kind === "slice") {
|
|
4154
|
+
const field = answer.field;
|
|
4155
|
+
return async () => sliceField(await readSlice(), field);
|
|
4156
|
+
}
|
|
3751
4157
|
return async () => {
|
|
3752
4158
|
throw new Error(answer.reason);
|
|
3753
4159
|
};
|
|
@@ -3865,7 +4271,11 @@ var GraftHost = class {
|
|
|
3865
4271
|
return ctx;
|
|
3866
4272
|
}
|
|
3867
4273
|
providerFor(deviceId, def) {
|
|
3868
|
-
const
|
|
4274
|
+
const readSlice = async () => {
|
|
4275
|
+
const slice = await this.readMerged(deviceId, def.name);
|
|
4276
|
+
return isSliceRecord(slice) ? slice : null;
|
|
4277
|
+
};
|
|
4278
|
+
const answers = Object.entries(def.composedMethods ?? {}).map(([name, answer]) => [name, composedMethod(answer, readSlice)]);
|
|
3869
4279
|
const withStatus = def.status === void 0 && def.runtimeState === void 0 ? [] : [["getStatus", async () => this.readMerged(deviceId, def.name)]];
|
|
3870
4280
|
return Object.fromEntries([...answers, ...withStatus]);
|
|
3871
4281
|
}
|
|
@@ -3942,7 +4352,7 @@ function entriesOf(request) {
|
|
|
3942
4352
|
}))];
|
|
3943
4353
|
}
|
|
3944
4354
|
function valueAtPath(slice, path) {
|
|
3945
|
-
return path.split(".").reduce((node, segment) => isRecord$
|
|
4355
|
+
return path.split(".").reduce((node, segment) => isRecord$3(node) ? node[segment] : void 0, slice);
|
|
3946
4356
|
}
|
|
3947
4357
|
function onlineOf(slice) {
|
|
3948
4358
|
const online = slice["online"];
|
|
@@ -4609,12 +5019,12 @@ var DECLARED_INTEGRATION_NAME_UNUSED = "Blocks";
|
|
|
4609
5019
|
var PULSE_HISTORY_MAX_POINTS = 5e3;
|
|
4610
5020
|
/** The owner-only shadow payload is not in the typed catalog: narrowed at the boundary, like `device.state-changed`. */
|
|
4611
5021
|
function parseNativeShadowChanged(data) {
|
|
4612
|
-
if (!isRecord$
|
|
5022
|
+
if (!isRecord$3(data)) return null;
|
|
4613
5023
|
const deviceId = data["deviceId"];
|
|
4614
5024
|
const capName = data["capName"];
|
|
4615
5025
|
const native = data["native"];
|
|
4616
5026
|
if (typeof deviceId !== "number" || typeof capName !== "string") return null;
|
|
4617
|
-
if (!isRecord$
|
|
5027
|
+
if (!isRecord$3(native)) return null;
|
|
4618
5028
|
return {
|
|
4619
5029
|
deviceId,
|
|
4620
5030
|
capName,
|
|
@@ -4778,7 +5188,43 @@ var ComposerAddon = class extends require_dist.BaseAddon {
|
|
|
4778
5188
|
describeTarget: readBoundCaps,
|
|
4779
5189
|
grafts,
|
|
4780
5190
|
now: () => Date.now(),
|
|
4781
|
-
newTimer: () => new NodeDeadlineTimer(() => Date.now())
|
|
5191
|
+
newTimer: () => new NodeDeadlineTimer(() => Date.now()),
|
|
5192
|
+
readActuatorMember: async (deviceId) => {
|
|
5193
|
+
try {
|
|
5194
|
+
const bindings = await api.deviceManager.getBindings.query({ deviceId });
|
|
5195
|
+
const found = await api.deviceManager.getDevice.query({ deviceId });
|
|
5196
|
+
return {
|
|
5197
|
+
kind: "ready",
|
|
5198
|
+
caps: bindings.entries.map((entry) => entry.capName),
|
|
5199
|
+
features: found === null ? [] : [...found.features]
|
|
5200
|
+
};
|
|
5201
|
+
} catch (err) {
|
|
5202
|
+
return {
|
|
5203
|
+
kind: "unreadable",
|
|
5204
|
+
error: require_dist.errMsg(err)
|
|
5205
|
+
};
|
|
5206
|
+
}
|
|
5207
|
+
},
|
|
5208
|
+
invokeActuator: (call) => invokeActuatorCall({
|
|
5209
|
+
switchSetState: async (deviceId, on) => {
|
|
5210
|
+
await api.switch.setState.mutate({
|
|
5211
|
+
deviceId,
|
|
5212
|
+
on
|
|
5213
|
+
});
|
|
5214
|
+
},
|
|
5215
|
+
setBrightness: async (deviceId, percentage) => {
|
|
5216
|
+
await api.brightness.setBrightness.mutate({
|
|
5217
|
+
deviceId,
|
|
5218
|
+
percentage
|
|
5219
|
+
});
|
|
5220
|
+
},
|
|
5221
|
+
setColor: async (deviceId, color) => {
|
|
5222
|
+
await api.color.setColor.mutate({
|
|
5223
|
+
deviceId,
|
|
5224
|
+
color
|
|
5225
|
+
});
|
|
5226
|
+
}
|
|
5227
|
+
}, call)
|
|
4782
5228
|
});
|
|
4783
5229
|
this.subscribe({ category: require_dist.EventCategory.DeviceStateChanged }, (event) => {
|
|
4784
5230
|
const data = parseDeviceStateChanged(event.data);
|