@camstack/system 1.2.314 → 1.2.315
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/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 +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.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/composer-plan.d.ts +8 -1
- package/dist/builtins/composer/composer.addon.js +191 -29
- package/dist/builtins/composer/composer.addon.mjs +195 -33
- package/dist/builtins/composer/composer.d.ts +5 -1
- package/dist/builtins/composer/composition-runtime.d.ts +10 -1
- package/dist/builtins/composer/existing-target.d.ts +9 -0
- package/dist/builtins/composer/field-record.d.ts +12 -3
- 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 +3 -103
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -102
- package/dist/builtins/device-manager/device-manager.addon.js +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- package/dist/builtins/doorbell/binding-mirror.d.ts +1 -0
- package/dist/builtins/doorbell/doorbell-composition-migration.d.ts +59 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.d.ts +3 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +315 -8
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +315 -8
- 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/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +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 +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/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- 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/composition-sources-DYqQLRzF.js +122 -0
- package/dist/composition-sources-yonsRneP.mjs +111 -0
- package/dist/{dist-BtNkk3Qz.js → dist-DVKv5i-e.js} +611 -299
- package/dist/{dist-cH4t1ZEm.mjs → dist-MzYJVCLE.mjs} +582 -300
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{retired-settings-keys-DQYHfZ0S.mjs → retired-settings-keys-8yY2sTc-.mjs} +1 -1
- package/dist/{retired-settings-keys-B7imb-qH.js → retired-settings-keys-iY5I0nKM.js} +1 -1
- package/package.json +1 -1
|
@@ -701,7 +701,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
701
701
|
return EventCategory;
|
|
702
702
|
}({});
|
|
703
703
|
//#endregion
|
|
704
|
-
//#region ../types/dist/sleep-
|
|
704
|
+
//#region ../types/dist/sleep-E6eD4yQS.mjs
|
|
705
705
|
/**
|
|
706
706
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
707
707
|
* window to float samples (D455).
|
|
@@ -2777,6 +2777,10 @@ function toNodeId(idOrRunnerId) {
|
|
|
2777
2777
|
const head = idOrRunnerId.split("/")[0];
|
|
2778
2778
|
return head === void 0 || head.length === 0 ? idOrRunnerId : head;
|
|
2779
2779
|
}
|
|
2780
|
+
z.object({
|
|
2781
|
+
field: z.string().min(1),
|
|
2782
|
+
on: z.literal("advance")
|
|
2783
|
+
});
|
|
2780
2784
|
/**
|
|
2781
2785
|
* Resolve the EFFECTIVE mount hint for a cap — the explicit `mount` when
|
|
2782
2786
|
* present, else the scope/mode/deviceNative-derived default. Single source
|
|
@@ -3508,7 +3512,7 @@ function errMsg(err) {
|
|
|
3508
3512
|
return String(err);
|
|
3509
3513
|
}
|
|
3510
3514
|
//#endregion
|
|
3511
|
-
//#region ../types/dist/composition-
|
|
3515
|
+
//#region ../types/dist/composition-D5BQywfe.mjs
|
|
3512
3516
|
var AUDIO_ENCODER_BY_CODEC = {
|
|
3513
3517
|
opus: "libopus",
|
|
3514
3518
|
aac: "aac",
|
|
@@ -3902,6 +3906,173 @@ function buildFfmpegArgs(inv) {
|
|
|
3902
3906
|
];
|
|
3903
3907
|
}
|
|
3904
3908
|
/**
|
|
3909
|
+
* LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
|
|
3910
|
+
* valutatore puro del suo FRONTE.
|
|
3911
|
+
*
|
|
3912
|
+
* Viveva dentro il builtin virtual-doorbell
|
|
3913
|
+
* (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
|
|
3914
|
+
* predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
|
|
3915
|
+
* `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
|
|
3916
|
+
* tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
|
|
3917
|
+
* fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
|
|
3918
|
+
* SPOSTA qui e il doorbell la ri-esporta.
|
|
3919
|
+
*
|
|
3920
|
+
* ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
|
|
3921
|
+
* due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
|
|
3922
|
+
* regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
|
|
3923
|
+
* booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
|
|
3924
|
+
*/
|
|
3925
|
+
/**
|
|
3926
|
+
* Known binary / switch source caps → the boolean slice field whose
|
|
3927
|
+
* false→true rise counts as ACTIVE. Every entry is "fire on active".
|
|
3928
|
+
* Sensors whose "active" reading is not a plain boolean (presence's string
|
|
3929
|
+
* state, connectivity's connected flag) are deliberately excluded — a
|
|
3930
|
+
* reconnect is not a doorbell press, and it is not a recording either.
|
|
3931
|
+
*/
|
|
3932
|
+
var SOURCE_CAP_ACTIVE_FIELD = {
|
|
3933
|
+
contact: "entryOpen",
|
|
3934
|
+
binary: "on",
|
|
3935
|
+
switch: "on",
|
|
3936
|
+
motion: "detected",
|
|
3937
|
+
flood: "flooded",
|
|
3938
|
+
gas: "detected",
|
|
3939
|
+
smoke: "detected",
|
|
3940
|
+
"carbon-monoxide": "detected",
|
|
3941
|
+
vibration: "detected",
|
|
3942
|
+
tamper: "tampered"
|
|
3943
|
+
};
|
|
3944
|
+
/**
|
|
3945
|
+
* The same caps → the slice field carrying the ms-epoch timestamp of the
|
|
3946
|
+
* last transition. Every source cap MUST appear here (guarded by a spec):
|
|
3947
|
+
* without a transition timestamp the evaluator cannot tell a genuine rise
|
|
3948
|
+
* from a boot-time hydration when the FIRST slice it ever sees is already
|
|
3949
|
+
* active, and errs towards silence — swallowing the rise.
|
|
3950
|
+
*
|
|
3951
|
+
* These timestamps are UPSTREAM ones, not ingest ones: the Home Assistant
|
|
3952
|
+
* provider derives them from `state.last_changed`, so they survive our own
|
|
3953
|
+
* restarts and correctly read as "hours ago" for a state that has been
|
|
3954
|
+
* active for hours. `motion` names its rise timestamp `lastDetectedAt`.
|
|
3955
|
+
*/
|
|
3956
|
+
var SOURCE_CAP_CHANGED_AT_FIELD = {
|
|
3957
|
+
contact: "lastChangedAt",
|
|
3958
|
+
binary: "lastChangedAt",
|
|
3959
|
+
switch: "lastChangedAt",
|
|
3960
|
+
motion: "lastDetectedAt",
|
|
3961
|
+
flood: "lastChangedAt",
|
|
3962
|
+
gas: "lastChangedAt",
|
|
3963
|
+
smoke: "lastChangedAt",
|
|
3964
|
+
"carbon-monoxide": "lastChangedAt",
|
|
3965
|
+
vibration: "lastChangedAt",
|
|
3966
|
+
tamper: "lastChangedAt"
|
|
3967
|
+
};
|
|
3968
|
+
/** Cap names whose presence in a device's bindings qualify it as a source. */
|
|
3969
|
+
var SOURCE_CAPS = Object.keys(SOURCE_CAP_ACTIVE_FIELD);
|
|
3970
|
+
/** True when a cap is a recognised binary/switch source. */
|
|
3971
|
+
function isSourceCap(capName) {
|
|
3972
|
+
return Object.prototype.hasOwnProperty.call(SOURCE_CAP_ACTIVE_FIELD, capName);
|
|
3973
|
+
}
|
|
3974
|
+
/** Extract the "active" boolean a source cap's slice carries, or null when
|
|
3975
|
+
* the cap is unknown or the field is missing / non-boolean. */
|
|
3976
|
+
function sliceActiveValue(capName, slice) {
|
|
3977
|
+
const field = SOURCE_CAP_ACTIVE_FIELD[capName];
|
|
3978
|
+
if (field === void 0) return null;
|
|
3979
|
+
const raw = slice[field];
|
|
3980
|
+
return typeof raw === "boolean" ? raw : null;
|
|
3981
|
+
}
|
|
3982
|
+
/** Ms-epoch transition timestamp a source cap's slice carries, or null when
|
|
3983
|
+
* it is absent, non-numeric or the zero "never observed" sentinel. */
|
|
3984
|
+
function sliceChangedAt(capName, slice) {
|
|
3985
|
+
const field = SOURCE_CAP_CHANGED_AT_FIELD[capName];
|
|
3986
|
+
if (field === void 0) return null;
|
|
3987
|
+
const raw = slice[field];
|
|
3988
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) return null;
|
|
3989
|
+
return raw;
|
|
3990
|
+
}
|
|
3991
|
+
/**
|
|
3992
|
+
* How recent a source's own transition timestamp must be for a FIRST
|
|
3993
|
+
* sighting that is already active to count as a genuine rise rather than a
|
|
3994
|
+
* hydration of long-standing state.
|
|
3995
|
+
*/
|
|
3996
|
+
var DEFAULT_FIRST_SIGHTING_FRESHNESS_MS = 3e4;
|
|
3997
|
+
/**
|
|
3998
|
+
* The source-independent edge rule. The caller supplies an already extracted
|
|
3999
|
+
* boolean level and source timestamp; this function owns first-sighting
|
|
4000
|
+
* freshness and false-to-true detection.
|
|
4001
|
+
*/
|
|
4002
|
+
function evaluateSensorLevelEdge(input) {
|
|
4003
|
+
const timestamp = input.changedAt;
|
|
4004
|
+
const freshFloor = Math.max(input.startedAtMs, input.nowMs - input.firstSightingFreshnessMs);
|
|
4005
|
+
const firstRise = input.priorLevel === void 0 && input.level && timestamp !== null && Number.isFinite(timestamp) && timestamp >= freshFloor;
|
|
4006
|
+
const transitionRise = input.priorLevel === false && input.level && timestamp !== null && Number.isFinite(timestamp);
|
|
4007
|
+
if ((firstRise || transitionRise) && timestamp !== null) return {
|
|
4008
|
+
kind: "rise",
|
|
4009
|
+
timestamp
|
|
4010
|
+
};
|
|
4011
|
+
return {
|
|
4012
|
+
kind: "none",
|
|
4013
|
+
nextPriorLevel: input.level
|
|
4014
|
+
};
|
|
4015
|
+
}
|
|
4016
|
+
/**
|
|
4017
|
+
* IL fronte. Puro: nessun orologio proprio, nessuna memoria — il chiamante
|
|
4018
|
+
* porta `prior`, `nowMs` e il proprio `startedAtMs`.
|
|
4019
|
+
*
|
|
4020
|
+
* Il caso della PRIMA slice già attiva è trattato esplicitamente e vale come
|
|
4021
|
+
* fronte solo se il timestamp UPSTREAM della transizione è posteriore a
|
|
4022
|
+
* `startedAtMs` **e** entro `firstSightingFreshnessMs`. Entrambe le metà
|
|
4023
|
+
* servono: la sola freschezza scatterebbe su un'idratazione al boot di uno
|
|
4024
|
+
* stato flippato pochi secondi prima del riavvio, e il solo "dopo che abbiamo
|
|
4025
|
+
* iniziato" scatterebbe, su un processo di lunga vita, per una sorgente
|
|
4026
|
+
* adottata oggi il cui stato è cambiato ieri. Il caso ambiguo ERRA VERSO IL
|
|
4027
|
+
* SILENZIO e lo dichiara (`baseline-seeded-stale-active`).
|
|
4028
|
+
*/
|
|
4029
|
+
function evaluateSensorEdge(input) {
|
|
4030
|
+
const value = sliceActiveValue(input.capName, input.slice);
|
|
4031
|
+
if (value === null) return {
|
|
4032
|
+
edge: "none",
|
|
4033
|
+
value: null,
|
|
4034
|
+
reason: isSourceCap(input.capName) ? "non-boolean-value" : "unknown-cap"
|
|
4035
|
+
};
|
|
4036
|
+
if (evaluateSensorLevelEdge({
|
|
4037
|
+
level: value,
|
|
4038
|
+
changedAt: sliceChangedAt(input.capName, input.slice),
|
|
4039
|
+
priorLevel: input.prior,
|
|
4040
|
+
startedAtMs: input.startedAtMs,
|
|
4041
|
+
nowMs: input.nowMs,
|
|
4042
|
+
firstSightingFreshnessMs: input.firstSightingFreshnessMs
|
|
4043
|
+
}).kind === "rise") return {
|
|
4044
|
+
edge: "rising",
|
|
4045
|
+
value: true
|
|
4046
|
+
};
|
|
4047
|
+
if (input.prior === void 0) {
|
|
4048
|
+
if (!value) return {
|
|
4049
|
+
edge: "none",
|
|
4050
|
+
value,
|
|
4051
|
+
reason: "baseline-seeded-inactive"
|
|
4052
|
+
};
|
|
4053
|
+
return {
|
|
4054
|
+
edge: "none",
|
|
4055
|
+
value,
|
|
4056
|
+
reason: "baseline-seeded-stale-active"
|
|
4057
|
+
};
|
|
4058
|
+
}
|
|
4059
|
+
if (input.prior === value) return {
|
|
4060
|
+
edge: "none",
|
|
4061
|
+
value,
|
|
4062
|
+
reason: "no-change"
|
|
4063
|
+
};
|
|
4064
|
+
if (!value) return {
|
|
4065
|
+
edge: "none",
|
|
4066
|
+
value,
|
|
4067
|
+
reason: "falling-edge"
|
|
4068
|
+
};
|
|
4069
|
+
return {
|
|
4070
|
+
edge: "none",
|
|
4071
|
+
value,
|
|
4072
|
+
reason: "missing-timestamp"
|
|
4073
|
+
};
|
|
4074
|
+
}
|
|
4075
|
+
/**
|
|
3905
4076
|
* Error types for the safe expression engine. Two distinct classes so callers
|
|
3906
4077
|
* can tell a compile-time (grammar) failure from a runtime (evaluation)
|
|
3907
4078
|
* failure — both are non-fatal to the host: read paths degrade to "skip link".
|
|
@@ -3924,6 +4095,9 @@ var ExpressionEvalError = class extends Error {
|
|
|
3924
4095
|
this.name = "ExpressionEvalError";
|
|
3925
4096
|
}
|
|
3926
4097
|
};
|
|
4098
|
+
function isStatefulBuiltin(name) {
|
|
4099
|
+
return name === "rose" || name === "count";
|
|
4100
|
+
}
|
|
3927
4101
|
function asFiniteNumber(value, name, index) {
|
|
3928
4102
|
if (typeof value !== "number" || !Number.isFinite(value)) throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a finite number`);
|
|
3929
4103
|
return value;
|
|
@@ -3959,6 +4133,37 @@ function toNumberOrNull(value) {
|
|
|
3959
4133
|
const parsed = Number(trimmed);
|
|
3960
4134
|
return Number.isFinite(parsed) ? parsed : null;
|
|
3961
4135
|
}
|
|
4136
|
+
var DEFAULT_ROSE_DEBOUNCE_MS = 2e3;
|
|
4137
|
+
function evaluateRose(level, changedAt, debounceMs, call, field, env) {
|
|
4138
|
+
const active = asBoolean(level, "rose", 0);
|
|
4139
|
+
const timestamp = typeof changedAt === "number" && Number.isFinite(changedAt) ? changedAt : null;
|
|
4140
|
+
const debounce = debounceMs === void 0 ? DEFAULT_ROSE_DEBOUNCE_MS : asFiniteNumber(debounceMs, "rose", 2);
|
|
4141
|
+
if (debounce < 0) throw new ExpressionEvalError("rose: debounce must be a non-negative finite number");
|
|
4142
|
+
const verdict = evaluateSensorLevelEdge({
|
|
4143
|
+
level: active,
|
|
4144
|
+
changedAt: timestamp,
|
|
4145
|
+
priorLevel: call.priorLevel,
|
|
4146
|
+
startedAtMs: env.startedAtMs,
|
|
4147
|
+
nowMs: env.nowMs,
|
|
4148
|
+
firstSightingFreshnessMs: DEFAULT_FIRST_SIGHTING_FRESHNESS_MS
|
|
4149
|
+
});
|
|
4150
|
+
call.priorLevel = verdict.kind === "rise" ? true : verdict.nextPriorLevel;
|
|
4151
|
+
if (verdict.kind === "none") return env.currentOutput;
|
|
4152
|
+
if (field.acceptedEdgeAt !== null && verdict.timestamp - field.acceptedEdgeAt < debounce) return env.currentOutput;
|
|
4153
|
+
field.acceptedEdgeAt = verdict.timestamp;
|
|
4154
|
+
return verdict.timestamp;
|
|
4155
|
+
}
|
|
4156
|
+
function evaluateCount(input, call, currentOutput) {
|
|
4157
|
+
const currentCount = typeof currentOutput === "number" && Number.isFinite(currentOutput) && currentOutput >= 0 ? currentOutput : 0;
|
|
4158
|
+
if (typeof input !== "number" || !Number.isFinite(input)) return currentCount;
|
|
4159
|
+
if (call.highWater === null) {
|
|
4160
|
+
call.highWater = input;
|
|
4161
|
+
return currentCount;
|
|
4162
|
+
}
|
|
4163
|
+
if (input <= call.highWater) return currentCount;
|
|
4164
|
+
call.highWater = input;
|
|
4165
|
+
return currentCount + 1;
|
|
4166
|
+
}
|
|
3962
4167
|
var INF = Number.POSITIVE_INFINITY;
|
|
3963
4168
|
var table = {
|
|
3964
4169
|
min: {
|
|
@@ -4098,6 +4303,20 @@ var table = {
|
|
|
4098
4303
|
const present = args.flatMap((a, i) => a === null ? [] : [asFiniteNumber(a, "latest", i)]);
|
|
4099
4304
|
return present.length === 0 ? null : finiteResult(Math.max(...present), "latest");
|
|
4100
4305
|
}
|
|
4306
|
+
},
|
|
4307
|
+
rose: {
|
|
4308
|
+
minArgs: 2,
|
|
4309
|
+
maxArgs: 3,
|
|
4310
|
+
apply: () => {
|
|
4311
|
+
throw new ExpressionEvalError("rose: stateful evaluation context is required");
|
|
4312
|
+
}
|
|
4313
|
+
},
|
|
4314
|
+
count: {
|
|
4315
|
+
minArgs: 1,
|
|
4316
|
+
maxArgs: 1,
|
|
4317
|
+
apply: () => {
|
|
4318
|
+
throw new ExpressionEvalError("count: stateful evaluation context is required");
|
|
4319
|
+
}
|
|
4101
4320
|
}
|
|
4102
4321
|
};
|
|
4103
4322
|
/** Frozen, null-prototype builtin table. */
|
|
@@ -4551,22 +4770,28 @@ function step(ctx) {
|
|
|
4551
4770
|
ctx.steps += 1;
|
|
4552
4771
|
if (ctx.steps > ctx.maxSteps) throw new ExpressionEvalError("expression evaluation step budget exceeded");
|
|
4553
4772
|
}
|
|
4554
|
-
function
|
|
4773
|
+
function childPath(path, child) {
|
|
4774
|
+
return `${path}/${child}`;
|
|
4775
|
+
}
|
|
4776
|
+
function evalNode(node, ctx, path) {
|
|
4555
4777
|
step(ctx);
|
|
4556
4778
|
switch (node.kind) {
|
|
4557
4779
|
case "literal": return node.value;
|
|
4558
4780
|
case "identifier":
|
|
4559
4781
|
if (!(node.name in ctx.scope)) throw new ExpressionEvalError(`unknown identifier: ${node.name}`);
|
|
4560
4782
|
return ctx.scope[node.name];
|
|
4561
|
-
case "unary": return evalUnary(node.op, evalNode(node.operand, ctx));
|
|
4562
|
-
case "binary": return evalBinary(node.op, evalNode(node.left, ctx), evalNode(node.right, ctx));
|
|
4783
|
+
case "unary": return evalUnary(node.op, evalNode(node.operand, ctx, childPath(path, "operand")));
|
|
4784
|
+
case "binary": return evalBinary(node.op, evalNode(node.left, ctx, childPath(path, "left")), evalNode(node.right, ctx, childPath(path, "right")));
|
|
4563
4785
|
case "logical": {
|
|
4564
|
-
const left = evalNode(node.left, ctx);
|
|
4565
|
-
if (node.op === "&&") return truthy(left) ? evalNode(node.right, ctx) : left;
|
|
4566
|
-
return truthy(left) ? left : evalNode(node.right, ctx);
|
|
4786
|
+
const left = evalNode(node.left, ctx, childPath(path, "left"));
|
|
4787
|
+
if (node.op === "&&") return truthy(left) ? evalNode(node.right, ctx, childPath(path, "right")) : left;
|
|
4788
|
+
return truthy(left) ? left : evalNode(node.right, ctx, childPath(path, "right"));
|
|
4789
|
+
}
|
|
4790
|
+
case "conditional": return truthy(evalNode(node.test, ctx, childPath(path, "test"))) ? evalNode(node.consequent, ctx, childPath(path, "consequent")) : evalNode(node.alternate, ctx, childPath(path, "alternate"));
|
|
4791
|
+
case "call": {
|
|
4792
|
+
const args = node.args.map((arg, index) => evalNode(arg, ctx, childPath(path, `arg:${index}`)));
|
|
4793
|
+
return invokeCall(node.callee, args, path, ctx);
|
|
4567
4794
|
}
|
|
4568
|
-
case "conditional": return truthy(evalNode(node.test, ctx)) ? evalNode(node.consequent, ctx) : evalNode(node.alternate, ctx);
|
|
4569
|
-
case "call": return evalCall(node.callee, node.args.map((a) => evalNode(a, ctx)), ctx.hooks);
|
|
4570
4795
|
}
|
|
4571
4796
|
}
|
|
4572
4797
|
function evalUnary(op, operand) {
|
|
@@ -4597,11 +4822,19 @@ function evalBinary(op, left, right) {
|
|
|
4597
4822
|
throw new ExpressionEvalError(`"${op}" requires two numbers or two strings`);
|
|
4598
4823
|
}
|
|
4599
4824
|
}
|
|
4600
|
-
function
|
|
4825
|
+
function invokeCall(callee, args, path, ctx) {
|
|
4601
4826
|
if (!Object.prototype.hasOwnProperty.call(EXPRESSION_BUILTINS, callee)) throw new ExpressionEvalError(`unknown function: ${callee}`);
|
|
4602
4827
|
const builtin = EXPRESSION_BUILTINS[callee];
|
|
4603
4828
|
if (args.length < builtin.minArgs || args.length > builtin.maxArgs) throw new ExpressionEvalError(`${callee}: wrong number of arguments (${args.length})`);
|
|
4604
|
-
|
|
4829
|
+
if (isStatefulBuiltin(callee)) {
|
|
4830
|
+
if (ctx.invokeStateful === void 0) throw new ExpressionEvalError(`${callee}: stateful evaluation context is required`);
|
|
4831
|
+
return ctx.invokeStateful({
|
|
4832
|
+
name: callee,
|
|
4833
|
+
path,
|
|
4834
|
+
args
|
|
4835
|
+
});
|
|
4836
|
+
}
|
|
4837
|
+
return builtin.apply(args, ctx.hooks);
|
|
4605
4838
|
}
|
|
4606
4839
|
/** Evaluate an AST node against a scope. Throws `ExpressionEvalError` on any
|
|
4607
4840
|
* runtime failure (unknown identifier, type mismatch, non-finite result,
|
|
@@ -4611,8 +4844,44 @@ function evaluateAst(node, scope, opts) {
|
|
|
4611
4844
|
scope,
|
|
4612
4845
|
hooks: opts?.hooks ?? EMPTY_HOOKS,
|
|
4613
4846
|
maxSteps: opts?.maxSteps ?? 4096,
|
|
4847
|
+
...opts?.invokeStateful === void 0 ? {} : { invokeStateful: opts.invokeStateful },
|
|
4614
4848
|
steps: 0
|
|
4615
|
-
});
|
|
4849
|
+
}, "root");
|
|
4850
|
+
}
|
|
4851
|
+
function createStatefulExpressionMemory() {
|
|
4852
|
+
return {
|
|
4853
|
+
calls: /* @__PURE__ */ new Map(),
|
|
4854
|
+
acceptedEdgeAt: null
|
|
4855
|
+
};
|
|
4856
|
+
}
|
|
4857
|
+
function cloneStatefulExpressionMemory(memory) {
|
|
4858
|
+
return {
|
|
4859
|
+
calls: new Map([...memory.calls].map(([path, call]) => [path, { ...call }])),
|
|
4860
|
+
acceptedEdgeAt: memory.acceptedEdgeAt
|
|
4861
|
+
};
|
|
4862
|
+
}
|
|
4863
|
+
function evaluateStatefulExpressionCall(invocation, memory, env) {
|
|
4864
|
+
const existing = memory.calls.get(invocation.path);
|
|
4865
|
+
switch (invocation.name) {
|
|
4866
|
+
case "rose": {
|
|
4867
|
+
const call = existing?.kind === "rose" ? existing : { kind: "rose" };
|
|
4868
|
+
memory.calls.set(invocation.path, call);
|
|
4869
|
+
const currentOutput = env.currentOutput === null || typeof env.currentOutput === "number" && Number.isFinite(env.currentOutput) ? env.currentOutput : null;
|
|
4870
|
+
return evaluateRose(invocation.args[0], invocation.args[1], invocation.args[2], call, memory, {
|
|
4871
|
+
currentOutput,
|
|
4872
|
+
startedAtMs: env.startedAtMs,
|
|
4873
|
+
nowMs: env.nowMs
|
|
4874
|
+
});
|
|
4875
|
+
}
|
|
4876
|
+
case "count": {
|
|
4877
|
+
const call = existing?.kind === "count" ? existing : {
|
|
4878
|
+
kind: "count",
|
|
4879
|
+
highWater: null
|
|
4880
|
+
};
|
|
4881
|
+
memory.calls.set(invocation.path, call);
|
|
4882
|
+
return evaluateCount(invocation.args[0], call, env.currentOutput);
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4616
4885
|
}
|
|
4617
4886
|
/**
|
|
4618
4887
|
* Coerce an untrusted `getByPath` / mirror read to an `ExpressionValue`.
|
|
@@ -4653,9 +4922,10 @@ function validateExpressionSource(src) {
|
|
|
4653
4922
|
/**
|
|
4654
4923
|
* Shared read-path evaluation. Builds a null-proto scope from `bindingValues`
|
|
4655
4924
|
* plus the injected `now` (supplied by the caller for determinism and
|
|
4656
|
-
* testability), compiles via the LRU, and evaluates.
|
|
4657
|
-
*
|
|
4658
|
-
*
|
|
4925
|
+
* testability), compiles via the LRU, and evaluates. Stateful calls are
|
|
4926
|
+
* delegated through `opts.invokeStateful` with their stable AST path. Any
|
|
4927
|
+
* failure (parse or eval) returns `{ ok: false }` — the caller treats that as
|
|
4928
|
+
* "skip this derivation", never as a throw that takes the pass down.
|
|
4659
4929
|
*/
|
|
4660
4930
|
function evaluateExpressionSource(expr, bindingValues, now, opts) {
|
|
4661
4931
|
const compiled = compileExpressionSafe(expr);
|
|
@@ -4787,6 +5057,7 @@ var CompositionItemEntrySchema = z.object({
|
|
|
4787
5057
|
var CompositionFieldsFeatureSchema = z.object({
|
|
4788
5058
|
kind: z.literal("fields"),
|
|
4789
5059
|
cap: z.string().min(1),
|
|
5060
|
+
enabled: z.boolean().optional(),
|
|
4790
5061
|
fields: z.record(z.string().min(1), CompositionFieldSourceSchema).refine((fields) => Object.keys(fields).length <= 32, { message: `at most 32 fields per capability` }),
|
|
4791
5062
|
/**
|
|
4792
5063
|
* Items of the cap's item array, keyed by item key (D663). Naming `items`
|
|
@@ -4800,9 +5071,14 @@ var CompositionFieldsFeatureSchema = z.object({
|
|
|
4800
5071
|
var CompositionPassthroughFeatureSchema = z.object({
|
|
4801
5072
|
kind: z.literal("passthrough"),
|
|
4802
5073
|
cap: z.string().min(1),
|
|
5074
|
+
enabled: z.boolean().optional(),
|
|
4803
5075
|
source: CompositionSourceRefSchema
|
|
4804
5076
|
});
|
|
4805
5077
|
var CompositionFeatureSchema = z.discriminatedUnion("kind", [CompositionFieldsFeatureSchema, CompositionPassthroughFeatureSchema]);
|
|
5078
|
+
/** Only an explicit `false` disables a feature; omitted preserves the existing wire default. */
|
|
5079
|
+
function compositionFeatureEnabled(feature) {
|
|
5080
|
+
return feature.enabled !== false;
|
|
5081
|
+
}
|
|
4806
5082
|
/**
|
|
4807
5083
|
* A NEW device. It has no `name` of its own: the block's name IS the device's
|
|
4808
5084
|
* name (2026-08-08). A second copy here would be a second writer of one string (D62).
|
|
@@ -11009,6 +11285,190 @@ function procedureAuthKey(auth) {
|
|
|
11009
11285
|
function kebabToCamel(s) {
|
|
11010
11286
|
return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
11011
11287
|
}
|
|
11288
|
+
/** Unwrap ZodNullable / ZodOptional / ZodDefault wrappers to reach the inner type.
|
|
11289
|
+
* Zod v4 exposes `.unwrap()` on all three wrapper classes. */
|
|
11290
|
+
function unwrapSchema(schema) {
|
|
11291
|
+
let s = schema;
|
|
11292
|
+
while (s instanceof z.ZodNullable || s instanceof z.ZodOptional || s instanceof z.ZodDefault) s = s.unwrap();
|
|
11293
|
+
return s;
|
|
11294
|
+
}
|
|
11295
|
+
function leafKind(schema) {
|
|
11296
|
+
const s = unwrapSchema(schema);
|
|
11297
|
+
if (s instanceof z.ZodEnum) return {
|
|
11298
|
+
kind: "enum",
|
|
11299
|
+
enumValues: s.options
|
|
11300
|
+
};
|
|
11301
|
+
if (s instanceof z.ZodNumber) return { kind: "number" };
|
|
11302
|
+
if (s instanceof z.ZodString) return { kind: "string" };
|
|
11303
|
+
if (s instanceof z.ZodBoolean) return { kind: "boolean" };
|
|
11304
|
+
return null;
|
|
11305
|
+
}
|
|
11306
|
+
/** Walk a cap status schema into flat wireable leaf fields (dotted paths).
|
|
11307
|
+
* Recurses into nested ZodObject (unwrapping nullable/optional/default first),
|
|
11308
|
+
* so fields with null live values are still offered. Arrays / unknown shapes
|
|
11309
|
+
* are skipped — never throws. */
|
|
11310
|
+
function enumerateSchemaFields(schema, prefix = "") {
|
|
11311
|
+
const root = unwrapSchema(schema);
|
|
11312
|
+
if (!(root instanceof z.ZodObject)) return [];
|
|
11313
|
+
const out = [];
|
|
11314
|
+
const shape = root.shape;
|
|
11315
|
+
for (const [key, field] of Object.entries(shape)) {
|
|
11316
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
11317
|
+
const inner = unwrapSchema(field);
|
|
11318
|
+
if (inner instanceof z.ZodObject) {
|
|
11319
|
+
out.push(...enumerateSchemaFields(inner, path));
|
|
11320
|
+
continue;
|
|
11321
|
+
}
|
|
11322
|
+
const leaf = leafKind(field);
|
|
11323
|
+
if (leaf) out.push({
|
|
11324
|
+
path,
|
|
11325
|
+
kind: leaf.kind,
|
|
11326
|
+
...leaf.enumValues ? { enumValues: leaf.enumValues } : {}
|
|
11327
|
+
});
|
|
11328
|
+
}
|
|
11329
|
+
return out;
|
|
11330
|
+
}
|
|
11331
|
+
/** Enumerate the per-item wireable fields of an item-array cap (see
|
|
11332
|
+
* `CapabilityStatusItemArray`): the item schema's leaf fields, minus the
|
|
11333
|
+
* `keyField` (the key comes from the link's `itemKey`, never from a wired
|
|
11334
|
+
* source), each tagged `item: true` so the authoring UI collects an
|
|
11335
|
+
* `itemKey` alongside the field. Never throws. */
|
|
11336
|
+
function enumerateItemArrayFields(itemArray) {
|
|
11337
|
+
return enumerateSchemaFields(itemArray.itemSchema).filter((f) => f.path !== itemArray.keyField).map((f) => ({
|
|
11338
|
+
...f,
|
|
11339
|
+
item: true
|
|
11340
|
+
}));
|
|
11341
|
+
}
|
|
11342
|
+
/**
|
|
11343
|
+
* What a capability's state field IS: kind, nullability, whether required. The
|
|
11344
|
+
* validator needs nullability, which `enumerateSchemaFields` does not report,
|
|
11345
|
+
* and that decides a field's unavailable mode (D393).
|
|
11346
|
+
*/
|
|
11347
|
+
function fieldAt(schema, path) {
|
|
11348
|
+
let current = schema;
|
|
11349
|
+
for (const segment of path.split(".")) {
|
|
11350
|
+
const container = unwrapSchema(current);
|
|
11351
|
+
if (!(container instanceof z.ZodObject)) return null;
|
|
11352
|
+
const next = container.shape[segment];
|
|
11353
|
+
if (!(next instanceof z.ZodType)) return null;
|
|
11354
|
+
current = next;
|
|
11355
|
+
}
|
|
11356
|
+
return current;
|
|
11357
|
+
}
|
|
11358
|
+
function describeStateField(schema, path) {
|
|
11359
|
+
const leaf = enumerateSchemaFields(schema).find((f) => f.path === path);
|
|
11360
|
+
if (leaf === void 0) return null;
|
|
11361
|
+
const field = fieldAt(schema, path);
|
|
11362
|
+
if (field === null) return null;
|
|
11363
|
+
return {
|
|
11364
|
+
path,
|
|
11365
|
+
kind: leaf.kind,
|
|
11366
|
+
nullable: field.safeParse(null).success,
|
|
11367
|
+
required: !field.safeParse(void 0).success,
|
|
11368
|
+
...leaf.enumValues === void 0 ? {} : { enumValues: leaf.enumValues }
|
|
11369
|
+
};
|
|
11370
|
+
}
|
|
11371
|
+
function describeTopLevelStateFields(schema) {
|
|
11372
|
+
return new Map(enumerateSchemaFields(schema).filter((f) => !f.path.includes(".")).flatMap((f) => {
|
|
11373
|
+
const described = describeStateField(schema, f.path);
|
|
11374
|
+
return described === null ? [] : [[f.path, described]];
|
|
11375
|
+
}));
|
|
11376
|
+
}
|
|
11377
|
+
/** Top-level keys a value MUST carry: those whose schema refuses `undefined`. */
|
|
11378
|
+
function requiredTopLevelKeys(schema) {
|
|
11379
|
+
const root = unwrapSchema(schema);
|
|
11380
|
+
if (!(root instanceof z.ZodObject)) return [];
|
|
11381
|
+
return Object.entries(root.shape).flatMap(([key, field]) => field instanceof z.ZodType && !field.safeParse(void 0).success ? [key] : []);
|
|
11382
|
+
}
|
|
11383
|
+
/**
|
|
11384
|
+
* The proper prefixes of `path` whose schema accepts `undefined`: a required
|
|
11385
|
+
* leaf under one of them is required only once something writes that parent
|
|
11386
|
+
* (`remaining.unit` is required only when `remaining` is present).
|
|
11387
|
+
*/
|
|
11388
|
+
function optionalAncestors(schema, path) {
|
|
11389
|
+
const segments = path.split(".");
|
|
11390
|
+
return segments.slice(0, -1).flatMap((_, i) => {
|
|
11391
|
+
const prefix = segments.slice(0, i + 1).join(".");
|
|
11392
|
+
const field = fieldAt(schema, prefix);
|
|
11393
|
+
return field !== null && field.safeParse(void 0).success ? [prefix] : [];
|
|
11394
|
+
});
|
|
11395
|
+
}
|
|
11396
|
+
var EVENT_CATEGORIES = { "doorbell.onPressed": EventCategory.DoorbellOnPressed };
|
|
11397
|
+
var CompositionEventPlanSchema = z.object({
|
|
11398
|
+
featureIndex: z.number().int().nonnegative(),
|
|
11399
|
+
cap: z.string(),
|
|
11400
|
+
eventName: z.string(),
|
|
11401
|
+
category: z.enum(EventCategory),
|
|
11402
|
+
stateField: z.string(),
|
|
11403
|
+
fieldKey: z.string()
|
|
11404
|
+
});
|
|
11405
|
+
function eventProblem(code, path, message) {
|
|
11406
|
+
return {
|
|
11407
|
+
code,
|
|
11408
|
+
path,
|
|
11409
|
+
message
|
|
11410
|
+
};
|
|
11411
|
+
}
|
|
11412
|
+
function payloadReason(error) {
|
|
11413
|
+
return error.issues.map((issue) => `${issue.path.length === 0 ? "payload" : issue.path.join(".")}: ${issue.message}`).join("; ");
|
|
11414
|
+
}
|
|
11415
|
+
/**
|
|
11416
|
+
* Plan one capability's state-derived events after its feature mode and fields
|
|
11417
|
+
* are known. A native replacement never becomes a second event authority.
|
|
11418
|
+
*/
|
|
11419
|
+
function planCompositionEvents(def, feature, fields) {
|
|
11420
|
+
if (feature.mode === "replace") return {
|
|
11421
|
+
problems: [],
|
|
11422
|
+
events: []
|
|
11423
|
+
};
|
|
11424
|
+
const problems = [];
|
|
11425
|
+
const events = [];
|
|
11426
|
+
for (const [eventName, declaration] of Object.entries(def.events ?? {})) {
|
|
11427
|
+
const fromState = declaration.fromState;
|
|
11428
|
+
if (fromState === void 0) continue;
|
|
11429
|
+
const statePath = `features[${feature.featureIndex}].fields.${fromState.field}`;
|
|
11430
|
+
const state = def.runtimeState ? describeStateField(def.runtimeState, fromState.field) : null;
|
|
11431
|
+
if (state === null) {
|
|
11432
|
+
problems.push(eventProblem("unknown-field", statePath, `\`${def.name}.${eventName}\` derives from missing state field \`${fromState.field}\``));
|
|
11433
|
+
continue;
|
|
11434
|
+
}
|
|
11435
|
+
if (state.kind !== "number") {
|
|
11436
|
+
problems.push(eventProblem("kind-mismatch", statePath, `\`${def.name}.${eventName}\` needs a numeric state field; \`${fromState.field}\` is ${state.kind}`));
|
|
11437
|
+
continue;
|
|
11438
|
+
}
|
|
11439
|
+
const eventKey = `${def.name}.${eventName}`;
|
|
11440
|
+
const category = EVENT_CATEGORIES[eventKey];
|
|
11441
|
+
if (category === void 0) {
|
|
11442
|
+
problems.push(eventProblem("not-composable", `features[${feature.featureIndex}].cap`, `\`${eventKey}\` has no explicit composition event-category mapping`));
|
|
11443
|
+
continue;
|
|
11444
|
+
}
|
|
11445
|
+
const payload = declaration.data.safeParse({
|
|
11446
|
+
deviceId: 1,
|
|
11447
|
+
timestamp: 1
|
|
11448
|
+
});
|
|
11449
|
+
if (!payload.success) {
|
|
11450
|
+
problems.push(eventProblem("not-composable", `features[${feature.featureIndex}].cap`, `\`${eventKey}\` cannot be derived from { deviceId, timestamp }: ${payloadReason(payload.error)}`));
|
|
11451
|
+
continue;
|
|
11452
|
+
}
|
|
11453
|
+
const field = fields.find((candidate) => candidate.featureIndex === feature.featureIndex && candidate.cap === def.name && candidate.path === fromState.field && candidate.item === void 0);
|
|
11454
|
+
if (field === void 0) {
|
|
11455
|
+
problems.push(eventProblem("unknown-field", statePath, `\`${eventKey}\` derives from \`${fromState.field}\`, but that field is not composed`));
|
|
11456
|
+
continue;
|
|
11457
|
+
}
|
|
11458
|
+
events.push({
|
|
11459
|
+
featureIndex: feature.featureIndex,
|
|
11460
|
+
cap: def.name,
|
|
11461
|
+
eventName,
|
|
11462
|
+
category,
|
|
11463
|
+
stateField: fromState.field,
|
|
11464
|
+
fieldKey: `${field.cap}.${field.path}`
|
|
11465
|
+
});
|
|
11466
|
+
}
|
|
11467
|
+
return {
|
|
11468
|
+
problems,
|
|
11469
|
+
events
|
|
11470
|
+
};
|
|
11471
|
+
}
|
|
11012
11472
|
/**
|
|
11013
11473
|
* What the validator says about a composition and what the composer says about
|
|
11014
11474
|
* a running one: the wire shapes the API returns (D659).
|
|
@@ -11063,12 +11523,14 @@ var CompositionUnavailableModeSchema = z.enum([
|
|
|
11063
11523
|
]);
|
|
11064
11524
|
/**
|
|
11065
11525
|
* `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
|
|
11066
|
-
* field of its cap changes value · `constant` reads nothing, computed once
|
|
11526
|
+
* field of its cap changes value · `constant` reads nothing, computed once ·
|
|
11527
|
+
* `accumulator` advances from the target's seed/current output.
|
|
11067
11528
|
*/
|
|
11068
11529
|
var CompositionFieldRoleSchema = z.enum([
|
|
11069
11530
|
"derived",
|
|
11070
11531
|
"clock",
|
|
11071
|
-
"constant"
|
|
11532
|
+
"constant",
|
|
11533
|
+
"accumulator"
|
|
11072
11534
|
]);
|
|
11073
11535
|
/** The item a planned field belongs to; its `path` is then the dotted path INSIDE the item. */
|
|
11074
11536
|
var CompositionFieldItemSchema = z.object({
|
|
@@ -11109,6 +11571,7 @@ var CompositionFeatureModeSchema = z.enum([
|
|
|
11109
11571
|
"replace"
|
|
11110
11572
|
]);
|
|
11111
11573
|
var CompositionFeaturePlanSchema = z.object({
|
|
11574
|
+
/** Index in the stored feature array; disabled features are omitted rather than re-indexed. */
|
|
11112
11575
|
featureIndex: z.number().int().nonnegative(),
|
|
11113
11576
|
cap: z.string(),
|
|
11114
11577
|
mode: CompositionFeatureModeSchema
|
|
@@ -11117,7 +11580,8 @@ var CompositionValidationSchema = z.object({
|
|
|
11117
11580
|
ok: z.boolean(),
|
|
11118
11581
|
problems: z.array(CompositionProblemSchema),
|
|
11119
11582
|
fields: z.array(CompositionFieldPlanSchema),
|
|
11120
|
-
features: z.array(CompositionFeaturePlanSchema)
|
|
11583
|
+
features: z.array(CompositionFeaturePlanSchema),
|
|
11584
|
+
events: z.array(CompositionEventPlanSchema)
|
|
11121
11585
|
});
|
|
11122
11586
|
/**
|
|
11123
11587
|
* Mirrors `CoreBlockStatus`, pinned by `core-blocks-source.spec.ts`. `degraded`:
|
|
@@ -29024,148 +29488,6 @@ var videoclipsCapability = {
|
|
|
29024
29488
|
}
|
|
29025
29489
|
};
|
|
29026
29490
|
/**
|
|
29027
|
-
* LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
|
|
29028
|
-
* valutatore puro del suo FRONTE.
|
|
29029
|
-
*
|
|
29030
|
-
* Viveva dentro il builtin virtual-doorbell
|
|
29031
|
-
* (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
|
|
29032
|
-
* predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
|
|
29033
|
-
* `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
|
|
29034
|
-
* tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
|
|
29035
|
-
* fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
|
|
29036
|
-
* SPOSTA qui e il doorbell la ri-esporta.
|
|
29037
|
-
*
|
|
29038
|
-
* ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
|
|
29039
|
-
* due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
|
|
29040
|
-
* regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
|
|
29041
|
-
* booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
|
|
29042
|
-
*/
|
|
29043
|
-
/**
|
|
29044
|
-
* Known binary / switch source caps → the boolean slice field whose
|
|
29045
|
-
* false→true rise counts as ACTIVE. Every entry is "fire on active".
|
|
29046
|
-
* Sensors whose "active" reading is not a plain boolean (presence's string
|
|
29047
|
-
* state, connectivity's connected flag) are deliberately excluded — a
|
|
29048
|
-
* reconnect is not a doorbell press, and it is not a recording either.
|
|
29049
|
-
*/
|
|
29050
|
-
var SOURCE_CAP_ACTIVE_FIELD = {
|
|
29051
|
-
contact: "entryOpen",
|
|
29052
|
-
binary: "on",
|
|
29053
|
-
switch: "on",
|
|
29054
|
-
motion: "detected",
|
|
29055
|
-
flood: "flooded",
|
|
29056
|
-
gas: "detected",
|
|
29057
|
-
smoke: "detected",
|
|
29058
|
-
"carbon-monoxide": "detected",
|
|
29059
|
-
vibration: "detected",
|
|
29060
|
-
tamper: "tampered"
|
|
29061
|
-
};
|
|
29062
|
-
/**
|
|
29063
|
-
* The same caps → the slice field carrying the ms-epoch timestamp of the
|
|
29064
|
-
* last transition. Every source cap MUST appear here (guarded by a spec):
|
|
29065
|
-
* without a transition timestamp the evaluator cannot tell a genuine rise
|
|
29066
|
-
* from a boot-time hydration when the FIRST slice it ever sees is already
|
|
29067
|
-
* active, and errs towards silence — swallowing the rise.
|
|
29068
|
-
*
|
|
29069
|
-
* These timestamps are UPSTREAM ones, not ingest ones: the Home Assistant
|
|
29070
|
-
* provider derives them from `state.last_changed`, so they survive our own
|
|
29071
|
-
* restarts and correctly read as "hours ago" for a state that has been
|
|
29072
|
-
* active for hours. `motion` names its rise timestamp `lastDetectedAt`.
|
|
29073
|
-
*/
|
|
29074
|
-
var SOURCE_CAP_CHANGED_AT_FIELD = {
|
|
29075
|
-
contact: "lastChangedAt",
|
|
29076
|
-
binary: "lastChangedAt",
|
|
29077
|
-
switch: "lastChangedAt",
|
|
29078
|
-
motion: "lastDetectedAt",
|
|
29079
|
-
flood: "lastChangedAt",
|
|
29080
|
-
gas: "lastChangedAt",
|
|
29081
|
-
smoke: "lastChangedAt",
|
|
29082
|
-
"carbon-monoxide": "lastChangedAt",
|
|
29083
|
-
vibration: "lastChangedAt",
|
|
29084
|
-
tamper: "lastChangedAt"
|
|
29085
|
-
};
|
|
29086
|
-
/** Cap names whose presence in a device's bindings qualify it as a source. */
|
|
29087
|
-
var SOURCE_CAPS = Object.keys(SOURCE_CAP_ACTIVE_FIELD);
|
|
29088
|
-
/** True when a cap is a recognised binary/switch source. */
|
|
29089
|
-
function isSourceCap(capName) {
|
|
29090
|
-
return Object.prototype.hasOwnProperty.call(SOURCE_CAP_ACTIVE_FIELD, capName);
|
|
29091
|
-
}
|
|
29092
|
-
/** Extract the "active" boolean a source cap's slice carries, or null when
|
|
29093
|
-
* the cap is unknown or the field is missing / non-boolean. */
|
|
29094
|
-
function sliceActiveValue(capName, slice) {
|
|
29095
|
-
const field = SOURCE_CAP_ACTIVE_FIELD[capName];
|
|
29096
|
-
if (field === void 0) return null;
|
|
29097
|
-
const raw = slice[field];
|
|
29098
|
-
return typeof raw === "boolean" ? raw : null;
|
|
29099
|
-
}
|
|
29100
|
-
/** Ms-epoch transition timestamp a source cap's slice carries, or null when
|
|
29101
|
-
* it is absent, non-numeric or the zero "never observed" sentinel. */
|
|
29102
|
-
function sliceChangedAt(capName, slice) {
|
|
29103
|
-
const field = SOURCE_CAP_CHANGED_AT_FIELD[capName];
|
|
29104
|
-
if (field === void 0) return null;
|
|
29105
|
-
const raw = slice[field];
|
|
29106
|
-
if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) return null;
|
|
29107
|
-
return raw;
|
|
29108
|
-
}
|
|
29109
|
-
/**
|
|
29110
|
-
* How recent a source's own transition timestamp must be for a FIRST
|
|
29111
|
-
* sighting that is already active to count as a genuine rise rather than a
|
|
29112
|
-
* hydration of long-standing state.
|
|
29113
|
-
*/
|
|
29114
|
-
var DEFAULT_FIRST_SIGHTING_FRESHNESS_MS = 3e4;
|
|
29115
|
-
/**
|
|
29116
|
-
* IL fronte. Puro: nessun orologio proprio, nessuna memoria — il chiamante
|
|
29117
|
-
* porta `prior`, `nowMs` e il proprio `startedAtMs`.
|
|
29118
|
-
*
|
|
29119
|
-
* Il caso della PRIMA slice già attiva è trattato esplicitamente e vale come
|
|
29120
|
-
* fronte solo se il timestamp UPSTREAM della transizione è posteriore a
|
|
29121
|
-
* `startedAtMs` **e** entro `firstSightingFreshnessMs`. Entrambe le metà
|
|
29122
|
-
* servono: la sola freschezza scatterebbe su un'idratazione al boot di uno
|
|
29123
|
-
* stato flippato pochi secondi prima del riavvio, e il solo "dopo che abbiamo
|
|
29124
|
-
* iniziato" scatterebbe, su un processo di lunga vita, per una sorgente
|
|
29125
|
-
* adottata oggi il cui stato è cambiato ieri. Il caso ambiguo ERRA VERSO IL
|
|
29126
|
-
* SILENZIO e lo dichiara (`baseline-seeded-stale-active`).
|
|
29127
|
-
*/
|
|
29128
|
-
function evaluateSensorEdge(input) {
|
|
29129
|
-
const value = sliceActiveValue(input.capName, input.slice);
|
|
29130
|
-
if (value === null) return {
|
|
29131
|
-
edge: "none",
|
|
29132
|
-
value: null,
|
|
29133
|
-
reason: isSourceCap(input.capName) ? "non-boolean-value" : "unknown-cap"
|
|
29134
|
-
};
|
|
29135
|
-
if (input.prior === void 0) {
|
|
29136
|
-
if (!value) return {
|
|
29137
|
-
edge: "none",
|
|
29138
|
-
value,
|
|
29139
|
-
reason: "baseline-seeded-inactive"
|
|
29140
|
-
};
|
|
29141
|
-
const changedAt = sliceChangedAt(input.capName, input.slice);
|
|
29142
|
-
const floor = Math.max(input.startedAtMs, input.nowMs - input.firstSightingFreshnessMs);
|
|
29143
|
-
if (changedAt === null || changedAt < floor) return {
|
|
29144
|
-
edge: "none",
|
|
29145
|
-
value,
|
|
29146
|
-
reason: "baseline-seeded-stale-active"
|
|
29147
|
-
};
|
|
29148
|
-
return {
|
|
29149
|
-
edge: "rising",
|
|
29150
|
-
value: true
|
|
29151
|
-
};
|
|
29152
|
-
}
|
|
29153
|
-
if (input.prior === value) return {
|
|
29154
|
-
edge: "none",
|
|
29155
|
-
value,
|
|
29156
|
-
reason: "no-change"
|
|
29157
|
-
};
|
|
29158
|
-
if (!value) return {
|
|
29159
|
-
edge: "none",
|
|
29160
|
-
value,
|
|
29161
|
-
reason: "falling-edge"
|
|
29162
|
-
};
|
|
29163
|
-
return {
|
|
29164
|
-
edge: "rising",
|
|
29165
|
-
value: true
|
|
29166
|
-
};
|
|
29167
|
-
}
|
|
29168
|
-
/**
|
|
29169
29491
|
* Doorbell button cap. Two kinds of providers coexist behind this cap
|
|
29170
29492
|
* name (same pattern as `snapshot`):
|
|
29171
29493
|
*
|
|
@@ -29238,7 +29560,13 @@ var doorbellCapability = {
|
|
|
29238
29560
|
* push (`ReolinkSimpleEvent.type === 'doorbell'`). There is no
|
|
29239
29561
|
* release/duration — it's a pulse.
|
|
29240
29562
|
*/
|
|
29241
|
-
onPressed: {
|
|
29563
|
+
onPressed: {
|
|
29564
|
+
data: DoorbellPressEventSchema,
|
|
29565
|
+
fromState: {
|
|
29566
|
+
field: "lastPressedAt",
|
|
29567
|
+
on: "advance"
|
|
29568
|
+
}
|
|
29569
|
+
} },
|
|
29242
29570
|
status: {
|
|
29243
29571
|
schema: DoorbellStatusSchema,
|
|
29244
29572
|
kind: "push"
|
|
@@ -42640,7 +42968,7 @@ function createDeviceExtensionProvider(options) {
|
|
|
42640
42968
|
var CompositionGraphError = class extends Error {};
|
|
42641
42969
|
/** `"cap.field"` of every field a peer writes — the same spelling as `compositionFieldKey`. */
|
|
42642
42970
|
function writtenFields(peer) {
|
|
42643
|
-
return new Set(peer.composition.features.flatMap((feature) => feature.kind !== "fields" ? [] : featureFieldEntries(feature).map(({ fieldPath }) => `${feature.cap}.${fieldPath}`)));
|
|
42971
|
+
return new Set(peer.composition.features.flatMap((feature) => !compositionFeatureEnabled(feature) || feature.kind !== "fields" ? [] : featureFieldEntries(feature).map(({ fieldPath }) => `${feature.cap}.${fieldPath}`)));
|
|
42644
42972
|
}
|
|
42645
42973
|
/** A read of the peer's OWN device is an edge only when it reads the peer's own output (rule 5, D663). */
|
|
42646
42974
|
function readsComposedValue(peer, written, read) {
|
|
@@ -42650,7 +42978,7 @@ function readsComposedValue(peer, written, read) {
|
|
|
42650
42978
|
}
|
|
42651
42979
|
function peerEdges(peer) {
|
|
42652
42980
|
const written = writtenFields(peer);
|
|
42653
|
-
return peer.composition.features.flatMap((feature) => feature.kind !== "fields" ? [] : featureFieldEntries(feature).map(({ fieldPath, source }) => [compositionReadKey({
|
|
42981
|
+
return peer.composition.features.flatMap((feature) => !compositionFeatureEnabled(feature) || feature.kind !== "fields" ? [] : featureFieldEntries(feature).map(({ fieldPath, source }) => [compositionReadKey({
|
|
42654
42982
|
source: peer.target,
|
|
42655
42983
|
cap: feature.cap,
|
|
42656
42984
|
fieldPath
|
|
@@ -42755,114 +43083,6 @@ function findCandidateCycles(candidate, stored) {
|
|
|
42755
43083
|
}];
|
|
42756
43084
|
});
|
|
42757
43085
|
}
|
|
42758
|
-
/** Unwrap ZodNullable / ZodOptional / ZodDefault wrappers to reach the inner type.
|
|
42759
|
-
* Zod v4 exposes `.unwrap()` on all three wrapper classes. */
|
|
42760
|
-
function unwrapSchema(schema) {
|
|
42761
|
-
let s = schema;
|
|
42762
|
-
while (s instanceof z.ZodNullable || s instanceof z.ZodOptional || s instanceof z.ZodDefault) s = s.unwrap();
|
|
42763
|
-
return s;
|
|
42764
|
-
}
|
|
42765
|
-
function leafKind(schema) {
|
|
42766
|
-
const s = unwrapSchema(schema);
|
|
42767
|
-
if (s instanceof z.ZodEnum) return {
|
|
42768
|
-
kind: "enum",
|
|
42769
|
-
enumValues: s.options
|
|
42770
|
-
};
|
|
42771
|
-
if (s instanceof z.ZodNumber) return { kind: "number" };
|
|
42772
|
-
if (s instanceof z.ZodString) return { kind: "string" };
|
|
42773
|
-
if (s instanceof z.ZodBoolean) return { kind: "boolean" };
|
|
42774
|
-
return null;
|
|
42775
|
-
}
|
|
42776
|
-
/** Walk a cap status schema into flat wireable leaf fields (dotted paths).
|
|
42777
|
-
* Recurses into nested ZodObject (unwrapping nullable/optional/default first),
|
|
42778
|
-
* so fields with null live values are still offered. Arrays / unknown shapes
|
|
42779
|
-
* are skipped — never throws. */
|
|
42780
|
-
function enumerateSchemaFields(schema, prefix = "") {
|
|
42781
|
-
const root = unwrapSchema(schema);
|
|
42782
|
-
if (!(root instanceof z.ZodObject)) return [];
|
|
42783
|
-
const out = [];
|
|
42784
|
-
const shape = root.shape;
|
|
42785
|
-
for (const [key, field] of Object.entries(shape)) {
|
|
42786
|
-
const path = prefix ? `${prefix}.${key}` : key;
|
|
42787
|
-
const inner = unwrapSchema(field);
|
|
42788
|
-
if (inner instanceof z.ZodObject) {
|
|
42789
|
-
out.push(...enumerateSchemaFields(inner, path));
|
|
42790
|
-
continue;
|
|
42791
|
-
}
|
|
42792
|
-
const leaf = leafKind(field);
|
|
42793
|
-
if (leaf) out.push({
|
|
42794
|
-
path,
|
|
42795
|
-
kind: leaf.kind,
|
|
42796
|
-
...leaf.enumValues ? { enumValues: leaf.enumValues } : {}
|
|
42797
|
-
});
|
|
42798
|
-
}
|
|
42799
|
-
return out;
|
|
42800
|
-
}
|
|
42801
|
-
/** Enumerate the per-item wireable fields of an item-array cap (see
|
|
42802
|
-
* `CapabilityStatusItemArray`): the item schema's leaf fields, minus the
|
|
42803
|
-
* `keyField` (the key comes from the link's `itemKey`, never from a wired
|
|
42804
|
-
* source), each tagged `item: true` so the authoring UI collects an
|
|
42805
|
-
* `itemKey` alongside the field. Never throws. */
|
|
42806
|
-
function enumerateItemArrayFields(itemArray) {
|
|
42807
|
-
return enumerateSchemaFields(itemArray.itemSchema).filter((f) => f.path !== itemArray.keyField).map((f) => ({
|
|
42808
|
-
...f,
|
|
42809
|
-
item: true
|
|
42810
|
-
}));
|
|
42811
|
-
}
|
|
42812
|
-
/**
|
|
42813
|
-
* What a capability's state field IS: kind, nullability, whether required. The
|
|
42814
|
-
* validator needs nullability, which `enumerateSchemaFields` does not report,
|
|
42815
|
-
* and that decides a field's unavailable mode (D393).
|
|
42816
|
-
*/
|
|
42817
|
-
function fieldAt(schema, path) {
|
|
42818
|
-
let current = schema;
|
|
42819
|
-
for (const segment of path.split(".")) {
|
|
42820
|
-
const container = unwrapSchema(current);
|
|
42821
|
-
if (!(container instanceof z.ZodObject)) return null;
|
|
42822
|
-
const next = container.shape[segment];
|
|
42823
|
-
if (!(next instanceof z.ZodType)) return null;
|
|
42824
|
-
current = next;
|
|
42825
|
-
}
|
|
42826
|
-
return current;
|
|
42827
|
-
}
|
|
42828
|
-
function describeStateField(schema, path) {
|
|
42829
|
-
const leaf = enumerateSchemaFields(schema).find((f) => f.path === path);
|
|
42830
|
-
if (leaf === void 0) return null;
|
|
42831
|
-
const field = fieldAt(schema, path);
|
|
42832
|
-
if (field === null) return null;
|
|
42833
|
-
return {
|
|
42834
|
-
path,
|
|
42835
|
-
kind: leaf.kind,
|
|
42836
|
-
nullable: field.safeParse(null).success,
|
|
42837
|
-
required: !field.safeParse(void 0).success,
|
|
42838
|
-
...leaf.enumValues === void 0 ? {} : { enumValues: leaf.enumValues }
|
|
42839
|
-
};
|
|
42840
|
-
}
|
|
42841
|
-
function describeTopLevelStateFields(schema) {
|
|
42842
|
-
return new Map(enumerateSchemaFields(schema).filter((f) => !f.path.includes(".")).flatMap((f) => {
|
|
42843
|
-
const described = describeStateField(schema, f.path);
|
|
42844
|
-
return described === null ? [] : [[f.path, described]];
|
|
42845
|
-
}));
|
|
42846
|
-
}
|
|
42847
|
-
/** Top-level keys a value MUST carry: those whose schema refuses `undefined`. */
|
|
42848
|
-
function requiredTopLevelKeys(schema) {
|
|
42849
|
-
const root = unwrapSchema(schema);
|
|
42850
|
-
if (!(root instanceof z.ZodObject)) return [];
|
|
42851
|
-
return Object.entries(root.shape).flatMap(([key, field]) => field instanceof z.ZodType && !field.safeParse(void 0).success ? [key] : []);
|
|
42852
|
-
}
|
|
42853
|
-
/**
|
|
42854
|
-
* The proper prefixes of `path` whose schema accepts `undefined`: a required
|
|
42855
|
-
* leaf under one of them is required only once something writes that parent
|
|
42856
|
-
* (`remaining.unit` is required only when `remaining` is present).
|
|
42857
|
-
*/
|
|
42858
|
-
function optionalAncestors(schema, path) {
|
|
42859
|
-
const segments = path.split(".");
|
|
42860
|
-
return segments.slice(0, -1).flatMap((_, i) => {
|
|
42861
|
-
const prefix = segments.slice(0, i + 1).join(".");
|
|
42862
|
-
const field = fieldAt(schema, prefix);
|
|
42863
|
-
return field !== null && field.safeParse(void 0).success ? [prefix] : [];
|
|
42864
|
-
});
|
|
42865
|
-
}
|
|
42866
43086
|
function problem(code, path, message) {
|
|
42867
43087
|
return {
|
|
42868
43088
|
code,
|
|
@@ -42874,7 +43094,8 @@ function refused(...problems) {
|
|
|
42874
43094
|
return {
|
|
42875
43095
|
problems,
|
|
42876
43096
|
fields: [],
|
|
42877
|
-
features: []
|
|
43097
|
+
features: [],
|
|
43098
|
+
events: []
|
|
42878
43099
|
};
|
|
42879
43100
|
}
|
|
42880
43101
|
/** Where a planned field sits in the composition: `features[N].fields.<path>` or `features[N].items.<key>.fields.<path>`. */
|
|
@@ -42916,7 +43137,12 @@ var BUILTIN_RESULT_KINDS = Object.freeze({
|
|
|
42916
43137
|
number: {
|
|
42917
43138
|
kind: "fixed",
|
|
42918
43139
|
result: ["number", "null"]
|
|
42919
|
-
}
|
|
43140
|
+
},
|
|
43141
|
+
rose: {
|
|
43142
|
+
kind: "fixed",
|
|
43143
|
+
result: ["number", "null"]
|
|
43144
|
+
},
|
|
43145
|
+
count: NUMBER
|
|
42920
43146
|
});
|
|
42921
43147
|
var KindInferenceError = class extends Error {};
|
|
42922
43148
|
function kindOfValue(value) {
|
|
@@ -42975,6 +43201,21 @@ function kindsOf(node, bindings) {
|
|
|
42975
43201
|
case "call": return callKinds(node.callee, node.args.map((a) => kindsOf(a, bindings)));
|
|
42976
43202
|
}
|
|
42977
43203
|
}
|
|
43204
|
+
function nodeContainsCall(node, callee) {
|
|
43205
|
+
switch (node.kind) {
|
|
43206
|
+
case "literal":
|
|
43207
|
+
case "identifier": return false;
|
|
43208
|
+
case "unary": return nodeContainsCall(node.operand, callee);
|
|
43209
|
+
case "binary":
|
|
43210
|
+
case "logical": return nodeContainsCall(node.left, callee) || nodeContainsCall(node.right, callee);
|
|
43211
|
+
case "conditional": return nodeContainsCall(node.test, callee) || nodeContainsCall(node.consequent, callee) || nodeContainsCall(node.alternate, callee);
|
|
43212
|
+
case "call": return node.callee === callee || node.args.some((arg) => nodeContainsCall(arg, callee));
|
|
43213
|
+
}
|
|
43214
|
+
}
|
|
43215
|
+
function expressionContainsBuiltinCall(expr, callee) {
|
|
43216
|
+
const compiled = compileExpressionSafe(expr);
|
|
43217
|
+
return compiled.ok && nodeContainsCall(compiled.parsed.ast, callee);
|
|
43218
|
+
}
|
|
42978
43219
|
function inferExpressionKinds(input) {
|
|
42979
43220
|
const compiled = compileExpressionSafe(input.expr);
|
|
42980
43221
|
if (!compiled.ok) return {
|
|
@@ -43095,6 +43336,7 @@ function enumLiteralProblem(cap, target, source, at) {
|
|
|
43095
43336
|
}
|
|
43096
43337
|
function roleOf(source) {
|
|
43097
43338
|
if (source.kind !== "expression") return "derived";
|
|
43339
|
+
if (expressionContainsBuiltinCall(source.expr, "count")) return "accumulator";
|
|
43098
43340
|
if (fieldReads(source).length > 0) return "derived";
|
|
43099
43341
|
const compiled = compileExpressionSafe(source.expr);
|
|
43100
43342
|
return compiled.ok && compiled.parsed.identifiers.has("now") ? "clock" : "constant";
|
|
@@ -43106,13 +43348,13 @@ function roleOf(source) {
|
|
|
43106
43348
|
* field that READS a source never gets `offline`.
|
|
43107
43349
|
*/
|
|
43108
43350
|
function unavailableFor(tf, role, mode, cap, at) {
|
|
43109
|
-
if (mode === "
|
|
43351
|
+
if (mode === "replace" && (role === "derived" || role === "accumulator")) return {
|
|
43110
43352
|
ok: true,
|
|
43111
|
-
unavailable:
|
|
43353
|
+
unavailable: "release"
|
|
43112
43354
|
};
|
|
43113
|
-
if (mode === "
|
|
43355
|
+
if (mode === "new" || role !== "derived") return {
|
|
43114
43356
|
ok: true,
|
|
43115
|
-
unavailable: "
|
|
43357
|
+
unavailable: tf.nullable ? "null" : "offline"
|
|
43116
43358
|
};
|
|
43117
43359
|
if (tf.nullable) return {
|
|
43118
43360
|
ok: true,
|
|
@@ -43185,7 +43427,8 @@ function planItemField(ctx, leaf) {
|
|
|
43185
43427
|
return {
|
|
43186
43428
|
problems: [...compatibility(capName, tf, kinds.kinds, at), ...enumProblem === null ? [] : [enumProblem]],
|
|
43187
43429
|
fields: [plan],
|
|
43188
|
-
features: []
|
|
43430
|
+
features: [],
|
|
43431
|
+
events: []
|
|
43189
43432
|
};
|
|
43190
43433
|
}
|
|
43191
43434
|
function planItems(def, feature, featureIndex, lookupCap, mode = "new") {
|
|
@@ -43204,7 +43447,8 @@ function planItems(def, feature, featureIndex, lookupCap, mode = "new") {
|
|
|
43204
43447
|
const results = Object.entries(feature.items ?? {}).flatMap(([key, item]) => [{
|
|
43205
43448
|
problems: missingLeaves(ctx, item, `${at}.${key}`),
|
|
43206
43449
|
fields: [],
|
|
43207
|
-
features: []
|
|
43450
|
+
features: [],
|
|
43451
|
+
events: []
|
|
43208
43452
|
}, ...Object.entries(item.fields).map(([path, source]) => planItemField(ctx, {
|
|
43209
43453
|
key,
|
|
43210
43454
|
label: item.label,
|
|
@@ -43214,7 +43458,8 @@ function planItems(def, feature, featureIndex, lookupCap, mode = "new") {
|
|
|
43214
43458
|
return {
|
|
43215
43459
|
problems: results.flatMap((r) => r.problems),
|
|
43216
43460
|
fields: results.flatMap((r) => r.fields),
|
|
43217
|
-
features: []
|
|
43461
|
+
features: [],
|
|
43462
|
+
events: []
|
|
43218
43463
|
};
|
|
43219
43464
|
}
|
|
43220
43465
|
function describeRead(read) {
|
|
@@ -43238,7 +43483,7 @@ function fromReading(reading, read) {
|
|
|
43238
43483
|
deadline: null
|
|
43239
43484
|
};
|
|
43240
43485
|
}
|
|
43241
|
-
function expressionOutcome(source, read, now) {
|
|
43486
|
+
function expressionOutcome(source, read, now, stateful) {
|
|
43242
43487
|
const values = {};
|
|
43243
43488
|
const staleReasons = [];
|
|
43244
43489
|
let fatalUnavailable = null;
|
|
@@ -43267,10 +43512,31 @@ function expressionOutcome(source, read, now) {
|
|
|
43267
43512
|
values[name] = reading.value;
|
|
43268
43513
|
if (reading.state === "stale") staleReasons.push(`\`${name}\`: ${reading.reason}`);
|
|
43269
43514
|
}
|
|
43270
|
-
|
|
43271
|
-
if (
|
|
43515
|
+
const unavailable = fatalUnavailable ?? nonFatalUnavailable;
|
|
43516
|
+
if (unavailable !== null) {
|
|
43517
|
+
if (stateful !== void 0 && stateful.currentOutput !== void 0 && expressionContainsBuiltinCall(source.expr, "count")) return {
|
|
43518
|
+
state: "stale",
|
|
43519
|
+
value: evaluateCount(null, {
|
|
43520
|
+
kind: "count",
|
|
43521
|
+
highWater: null
|
|
43522
|
+
}, stateful.currentOutput),
|
|
43523
|
+
reason: `${unavailable.reason}; holding the last accumulator value`,
|
|
43524
|
+
deadline: null
|
|
43525
|
+
};
|
|
43526
|
+
return unavailable;
|
|
43527
|
+
}
|
|
43272
43528
|
const deadlines = [];
|
|
43273
|
-
const result = evaluateExpressionSource(source.expr, values, now, {
|
|
43529
|
+
const result = evaluateExpressionSource(source.expr, values, now, {
|
|
43530
|
+
hooks: { noteDeadline: (at) => deadlines.push(at) },
|
|
43531
|
+
...stateful === void 0 ? {} : { invokeStateful: (invocation) => evaluateStatefulExpressionCall(stateful.callPathPrefix === void 0 ? invocation : {
|
|
43532
|
+
...invocation,
|
|
43533
|
+
path: `${stateful.callPathPrefix}@${invocation.path}`
|
|
43534
|
+
}, stateful.expressionMemory, {
|
|
43535
|
+
currentOutput: stateful.currentOutput,
|
|
43536
|
+
startedAtMs: stateful.startedAtMs,
|
|
43537
|
+
nowMs: now
|
|
43538
|
+
}) }
|
|
43539
|
+
});
|
|
43274
43540
|
if (!result.ok) return {
|
|
43275
43541
|
state: "unavailable",
|
|
43276
43542
|
value: null,
|
|
@@ -43289,7 +43555,7 @@ function evaluateCompositionField(input) {
|
|
|
43289
43555
|
const { source } = input;
|
|
43290
43556
|
switch (source.kind) {
|
|
43291
43557
|
case "from": return fromReading(input.read(source), source);
|
|
43292
|
-
case "expression": return expressionOutcome(source, input.read, input.now);
|
|
43558
|
+
case "expression": return expressionOutcome(source, input.read, input.now, input.stateful);
|
|
43293
43559
|
case "code": return {
|
|
43294
43560
|
state: "unavailable",
|
|
43295
43561
|
value: null,
|
|
@@ -43367,7 +43633,8 @@ function planTopLevelField({ def, fields }, field, mode, lookupCap) {
|
|
|
43367
43633
|
return {
|
|
43368
43634
|
problems: [...compatibility(def.name, tf, kinds.kinds, fieldAt), ...enumProblem === null ? [] : [enumProblem]],
|
|
43369
43635
|
fields: [plan],
|
|
43370
|
-
features: []
|
|
43636
|
+
features: [],
|
|
43637
|
+
events: []
|
|
43371
43638
|
};
|
|
43372
43639
|
}
|
|
43373
43640
|
function planFeature(feature, featureIndex, target, lookupCap) {
|
|
@@ -43383,14 +43650,22 @@ function planFeature(feature, featureIndex, target, lookupCap) {
|
|
|
43383
43650
|
}, target.mode, lookupCap));
|
|
43384
43651
|
const items = feature.items === void 0 ? [] : [planItems(cap.def, feature, featureIndex, lookupCap, target.mode)];
|
|
43385
43652
|
const all = [...perField, ...items];
|
|
43653
|
+
const fields = all.flatMap((result) => result.fields);
|
|
43654
|
+
const featurePlan = {
|
|
43655
|
+
featureIndex,
|
|
43656
|
+
cap: cap.def.name,
|
|
43657
|
+
mode: target.mode
|
|
43658
|
+
};
|
|
43659
|
+
const eventPlan = planCompositionEvents(cap.def, featurePlan, fields);
|
|
43386
43660
|
return {
|
|
43387
|
-
problems: [
|
|
43388
|
-
|
|
43389
|
-
|
|
43390
|
-
|
|
43391
|
-
|
|
43392
|
-
|
|
43393
|
-
|
|
43661
|
+
problems: [
|
|
43662
|
+
...capProblems,
|
|
43663
|
+
...all.flatMap((r) => r.problems),
|
|
43664
|
+
...eventPlan.problems
|
|
43665
|
+
],
|
|
43666
|
+
fields,
|
|
43667
|
+
features: [featurePlan],
|
|
43668
|
+
events: eventPlan.events
|
|
43394
43669
|
};
|
|
43395
43670
|
}
|
|
43396
43671
|
/** A cap the device serves itself; one the composer grafted (an earlier ADD) is not native. */
|
|
@@ -43432,17 +43707,23 @@ function planComposition(composition, lookupCap, target) {
|
|
|
43432
43707
|
const planTarget = planTargetOf(composition, target);
|
|
43433
43708
|
if ("code" in planTarget) return refused(planTarget);
|
|
43434
43709
|
const results = composition.features.map((feature, index) => {
|
|
43435
|
-
|
|
43436
|
-
return
|
|
43710
|
+
const result = feature.kind === "passthrough" ? refused(problem("reserved-variant", `features[${index}]`, `whole-capability passthrough of \`${feature.cap}\` arrives in slice 3`)) : planFeature(feature, index, featureTargetOf(planTarget, feature.cap), lookupCap);
|
|
43711
|
+
return compositionFeatureEnabled(feature) ? result : {
|
|
43712
|
+
problems: result.problems,
|
|
43713
|
+
fields: [],
|
|
43714
|
+
features: [],
|
|
43715
|
+
events: []
|
|
43716
|
+
};
|
|
43437
43717
|
});
|
|
43438
43718
|
return {
|
|
43439
43719
|
problems: results.flatMap((r) => r.problems),
|
|
43440
43720
|
fields: results.flatMap((r) => r.fields),
|
|
43441
|
-
features: results.flatMap((r) => r.features)
|
|
43721
|
+
features: results.flatMap((r) => r.features),
|
|
43722
|
+
events: results.flatMap((r) => r.events)
|
|
43442
43723
|
};
|
|
43443
43724
|
}
|
|
43444
43725
|
function compositionSourceRefs(composition) {
|
|
43445
|
-
const refs = composition.features.flatMap((f) => f.kind === "fields" ? featureSources(f).flatMap(fieldReads).map((r) => r.source) : []);
|
|
43726
|
+
const refs = composition.features.flatMap((f) => compositionFeatureEnabled(f) && f.kind === "fields" ? featureSources(f).flatMap(fieldReads).map((r) => r.source) : []);
|
|
43446
43727
|
return [...new Map(refs.map((r) => [compositionSourceKey(r), r])).values()];
|
|
43447
43728
|
}
|
|
43448
43729
|
/**
|
|
@@ -43577,7 +43858,8 @@ function validateComposition(composition, ctx) {
|
|
|
43577
43858
|
ok: problems.length === 0,
|
|
43578
43859
|
problems,
|
|
43579
43860
|
fields: [...plan.fields],
|
|
43580
|
-
features: [...plan.features]
|
|
43861
|
+
features: [...plan.features],
|
|
43862
|
+
events: [...plan.events]
|
|
43581
43863
|
};
|
|
43582
43864
|
}
|
|
43583
43865
|
/**
|
|
@@ -57112,4 +57394,4 @@ function enumerateInferenceDevices(hw) {
|
|
|
57112
57394
|
return out;
|
|
57113
57395
|
}
|
|
57114
57396
|
//#endregion
|
|
57115
|
-
export {
|
|
57397
|
+
export { coreBlockAddonId as $, asNumber as $n, ptzAutotrackCapability as $t, STORAGE_ACCESS_FALLBACK as A, cloneStatefulExpressionMemory as An, isSameAddonId as At, addonWidgetsCapability as B, toExpressionValue as Bn, looseSchema as Bt, METHOD_ACCESS_MAP as C, withLocationMode as Cn, foldSnapshotByFunction as Ct, REDACTED_SECRET as D, SOURCE_CAP_ACTIVE_FIELD as Dn, isCollectionArrayMethod as Dt, RECORDING_SOURCE_CAMSTACK_ADDON as E, SOURCE_CAPS as En, isArrayOutputSchema as Et, StorageLocationTypeSchema as F, compositionSliceKey as Fn, lifecycleJobSchema as Ft, batteryCapability as G, BaseAddon as Gn, motionCapability as Gt, authProviderCapability as H, DeviceRole as Hn, mayWriteToLocation as Ht, StorageMigrationJobSchema as I, compositionSourceKey as In, localNetworkCapability as It, cameraGridLayoutCapability as J, DEVICE_STATUS_METHOD as Jn, objectInputDeclaresRequiredDeviceId as Jt, buildCapabilityLookup as K, DATAPLANE_SECRET_HEADER as Kn, normalizeUnit as Kt, UserRecordSchema as L, createStatefulExpressionMemory as Ln, locationIconKey as Lt, ScopedTokenSchema as M, composedDeviceStableId as Mn, kebabToCamel as Mt, StorageCleanupJobSchema as N, compositionFeatureEnabled as Nn, legacyAutoLinkIds as Nt, RUNTIME_DEFAULTS as O, SOURCE_CAP_CHANGED_AT_FIELD as On, isLocationEnabled as Ot, StorageLocationModeSchema as P, compositionItemFieldPath as Pn, legacyModeOf as Pt, compositionSourceRefs as Q, asJsonObject as Qn, procedureAuthKey as Qt, addonPagesCapability as R, evaluateSensorEdge as Rn, logDestinationCapability as Rt, LocationIconIdSchema as S, vectorStoreCapability as Sn, filesystemBrowseCapability as St, MOTION_CLOSE_AFTER_MS as T, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as Tn, gbToBytes as Tt, backupCapability as U, DeviceType as Un, metricsProviderCapability as Ut, alertsCapability as V, DeviceFeature as Vn, mayReadLocation as Vt, bareAddonId as W, errMsg as Wn, modeMayWrite as Wt, composerCapability as X, ReadinessTimeoutError as Xn, planComposition as Xt, collectSecretConfigKeys as Y, ReadinessRegistry as Yn, parseStreamParamsFormPatch as Yt, compositionFieldKey as Z, WELL_KNOWN_TAB_MAP as Zn, platformProbeCapability as Zt, FIRST_LEVEL_MACRO_CLASSES as _, streamQualityLabel as _n, enumerateSchemaFields as _t, BatteryStatusSchema as a, runtimeStatePolicyFor as an, hydrateSchema as ar, dataStoreProviderCapability as at, LOG_LEVEL_RANK as b, vectorDimFromBase64 as bn, expandContainer as bt, COMPOSED_ITEM_FIXED_FIELDS as c, scoreRuntimes as cn, nodePin as cr, defineExtensionSlotProvider as ct, CORE_BLOCK_ADDON_PREFIX as d, snapshotCapability as dn, readinessKey as dr, deviceManagerCapability as dt, reducePoints as en, asString$1 as er, coreBlockComposition as et, CoreBlockSchema as f, storageCapability as fn, resolveCapMount as fr, deviceStateCapability as ft, DeviceStatusSchema as g, streamCatalogCapability as gn, enumerateItemArrayFields as gt, DeclaredDevices as h, storageProviderCapability as hn, EventCategory as hr, enumerateInferenceDevices as ht, BaseDevice as i, resolveMethodAuth as in, expandCapMethods as ir, createDeviceExtensionProvider as it, STREAM_PROFILE_META as j, composedDeviceRef as jn, isVoidInput as jt, RecordingStatusSchema as k, buildFfmpegArgs as kn, isObjectInput as kt, COMPOSER_RUNNER_ID as l, settingsStoreCapability as ln, parseJsonObject as lr, deriveBatteryPresence as lt, DERIVED_CAMERA_SILENCED_CAP_NAMES as m, storageOccupancyCapability as mn, sleep as mr, doorbellCapability as mt, AlertSchema as n, resolveBucketMs as nn, emitDownForOwnedCaps as nr, coreBlockSourceKind as nt, CAMERA_SWITCH_ORDER as o, scopesAllowAddon as on, isDeviceConfigCap as or, declarationOwnerNodeId as ot, DAY_NAMES as p, storageMigrationCapability as pn, scopeKey as pr, deviceStatusCapability as pt, buildStreamParamsConfigSchema as q, DEVICE_SETTINGS_CONTRIBUTION_METHODS as qn, objectInputDeclaresAddonId as qt, ApiKeyRecordSchema as r, resolveLocationMode as rn, emitReadiness as rr, coreBlocksCapability as rt, CAP_NAMES_WITH_STATUS as s, scopesAllowDeviceCap as sn, isEvent as sr, decodeVectorBase64 as st, ALL_CAPABILITY_DEFINITIONS as t, replacedFieldKeys as tn, createEvent as tr, coreBlockIdFromAddonId as tt, CORE_BLOCKS_ADDON_ID as u, siteZoneKey as un, parseJsonUnknown as ur, deviceExtensionCapability as ut, FieldClaimModeSchema as v, userManagementCapability as vn, evaluateCompositionField as vt, MINUTES_PER_DAY as w, COMPOSER_ADDON_ID as wn, formatMinutes as wt, LegacyStorageLocationDefaultSchema as x, vectorIndexDimConflictError as xn, extractNestedAddonId as xt, FieldClaimSchema as y, validateComposition as yn, evictionPolicyOfLocation as yt, addonSettingsCapability as z, isSourceCap as zn, logLevelAtMost as zt };
|