@camstack/types 1.2.450 → 1.2.452
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/CHANGELOG.md +21 -0
- package/dist/addon/base-addon.d.ts +27 -0
- package/dist/addon.js +9 -4
- package/dist/addon.mjs +9 -4
- package/dist/automation/condition-eval.d.ts +20 -5
- package/dist/automation/detection-subject.d.ts +154 -0
- package/dist/automation/detection-trigger.d.ts +16 -6
- package/dist/automation/index.d.ts +10 -4
- package/dist/automation/legacy-recipe.d.ts +130 -0
- package/dist/automation/normalize-units.d.ts +4 -3
- package/dist/automation/recipe.d.ts +286 -142
- package/dist/automation/stationary-trigger.d.ts +4 -4
- package/dist/automation/step-runner.d.ts +3 -1
- package/dist/automation/trace.d.ts +51 -6
- package/dist/automation/when.d.ts +295 -0
- package/dist/capabilities/actions.cap.d.ts +10 -0
- package/dist/capabilities/addon-settings.cap.d.ts +18 -0
- package/dist/capabilities/broker.cap.d.ts +8 -0
- package/dist/capabilities/core-blocks.cap.d.ts +2546 -51
- package/dist/capabilities/device-discovery.cap.d.ts +2 -0
- package/dist/capabilities/device-manager.cap.d.ts +16 -1
- package/dist/capabilities/device-provider.cap.d.ts +165 -0
- package/dist/capabilities/index.d.ts +3 -3
- package/dist/capabilities/integrations.cap.d.ts +1 -0
- package/dist/capabilities/keychain.cap.d.ts +12 -0
- package/dist/capabilities/local-network.cap.d.ts +198 -0
- package/dist/capabilities/reboot.cap.d.ts +1 -1
- package/dist/capabilities/storage-admin.cap.d.ts +155 -0
- package/dist/client.js +21 -1
- package/dist/client.mjs +21 -1
- package/dist/{composition-kSuOifuG.mjs → composition-C7WdThmP.mjs} +1 -1
- package/dist/{composition-CO6Sak-s.js → composition-tP-vAKqU.js} +1 -1
- package/dist/device/base-device-provider.d.ts +17 -0
- package/dist/device/device-type.d.ts +7 -1
- package/dist/{err-msg-DWXWc0Di.mjs → err-msg-BD5buxvT.mjs} +6 -0
- package/dist/{err-msg-EJhsFHqa.js → err-msg-DkPZXb-g.js} +6 -0
- package/dist/generated/addon-api.d.ts +84 -0
- package/dist/generated/collection-array-methods.d.ts +1 -1
- package/dist/generated/customize-catalog.d.ts +1 -1
- package/dist/generated/method-access-map.d.ts +1 -1
- package/dist/generated/system-proxy.d.ts +5 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1992 -713
- package/dist/index.mjs +1957 -710
- package/dist/interfaces/addon.d.ts +16 -0
- package/dist/interfaces/config-ui-secrets.d.ts +5 -3
- package/dist/node.js +2 -2
- package/dist/node.mjs +2 -2
- package/dist/{sleep-BSPhInRm.js → sleep-CewR0zRz.js} +125 -6
- package/dist/{sleep-63gM_1JR.mjs → sleep-CeyhrcOk.mjs} +125 -6
- package/dist/vendor-login-proxy/cookie-jar.d.ts +30 -0
- package/dist/vendor-login-proxy/index.d.ts +1 -0
- package/dist/vendor-login-proxy/login-proxy.d.ts +6 -0
- package/dist/vendor-login-proxy/url-rewrite.d.ts +9 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_sleep = require("./sleep-
|
|
2
|
+
const require_sleep = require("./sleep-CewR0zRz.js");
|
|
3
3
|
const require_event_category = require("./event-category-E17zaHjN.js");
|
|
4
|
-
const require_composition = require("./composition-
|
|
4
|
+
const require_composition = require("./composition-tP-vAKqU.js");
|
|
5
5
|
const require_node_color = require("./node-color.js");
|
|
6
|
-
const require_err_msg = require("./err-msg-
|
|
6
|
+
const require_err_msg = require("./err-msg-DkPZXb-g.js");
|
|
7
7
|
const require_enums = require("./enums.js");
|
|
8
8
|
let zod = require("zod");
|
|
9
9
|
//#region src/media/media-owner-key.ts
|
|
@@ -8845,47 +8845,664 @@ function paramMetaOf(field) {
|
|
|
8845
8845
|
return metaOf(field) ?? metaOf(unwrapSchema(field));
|
|
8846
8846
|
}
|
|
8847
8847
|
//#endregion
|
|
8848
|
-
//#region src/
|
|
8848
|
+
//#region src/catalogs/coco-classmap.ts
|
|
8849
|
+
var COCO_80_LABELS = [
|
|
8850
|
+
{
|
|
8851
|
+
id: "person",
|
|
8852
|
+
name: "Person"
|
|
8853
|
+
},
|
|
8854
|
+
{
|
|
8855
|
+
id: "bicycle",
|
|
8856
|
+
name: "Bicycle"
|
|
8857
|
+
},
|
|
8858
|
+
{
|
|
8859
|
+
id: "car",
|
|
8860
|
+
name: "Car"
|
|
8861
|
+
},
|
|
8862
|
+
{
|
|
8863
|
+
id: "motorcycle",
|
|
8864
|
+
name: "Motorcycle"
|
|
8865
|
+
},
|
|
8866
|
+
{
|
|
8867
|
+
id: "airplane",
|
|
8868
|
+
name: "Airplane"
|
|
8869
|
+
},
|
|
8870
|
+
{
|
|
8871
|
+
id: "bus",
|
|
8872
|
+
name: "Bus"
|
|
8873
|
+
},
|
|
8874
|
+
{
|
|
8875
|
+
id: "train",
|
|
8876
|
+
name: "Train"
|
|
8877
|
+
},
|
|
8878
|
+
{
|
|
8879
|
+
id: "truck",
|
|
8880
|
+
name: "Truck"
|
|
8881
|
+
},
|
|
8882
|
+
{
|
|
8883
|
+
id: "boat",
|
|
8884
|
+
name: "Boat"
|
|
8885
|
+
},
|
|
8886
|
+
{
|
|
8887
|
+
id: "traffic light",
|
|
8888
|
+
name: "Traffic Light"
|
|
8889
|
+
},
|
|
8890
|
+
{
|
|
8891
|
+
id: "fire hydrant",
|
|
8892
|
+
name: "Fire Hydrant"
|
|
8893
|
+
},
|
|
8894
|
+
{
|
|
8895
|
+
id: "stop sign",
|
|
8896
|
+
name: "Stop Sign"
|
|
8897
|
+
},
|
|
8898
|
+
{
|
|
8899
|
+
id: "parking meter",
|
|
8900
|
+
name: "Parking Meter"
|
|
8901
|
+
},
|
|
8902
|
+
{
|
|
8903
|
+
id: "bench",
|
|
8904
|
+
name: "Bench"
|
|
8905
|
+
},
|
|
8906
|
+
{
|
|
8907
|
+
id: "bird",
|
|
8908
|
+
name: "Bird"
|
|
8909
|
+
},
|
|
8910
|
+
{
|
|
8911
|
+
id: "cat",
|
|
8912
|
+
name: "Cat"
|
|
8913
|
+
},
|
|
8914
|
+
{
|
|
8915
|
+
id: "dog",
|
|
8916
|
+
name: "Dog"
|
|
8917
|
+
},
|
|
8918
|
+
{
|
|
8919
|
+
id: "horse",
|
|
8920
|
+
name: "Horse"
|
|
8921
|
+
},
|
|
8922
|
+
{
|
|
8923
|
+
id: "sheep",
|
|
8924
|
+
name: "Sheep"
|
|
8925
|
+
},
|
|
8926
|
+
{
|
|
8927
|
+
id: "cow",
|
|
8928
|
+
name: "Cow"
|
|
8929
|
+
},
|
|
8930
|
+
{
|
|
8931
|
+
id: "elephant",
|
|
8932
|
+
name: "Elephant"
|
|
8933
|
+
},
|
|
8934
|
+
{
|
|
8935
|
+
id: "bear",
|
|
8936
|
+
name: "Bear"
|
|
8937
|
+
},
|
|
8938
|
+
{
|
|
8939
|
+
id: "zebra",
|
|
8940
|
+
name: "Zebra"
|
|
8941
|
+
},
|
|
8942
|
+
{
|
|
8943
|
+
id: "giraffe",
|
|
8944
|
+
name: "Giraffe"
|
|
8945
|
+
},
|
|
8946
|
+
{
|
|
8947
|
+
id: "backpack",
|
|
8948
|
+
name: "Backpack"
|
|
8949
|
+
},
|
|
8950
|
+
{
|
|
8951
|
+
id: "umbrella",
|
|
8952
|
+
name: "Umbrella"
|
|
8953
|
+
},
|
|
8954
|
+
{
|
|
8955
|
+
id: "handbag",
|
|
8956
|
+
name: "Handbag"
|
|
8957
|
+
},
|
|
8958
|
+
{
|
|
8959
|
+
id: "tie",
|
|
8960
|
+
name: "Tie"
|
|
8961
|
+
},
|
|
8962
|
+
{
|
|
8963
|
+
id: "suitcase",
|
|
8964
|
+
name: "Suitcase"
|
|
8965
|
+
},
|
|
8966
|
+
{
|
|
8967
|
+
id: "frisbee",
|
|
8968
|
+
name: "Frisbee"
|
|
8969
|
+
},
|
|
8970
|
+
{
|
|
8971
|
+
id: "skis",
|
|
8972
|
+
name: "Skis"
|
|
8973
|
+
},
|
|
8974
|
+
{
|
|
8975
|
+
id: "snowboard",
|
|
8976
|
+
name: "Snowboard"
|
|
8977
|
+
},
|
|
8978
|
+
{
|
|
8979
|
+
id: "sports ball",
|
|
8980
|
+
name: "Sports Ball"
|
|
8981
|
+
},
|
|
8982
|
+
{
|
|
8983
|
+
id: "kite",
|
|
8984
|
+
name: "Kite"
|
|
8985
|
+
},
|
|
8986
|
+
{
|
|
8987
|
+
id: "baseball bat",
|
|
8988
|
+
name: "Baseball Bat"
|
|
8989
|
+
},
|
|
8990
|
+
{
|
|
8991
|
+
id: "baseball glove",
|
|
8992
|
+
name: "Baseball Glove"
|
|
8993
|
+
},
|
|
8994
|
+
{
|
|
8995
|
+
id: "skateboard",
|
|
8996
|
+
name: "Skateboard"
|
|
8997
|
+
},
|
|
8998
|
+
{
|
|
8999
|
+
id: "surfboard",
|
|
9000
|
+
name: "Surfboard"
|
|
9001
|
+
},
|
|
9002
|
+
{
|
|
9003
|
+
id: "tennis racket",
|
|
9004
|
+
name: "Tennis Racket"
|
|
9005
|
+
},
|
|
9006
|
+
{
|
|
9007
|
+
id: "bottle",
|
|
9008
|
+
name: "Bottle"
|
|
9009
|
+
},
|
|
9010
|
+
{
|
|
9011
|
+
id: "wine glass",
|
|
9012
|
+
name: "Wine Glass"
|
|
9013
|
+
},
|
|
9014
|
+
{
|
|
9015
|
+
id: "cup",
|
|
9016
|
+
name: "Cup"
|
|
9017
|
+
},
|
|
9018
|
+
{
|
|
9019
|
+
id: "fork",
|
|
9020
|
+
name: "Fork"
|
|
9021
|
+
},
|
|
9022
|
+
{
|
|
9023
|
+
id: "knife",
|
|
9024
|
+
name: "Knife"
|
|
9025
|
+
},
|
|
9026
|
+
{
|
|
9027
|
+
id: "spoon",
|
|
9028
|
+
name: "Spoon"
|
|
9029
|
+
},
|
|
9030
|
+
{
|
|
9031
|
+
id: "bowl",
|
|
9032
|
+
name: "Bowl"
|
|
9033
|
+
},
|
|
9034
|
+
{
|
|
9035
|
+
id: "banana",
|
|
9036
|
+
name: "Banana"
|
|
9037
|
+
},
|
|
9038
|
+
{
|
|
9039
|
+
id: "apple",
|
|
9040
|
+
name: "Apple"
|
|
9041
|
+
},
|
|
9042
|
+
{
|
|
9043
|
+
id: "sandwich",
|
|
9044
|
+
name: "Sandwich"
|
|
9045
|
+
},
|
|
9046
|
+
{
|
|
9047
|
+
id: "orange",
|
|
9048
|
+
name: "Orange"
|
|
9049
|
+
},
|
|
9050
|
+
{
|
|
9051
|
+
id: "broccoli",
|
|
9052
|
+
name: "Broccoli"
|
|
9053
|
+
},
|
|
9054
|
+
{
|
|
9055
|
+
id: "carrot",
|
|
9056
|
+
name: "Carrot"
|
|
9057
|
+
},
|
|
9058
|
+
{
|
|
9059
|
+
id: "hot dog",
|
|
9060
|
+
name: "Hot Dog"
|
|
9061
|
+
},
|
|
9062
|
+
{
|
|
9063
|
+
id: "pizza",
|
|
9064
|
+
name: "Pizza"
|
|
9065
|
+
},
|
|
9066
|
+
{
|
|
9067
|
+
id: "donut",
|
|
9068
|
+
name: "Donut"
|
|
9069
|
+
},
|
|
9070
|
+
{
|
|
9071
|
+
id: "cake",
|
|
9072
|
+
name: "Cake"
|
|
9073
|
+
},
|
|
9074
|
+
{
|
|
9075
|
+
id: "chair",
|
|
9076
|
+
name: "Chair"
|
|
9077
|
+
},
|
|
9078
|
+
{
|
|
9079
|
+
id: "couch",
|
|
9080
|
+
name: "Couch"
|
|
9081
|
+
},
|
|
9082
|
+
{
|
|
9083
|
+
id: "potted plant",
|
|
9084
|
+
name: "Potted Plant"
|
|
9085
|
+
},
|
|
9086
|
+
{
|
|
9087
|
+
id: "bed",
|
|
9088
|
+
name: "Bed"
|
|
9089
|
+
},
|
|
9090
|
+
{
|
|
9091
|
+
id: "dining table",
|
|
9092
|
+
name: "Dining Table"
|
|
9093
|
+
},
|
|
9094
|
+
{
|
|
9095
|
+
id: "toilet",
|
|
9096
|
+
name: "Toilet"
|
|
9097
|
+
},
|
|
9098
|
+
{
|
|
9099
|
+
id: "tv",
|
|
9100
|
+
name: "TV"
|
|
9101
|
+
},
|
|
9102
|
+
{
|
|
9103
|
+
id: "laptop",
|
|
9104
|
+
name: "Laptop"
|
|
9105
|
+
},
|
|
9106
|
+
{
|
|
9107
|
+
id: "mouse",
|
|
9108
|
+
name: "Mouse"
|
|
9109
|
+
},
|
|
9110
|
+
{
|
|
9111
|
+
id: "remote",
|
|
9112
|
+
name: "Remote"
|
|
9113
|
+
},
|
|
9114
|
+
{
|
|
9115
|
+
id: "keyboard",
|
|
9116
|
+
name: "Keyboard"
|
|
9117
|
+
},
|
|
9118
|
+
{
|
|
9119
|
+
id: "cell phone",
|
|
9120
|
+
name: "Cell Phone"
|
|
9121
|
+
},
|
|
9122
|
+
{
|
|
9123
|
+
id: "microwave",
|
|
9124
|
+
name: "Microwave"
|
|
9125
|
+
},
|
|
9126
|
+
{
|
|
9127
|
+
id: "oven",
|
|
9128
|
+
name: "Oven"
|
|
9129
|
+
},
|
|
9130
|
+
{
|
|
9131
|
+
id: "toaster",
|
|
9132
|
+
name: "Toaster"
|
|
9133
|
+
},
|
|
9134
|
+
{
|
|
9135
|
+
id: "sink",
|
|
9136
|
+
name: "Sink"
|
|
9137
|
+
},
|
|
9138
|
+
{
|
|
9139
|
+
id: "refrigerator",
|
|
9140
|
+
name: "Refrigerator"
|
|
9141
|
+
},
|
|
9142
|
+
{
|
|
9143
|
+
id: "book",
|
|
9144
|
+
name: "Book"
|
|
9145
|
+
},
|
|
9146
|
+
{
|
|
9147
|
+
id: "clock",
|
|
9148
|
+
name: "Clock"
|
|
9149
|
+
},
|
|
9150
|
+
{
|
|
9151
|
+
id: "vase",
|
|
9152
|
+
name: "Vase"
|
|
9153
|
+
},
|
|
9154
|
+
{
|
|
9155
|
+
id: "scissors",
|
|
9156
|
+
name: "Scissors"
|
|
9157
|
+
},
|
|
9158
|
+
{
|
|
9159
|
+
id: "teddy bear",
|
|
9160
|
+
name: "Teddy Bear"
|
|
9161
|
+
},
|
|
9162
|
+
{
|
|
9163
|
+
id: "hair drier",
|
|
9164
|
+
name: "Hair Drier"
|
|
9165
|
+
},
|
|
9166
|
+
{
|
|
9167
|
+
id: "toothbrush",
|
|
9168
|
+
name: "Toothbrush"
|
|
9169
|
+
}
|
|
9170
|
+
];
|
|
9171
|
+
var MACRO_LABELS = [
|
|
9172
|
+
{
|
|
9173
|
+
id: "person",
|
|
9174
|
+
name: "Person"
|
|
9175
|
+
},
|
|
9176
|
+
{
|
|
9177
|
+
id: "vehicle",
|
|
9178
|
+
name: "Vehicle"
|
|
9179
|
+
},
|
|
9180
|
+
{
|
|
9181
|
+
id: "animal",
|
|
9182
|
+
name: "Animal"
|
|
9183
|
+
},
|
|
9184
|
+
{
|
|
9185
|
+
id: "package",
|
|
9186
|
+
name: "Package"
|
|
9187
|
+
}
|
|
9188
|
+
];
|
|
8849
9189
|
/**
|
|
8850
|
-
* The
|
|
8851
|
-
*
|
|
9190
|
+
* The class names a per-class COUNT can ever be keyed on — the macro ids, and
|
|
9191
|
+
* only those.
|
|
8852
9192
|
*
|
|
8853
|
-
*
|
|
8854
|
-
*
|
|
8855
|
-
*
|
|
8856
|
-
*
|
|
8857
|
-
*
|
|
8858
|
-
*
|
|
9193
|
+
* `preserveOriginal: false` below is the whole reason this set exists: the
|
|
9194
|
+
* executor maps each raw COCO label onto its macro and DROPS everything else,
|
|
9195
|
+
* so a tracked detection's `className` is always one of {@link MACRO_LABELS}.
|
|
9196
|
+
* Any surface that indexes counts by class name — the ZoneAnalytics occupancy
|
|
9197
|
+
* snapshot's `byClass`, and therefore every occupancy rule — can match nothing
|
|
9198
|
+
* else. Derived from the catalog, never hand-listed, so a new macro joins it by
|
|
9199
|
+
* being declared once.
|
|
9200
|
+
*
|
|
9201
|
+
* Cost of not having it, 2026-08-12: the notification rule editor offered the
|
|
9202
|
+
* full taxonomy for an occupancy class (occupancy is the one condition that
|
|
9203
|
+
* does not go through the engine's macro-expanding `expandClassSelector`), two
|
|
9204
|
+
* live rules were authored on `car`, and both counters read 0 forever.
|
|
9205
|
+
*/
|
|
9206
|
+
var DETECTION_MACRO_CLASSES = new Set(MACRO_LABELS.map((l) => l.id));
|
|
9207
|
+
/** True when `className` is a macro the detector can actually emit (and hence a
|
|
9208
|
+
* class a count can be keyed on) — see {@link DETECTION_MACRO_CLASSES}. */
|
|
9209
|
+
function isDetectionMacroClass(className) {
|
|
9210
|
+
return DETECTION_MACRO_CLASSES.has(className.trim().toLowerCase());
|
|
9211
|
+
}
|
|
9212
|
+
var COCO_TO_MACRO = {
|
|
9213
|
+
mapping: {
|
|
9214
|
+
person: "person",
|
|
9215
|
+
bicycle: "vehicle",
|
|
9216
|
+
car: "vehicle",
|
|
9217
|
+
motorcycle: "vehicle",
|
|
9218
|
+
airplane: "vehicle",
|
|
9219
|
+
bus: "vehicle",
|
|
9220
|
+
train: "vehicle",
|
|
9221
|
+
truck: "vehicle",
|
|
9222
|
+
boat: "vehicle",
|
|
9223
|
+
bird: "animal",
|
|
9224
|
+
cat: "animal",
|
|
9225
|
+
dog: "animal",
|
|
9226
|
+
horse: "animal",
|
|
9227
|
+
sheep: "animal",
|
|
9228
|
+
cow: "animal",
|
|
9229
|
+
elephant: "animal",
|
|
9230
|
+
bear: "animal",
|
|
9231
|
+
zebra: "animal",
|
|
9232
|
+
giraffe: "animal",
|
|
9233
|
+
suitcase: "package",
|
|
9234
|
+
backpack: "package",
|
|
9235
|
+
handbag: "package"
|
|
9236
|
+
},
|
|
9237
|
+
preserveOriginal: false
|
|
9238
|
+
};
|
|
9239
|
+
//#endregion
|
|
9240
|
+
//#region src/automation/detection-subject.ts
|
|
9241
|
+
/**
|
|
9242
|
+
* WHAT a detection-shaped leaf is about: one MACRO class (person, vehicle,
|
|
9243
|
+
* animal, package) plus an OPTIONAL label that narrows it.
|
|
8859
9244
|
*
|
|
8860
|
-
*
|
|
9245
|
+
* The taxonomy is `COCO_TO_MACRO` (and `CLASS_MAP_MACRO_TARGETS`) — never a
|
|
9246
|
+
* second list. The label that fits depends on the macro class:
|
|
8861
9247
|
*
|
|
8862
|
-
* -
|
|
8863
|
-
*
|
|
8864
|
-
*
|
|
8865
|
-
* -
|
|
8866
|
-
*
|
|
8867
|
-
*
|
|
8868
|
-
*
|
|
8869
|
-
*
|
|
8870
|
-
*
|
|
9248
|
+
* - `animal` → a sub-class (`dog`, `cat`, …);
|
|
9249
|
+
* - `person` → a known face (an identity of the `face-gallery`);
|
|
9250
|
+
* - `vehicle` → a plate (its text) or a sub-class (`car`, `truck`, …);
|
|
9251
|
+
* - `package` → a sub-class.
|
|
9252
|
+
*
|
|
9253
|
+
* `other` exists only so a class the taxonomy does not know (a custom model's
|
|
9254
|
+
* `forklift`) survives the conversion of a stored recipe without widening.
|
|
9255
|
+
*
|
|
9256
|
+
* A label is decided LATE (D683): face recognition and LPR land after the
|
|
9257
|
+
* detection starts. The track-lifecycle `update` event carries the newly
|
|
9258
|
+
* resolved `label` / `plateText`, which is why a leaf with a label fires when
|
|
9259
|
+
* the subject GETS it — not at the first detection.
|
|
9260
|
+
*/
|
|
9261
|
+
var SubClassLabelSchema = zod.z.object({
|
|
9262
|
+
kind: zod.z.literal("sub-class"),
|
|
9263
|
+
className: zod.z.string().min(1)
|
|
9264
|
+
});
|
|
9265
|
+
var IdentityLabelSchema = zod.z.object({
|
|
9266
|
+
kind: zod.z.literal("identity"),
|
|
9267
|
+
/** The `face-gallery` identity id (stable across a rename). */
|
|
9268
|
+
identityId: zod.z.string().min(1),
|
|
9269
|
+
/** The identity's name — what a track-lifecycle event carries as `label`. */
|
|
9270
|
+
name: zod.z.string().min(1)
|
|
9271
|
+
});
|
|
9272
|
+
var PlateLabelSchema = zod.z.object({
|
|
9273
|
+
kind: zod.z.literal("plate"),
|
|
9274
|
+
text: zod.z.string().min(1)
|
|
9275
|
+
});
|
|
9276
|
+
function macroOf$1(className) {
|
|
9277
|
+
return COCO_TO_MACRO.mapping[className.toLowerCase()] ?? className.toLowerCase();
|
|
9278
|
+
}
|
|
9279
|
+
/** A sub-class label must belong to the macro it is under. */
|
|
9280
|
+
function subClassBelongs(macro, label) {
|
|
9281
|
+
return label === void 0 || macroOf$1(label.className) === macro;
|
|
9282
|
+
}
|
|
9283
|
+
var subClassOf = (macro) => SubClassLabelSchema.refine((l) => subClassBelongs(macro, l), { message: `that class is not a kind of ${macro} in the detection taxonomy` });
|
|
9284
|
+
var DetectionSubjectSchema = zod.z.discriminatedUnion("macro", [
|
|
9285
|
+
zod.z.object({
|
|
9286
|
+
macro: zod.z.literal("animal"),
|
|
9287
|
+
label: subClassOf("animal").optional()
|
|
9288
|
+
}),
|
|
9289
|
+
zod.z.object({
|
|
9290
|
+
macro: zod.z.literal("person"),
|
|
9291
|
+
label: IdentityLabelSchema.optional()
|
|
9292
|
+
}),
|
|
9293
|
+
zod.z.object({
|
|
9294
|
+
macro: zod.z.literal("vehicle"),
|
|
9295
|
+
label: zod.z.discriminatedUnion("kind", [SubClassLabelSchema.refine((l) => subClassBelongs("vehicle", l), { message: "that class is not a kind of vehicle in the detection taxonomy" }), PlateLabelSchema]).optional()
|
|
9296
|
+
}),
|
|
9297
|
+
zod.z.object({
|
|
9298
|
+
macro: zod.z.literal("package"),
|
|
9299
|
+
label: subClassOf("package").optional()
|
|
9300
|
+
}),
|
|
9301
|
+
zod.z.object({
|
|
9302
|
+
macro: zod.z.literal("other"),
|
|
9303
|
+
className: zod.z.string().min(1)
|
|
9304
|
+
})
|
|
9305
|
+
]);
|
|
9306
|
+
/**
|
|
9307
|
+
* The subject of a PARKED object. The stationary registry's event carries a
|
|
9308
|
+
* class and a zone, no face and no plate — an identity or plate label there
|
|
9309
|
+
* could never match, so the schema refuses it instead of letting it sit dead.
|
|
9310
|
+
*/
|
|
9311
|
+
var StationarySubjectSchema = DetectionSubjectSchema.refine((s) => s.macro === "other" || s.label === void 0 || s.label.kind === "sub-class", { message: "a parked object carries no face or plate — only a class can narrow it" });
|
|
9312
|
+
/**
|
|
9313
|
+
* Where an editor lists the labels of a macro class. Sub-classes come from the
|
|
9314
|
+
* taxonomy (`subClassesOf`); identities and plates come from the galleries'
|
|
9315
|
+
* own methods, so a picker reads the cap and never a copy of it.
|
|
9316
|
+
*/
|
|
9317
|
+
var DETECTION_LABEL_SOURCES = {
|
|
9318
|
+
animal: [{ kind: "sub-class" }],
|
|
9319
|
+
person: [{
|
|
9320
|
+
kind: "cap",
|
|
9321
|
+
cap: "face-gallery",
|
|
9322
|
+
method: "listIdentities",
|
|
9323
|
+
label: "identity"
|
|
9324
|
+
}],
|
|
9325
|
+
vehicle: [{
|
|
9326
|
+
kind: "cap",
|
|
9327
|
+
cap: "plate-gallery",
|
|
9328
|
+
method: "listPlates",
|
|
9329
|
+
label: "plate"
|
|
9330
|
+
}, { kind: "sub-class" }],
|
|
9331
|
+
package: [{ kind: "sub-class" }]
|
|
9332
|
+
};
|
|
9333
|
+
/** The raw classes the taxonomy files under a macro class. */
|
|
9334
|
+
function subClassesOf(macro) {
|
|
9335
|
+
return Object.entries(COCO_TO_MACRO.mapping).filter(([, m]) => m === macro).map(([raw]) => raw);
|
|
9336
|
+
}
|
|
9337
|
+
/**
|
|
9338
|
+
* A stored class string as a subject: a macro name stays a macro; a raw class
|
|
9339
|
+
* the taxonomy files under a macro becomes that macro plus the sub-class label
|
|
9340
|
+
* (`dog` → animal / dog); anything else is `other`, kept verbatim.
|
|
9341
|
+
*/
|
|
9342
|
+
function subjectFromClassName(className) {
|
|
9343
|
+
const name = className.trim().toLowerCase();
|
|
9344
|
+
const macro = COCO_TO_MACRO.mapping[name];
|
|
9345
|
+
if (new Set(Object.values(COCO_TO_MACRO.mapping)).has(name)) return macroOnly(name);
|
|
9346
|
+
if (macro === void 0) return {
|
|
9347
|
+
macro: "other",
|
|
9348
|
+
className
|
|
9349
|
+
};
|
|
9350
|
+
const label = {
|
|
9351
|
+
kind: "sub-class",
|
|
9352
|
+
className: name
|
|
9353
|
+
};
|
|
9354
|
+
switch (macro) {
|
|
9355
|
+
case "animal": return {
|
|
9356
|
+
macro: "animal",
|
|
9357
|
+
label
|
|
9358
|
+
};
|
|
9359
|
+
case "vehicle": return {
|
|
9360
|
+
macro: "vehicle",
|
|
9361
|
+
label
|
|
9362
|
+
};
|
|
9363
|
+
case "package": return {
|
|
9364
|
+
macro: "package",
|
|
9365
|
+
label
|
|
9366
|
+
};
|
|
9367
|
+
default: return {
|
|
9368
|
+
macro: "other",
|
|
9369
|
+
className
|
|
9370
|
+
};
|
|
9371
|
+
}
|
|
9372
|
+
}
|
|
9373
|
+
function macroOnly(macro) {
|
|
9374
|
+
switch (macro) {
|
|
9375
|
+
case "animal": return { macro: "animal" };
|
|
9376
|
+
case "person": return { macro: "person" };
|
|
9377
|
+
case "vehicle": return { macro: "vehicle" };
|
|
9378
|
+
case "package": return { macro: "package" };
|
|
9379
|
+
default: return {
|
|
9380
|
+
macro: "other",
|
|
9381
|
+
className: macro
|
|
9382
|
+
};
|
|
9383
|
+
}
|
|
9384
|
+
}
|
|
9385
|
+
function normalisePlate(text) {
|
|
9386
|
+
return text.toUpperCase().replace(/[^A-Z0-9]/g, "");
|
|
9387
|
+
}
|
|
9388
|
+
/** A human name for a subject, for a reason line. */
|
|
9389
|
+
function describeSubject(subject) {
|
|
9390
|
+
if (subject.macro === "other") return subject.className;
|
|
9391
|
+
const label = subject.label;
|
|
9392
|
+
if (label === void 0) return subject.macro;
|
|
9393
|
+
switch (label.kind) {
|
|
9394
|
+
case "sub-class": return `${subject.macro} ${label.className}`;
|
|
9395
|
+
case "identity": return `${subject.macro} ${label.name}`;
|
|
9396
|
+
case "plate": return `${subject.macro} plate ${label.text}`;
|
|
9397
|
+
}
|
|
9398
|
+
}
|
|
9399
|
+
/** Does one observation satisfy one subject, and which label did it match? */
|
|
9400
|
+
function subjectMatches(subject, observed) {
|
|
9401
|
+
const className = observed.className.toLowerCase();
|
|
9402
|
+
if (subject.macro === "other") return className === subject.className.toLowerCase() ? {
|
|
9403
|
+
matches: true,
|
|
9404
|
+
label: ""
|
|
9405
|
+
} : {
|
|
9406
|
+
matches: false,
|
|
9407
|
+
reason: `class ${observed.className || "unknown"} is not ${subject.className}`
|
|
9408
|
+
};
|
|
9409
|
+
if (macroOf$1(className) !== subject.macro) return {
|
|
9410
|
+
matches: false,
|
|
9411
|
+
reason: `class ${observed.className || "unknown"} is not a ${subject.macro}`
|
|
9412
|
+
};
|
|
9413
|
+
const label = subject.label;
|
|
9414
|
+
if (label === void 0) return {
|
|
9415
|
+
matches: true,
|
|
9416
|
+
label: ""
|
|
9417
|
+
};
|
|
9418
|
+
switch (label.kind) {
|
|
9419
|
+
case "sub-class": {
|
|
9420
|
+
const wanted = label.className.toLowerCase();
|
|
9421
|
+
return [
|
|
9422
|
+
className,
|
|
9423
|
+
...observed.classes ?? [],
|
|
9424
|
+
observed.label ?? ""
|
|
9425
|
+
].map((c) => c.toLowerCase()).includes(wanted) ? {
|
|
9426
|
+
matches: true,
|
|
9427
|
+
label: label.className
|
|
9428
|
+
} : {
|
|
9429
|
+
matches: false,
|
|
9430
|
+
reason: `${subject.macro} ${label.className} not seen yet`
|
|
9431
|
+
};
|
|
9432
|
+
}
|
|
9433
|
+
case "identity": {
|
|
9434
|
+
const byId = observed.identityId !== void 0 && observed.identityId === label.identityId;
|
|
9435
|
+
const byName = observed.label !== void 0 && observed.label.toLowerCase() === label.name.toLowerCase();
|
|
9436
|
+
return byId || byName ? {
|
|
9437
|
+
matches: true,
|
|
9438
|
+
label: label.name
|
|
9439
|
+
} : {
|
|
9440
|
+
matches: false,
|
|
9441
|
+
reason: `face ${label.name} not recognised yet${observed.label === void 0 || observed.label === "" ? "" : ` (seen as ${observed.label})`}`
|
|
9442
|
+
};
|
|
9443
|
+
}
|
|
9444
|
+
case "plate": {
|
|
9445
|
+
const text = observed.plateText ?? observed.label ?? "";
|
|
9446
|
+
return text !== "" && normalisePlate(text) === normalisePlate(label.text) ? {
|
|
9447
|
+
matches: true,
|
|
9448
|
+
label: label.text
|
|
9449
|
+
} : {
|
|
9450
|
+
matches: false,
|
|
9451
|
+
reason: `plate ${label.text} not read yet${text === "" ? "" : ` (read ${text})`}`
|
|
9452
|
+
};
|
|
9453
|
+
}
|
|
9454
|
+
}
|
|
9455
|
+
}
|
|
9456
|
+
/** The first subject an observation satisfies, else why the last one did not. */
|
|
9457
|
+
function anySubjectMatches(subjects, observed) {
|
|
9458
|
+
let lastReason = "no subject listed";
|
|
9459
|
+
for (const subject of subjects) {
|
|
9460
|
+
const verdict = subjectMatches(subject, observed);
|
|
9461
|
+
if (verdict.matches) return verdict;
|
|
9462
|
+
lastReason = verdict.reason;
|
|
9463
|
+
}
|
|
9464
|
+
return {
|
|
9465
|
+
matches: false,
|
|
9466
|
+
reason: subjects.length === 1 ? lastReason : `none of [${subjects.map(describeSubject).join(", ")}] matches (${observed.className || "unknown"})`
|
|
9467
|
+
};
|
|
9468
|
+
}
|
|
9469
|
+
//#endregion
|
|
9470
|
+
//#region src/automation/when.ts
|
|
9471
|
+
/**
|
|
9472
|
+
* The `when` tree — the ONE place an automation says when it runs.
|
|
9473
|
+
*
|
|
9474
|
+
* Containers are `all` / `any` / `not`. Leaves come in two kinds, told apart by
|
|
9475
|
+
* what they ARE, not by which section of a form they sit in:
|
|
9476
|
+
*
|
|
9477
|
+
* - **event leaves (instants)** — something HAPPENS: a device field *becomes*
|
|
9478
|
+
* a value, a device event, a schedule tick, an interval, a manual run, a
|
|
9479
|
+
* detection, a parked object appearing or leaving;
|
|
9480
|
+
* - **state leaves (durations)** — something IS true: a device field *is*
|
|
9481
|
+
* compared to a value, a parked object is present, an expression holds.
|
|
8871
9482
|
*
|
|
8872
|
-
*
|
|
8873
|
-
*
|
|
8874
|
-
*
|
|
9483
|
+
* Firing rule (ADR, "one tree"):
|
|
9484
|
+
* 1. A tree with event leaves fires when ONE of them occurs AND the whole tree
|
|
9485
|
+
* is true at that instant. The event that occurred is true; every other
|
|
9486
|
+
* event leaf is false.
|
|
9487
|
+
* 2. A tree of state leaves only fires when it goes false → true. The first
|
|
9488
|
+
* evaluation seeds the state, so it never fires because it was already true.
|
|
9489
|
+
* 3. Two event leaves that can only be satisfied together are refused at
|
|
9490
|
+
* author time ({@link validateWhenTree}).
|
|
8875
9491
|
*/
|
|
8876
9492
|
/**
|
|
8877
|
-
* Depth of
|
|
9493
|
+
* Depth of the `when` tree, counting the root container as level 1. The old
|
|
9494
|
+
* condition tree was bounded at 3 under a flat list of triggers; the trigger
|
|
9495
|
+
* list is now the tree's top `any`, one level, so the bound is 3 + 1 and a
|
|
9496
|
+
* converted recipe never exceeds it.
|
|
8878
9497
|
*
|
|
8879
|
-
*
|
|
8880
|
-
*
|
|
8881
|
-
* need a fourth level have an equivalent at three. An editor that refuses depth
|
|
8882
|
-
* 4 should offer that rewrite rather than refusing blankly.
|
|
9498
|
+
* `NOT` over a group is always rewritable by De Morgan, so the shapes that
|
|
9499
|
+
* would need a fifth level have an equivalent at four.
|
|
8883
9500
|
*/
|
|
8884
|
-
var
|
|
9501
|
+
var MAX_WHEN_DEPTH = 4;
|
|
8885
9502
|
/** Leaves per automation. A UI that grows without a bound becomes a language. */
|
|
8886
|
-
var
|
|
8887
|
-
/** How a leaf compares a device field to a value. Derived from the
|
|
8888
|
-
* `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
|
|
9503
|
+
var MAX_WHEN_LEAVES = 32;
|
|
9504
|
+
/** How a state leaf compares a device field to a value. Derived from the
|
|
9505
|
+
* field's `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
|
|
8889
9506
|
var AutomationConditionOperatorSchema = zod.z.enum([
|
|
8890
9507
|
"eq",
|
|
8891
9508
|
"ne",
|
|
@@ -8896,12 +9513,76 @@ var AutomationConditionOperatorSchema = zod.z.enum([
|
|
|
8896
9513
|
"contains",
|
|
8897
9514
|
"in"
|
|
8898
9515
|
]);
|
|
8899
|
-
|
|
8900
|
-
|
|
9516
|
+
/**
|
|
9517
|
+
* D8 compliance, and it is the reason a "becomes" leaf is not merely an event
|
|
9518
|
+
* subscription: it evaluates against the **state mirror**, which is reconciled,
|
|
9519
|
+
* and an event only WAKES the evaluation. A dropped event therefore DELAYS a
|
|
9520
|
+
* fire; it does not lose it.
|
|
9521
|
+
*/
|
|
9522
|
+
var DeviceBecomesLeafSchema = zod.z.object({
|
|
9523
|
+
kind: zod.z.literal("device-becomes"),
|
|
9524
|
+
deviceId: zod.z.number().int().nonnegative(),
|
|
9525
|
+
cap: zod.z.string().min(1),
|
|
9526
|
+
fieldPath: zod.z.string().min(1),
|
|
9527
|
+
/** Fire when the field takes this value. Omit to fire on any change. */
|
|
9528
|
+
becomes: zod.z.union([
|
|
9529
|
+
zod.z.string(),
|
|
9530
|
+
zod.z.number(),
|
|
9531
|
+
zod.z.boolean()
|
|
9532
|
+
]).optional(),
|
|
9533
|
+
/** Only on a CHANGE of value, not on every re-report. */
|
|
9534
|
+
edge: zod.z.boolean().optional(),
|
|
9535
|
+
/** The new value must hold this long before it counts. */
|
|
9536
|
+
forMs: zod.z.number().int().min(0).max(864e5).optional(),
|
|
9537
|
+
/** Collapse a burst into one occurrence. */
|
|
9538
|
+
debounceMs: zod.z.number().int().min(0).max(6e5).optional()
|
|
9539
|
+
});
|
|
9540
|
+
var DeviceEventLeafSchema = zod.z.object({
|
|
9541
|
+
kind: zod.z.literal("device-event"),
|
|
9542
|
+
/** An `EventCategory` value. */
|
|
9543
|
+
category: zod.z.string().min(1),
|
|
9544
|
+
deviceId: zod.z.number().int().nonnegative().optional()
|
|
9545
|
+
});
|
|
9546
|
+
var ScheduleLeafSchema = zod.z.object({
|
|
9547
|
+
kind: zod.z.literal("schedule"),
|
|
9548
|
+
cron: zod.z.string().min(1).max(120)
|
|
9549
|
+
});
|
|
9550
|
+
var ManualLeafSchema = zod.z.object({ kind: zod.z.literal("manual") });
|
|
9551
|
+
var IntervalLeafSchema = zod.z.object({
|
|
9552
|
+
kind: zod.z.literal("interval"),
|
|
9553
|
+
minutes: zod.z.number().int().min(1).max(1440)
|
|
9554
|
+
});
|
|
9555
|
+
var DetectionLeafSchema = zod.z.object({
|
|
9556
|
+
kind: zod.z.literal("detection"),
|
|
9557
|
+
/** One macro class each, optionally narrowed by a label; a track of any listed subject fires. */
|
|
9558
|
+
subjects: zod.z.array(DetectionSubjectSchema).min(1),
|
|
9559
|
+
deviceIds: zod.z.array(zod.z.number().int().nonnegative()).min(1),
|
|
9560
|
+
/** The track must have visited at least one of these zones. */
|
|
9561
|
+
zones: zod.z.array(zod.z.string().min(1)).min(1).optional(),
|
|
9562
|
+
/** Best-so-far confidence the track must reach. */
|
|
9563
|
+
minScore: zod.z.number().min(0).max(1).optional()
|
|
9564
|
+
});
|
|
9565
|
+
var StationaryLeafSchema = zod.z.object({
|
|
9566
|
+
kind: zod.z.literal("stationary"),
|
|
9567
|
+
deviceIds: zod.z.array(zod.z.number().int().nonnegative()).min(1),
|
|
9568
|
+
subjects: zod.z.array(StationarySubjectSchema).min(1).optional(),
|
|
9569
|
+
zones: zod.z.array(zod.z.string().min(1)).min(1).optional(),
|
|
9570
|
+
/** `appeared`: it became parked. `departed`: the registry retired it. */
|
|
9571
|
+
phase: zod.z.enum(["appeared", "departed"])
|
|
9572
|
+
});
|
|
9573
|
+
/** "is": one device field compared to one value, NOW. */
|
|
9574
|
+
var DeviceIsLeafSchema = zod.z.object({
|
|
9575
|
+
kind: zod.z.literal("device-is"),
|
|
8901
9576
|
deviceId: zod.z.number().int().nonnegative(),
|
|
8902
9577
|
cap: zod.z.string().min(1),
|
|
8903
9578
|
fieldPath: zod.z.string().min(1),
|
|
8904
9579
|
operator: AutomationConditionOperatorSchema,
|
|
9580
|
+
/**
|
|
9581
|
+
* The value. A number on a quantity field is stored in the field's base unit
|
|
9582
|
+
* (D944); a quantity value is the WRITE form, which the save boundary
|
|
9583
|
+
* replaces with the converted number and `unit`. A leaf that still carries
|
|
9584
|
+
* one is never evaluated (it fails).
|
|
9585
|
+
*/
|
|
8905
9586
|
value: zod.z.union([
|
|
8906
9587
|
zod.z.string(),
|
|
8907
9588
|
zod.z.number(),
|
|
@@ -8909,24 +9590,26 @@ var AutomationConditionLeafSchema = zod.z.object({
|
|
|
8909
9590
|
zod.z.array(zod.z.union([zod.z.string(), zod.z.number()])),
|
|
8910
9591
|
QuantityValueSchema
|
|
8911
9592
|
]),
|
|
9593
|
+
/** The unit the person picked for `value` (display only; `value` is in the base unit). */
|
|
8912
9594
|
unit: zod.z.string().min(1).optional()
|
|
8913
9595
|
});
|
|
8914
|
-
|
|
9596
|
+
/**
|
|
9597
|
+
* True when the camera currently has a PARKED object matching `subjects` in at
|
|
9598
|
+
* least one of `zones`. Reads the zone-analytics snapshot through the same
|
|
9599
|
+
* mirror as every other state leaf, so a dropped event never decides it (D49).
|
|
9600
|
+
*/
|
|
9601
|
+
var StationaryPresentLeafSchema = zod.z.object({
|
|
8915
9602
|
kind: zod.z.literal("stationary-present"),
|
|
8916
9603
|
deviceId: zod.z.number().int().nonnegative(),
|
|
8917
|
-
|
|
8918
|
-
classes: zod.z.array(zod.z.string().min(1)).min(1).optional(),
|
|
8919
|
-
/** The parked object must be in at least one of these zones. */
|
|
9604
|
+
subjects: zod.z.array(StationarySubjectSchema).min(1).optional(),
|
|
8920
9605
|
zones: zod.z.array(zod.z.string().min(1)).min(1).optional()
|
|
8921
9606
|
});
|
|
8922
9607
|
/**
|
|
8923
|
-
* The
|
|
8924
|
-
*
|
|
8925
|
-
*
|
|
8926
|
-
* validation is the SAME function `ExpressionSourceSchema` runs, so the two
|
|
8927
|
-
* cannot drift — an expression that one accepts, the other accepts.
|
|
9608
|
+
* The escape hatch: the bounded expression engine over named device bindings.
|
|
9609
|
+
* Declared as a plain object so a discriminated union can read `kind`; the
|
|
9610
|
+
* author-time validation is the SAME function `ExpressionSourceSchema` runs.
|
|
8928
9611
|
*/
|
|
8929
|
-
var
|
|
9612
|
+
var ExpressionLeafSchema = zod.z.object({
|
|
8930
9613
|
kind: zod.z.literal("expression"),
|
|
8931
9614
|
expr: zod.z.string().min(1).max(require_composition.MAX_EXPRESSION_SOURCE_LENGTH),
|
|
8932
9615
|
bindings: zod.z.record(zod.z.string().regex(require_composition.EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
|
|
@@ -8938,55 +9621,180 @@ var AutomationConditionExpressionSchema = zod.z.object({
|
|
|
8938
9621
|
path: ["expr"]
|
|
8939
9622
|
});
|
|
8940
9623
|
});
|
|
8941
|
-
var
|
|
9624
|
+
var AutomationWhenSchema = zod.z.lazy(() => zod.z.discriminatedUnion("kind", [
|
|
8942
9625
|
zod.z.object({
|
|
8943
9626
|
kind: zod.z.literal("all"),
|
|
8944
|
-
children: zod.z.array(
|
|
9627
|
+
children: zod.z.array(AutomationWhenSchema)
|
|
8945
9628
|
}),
|
|
8946
9629
|
zod.z.object({
|
|
8947
9630
|
kind: zod.z.literal("any"),
|
|
8948
|
-
children: zod.z.array(
|
|
9631
|
+
children: zod.z.array(AutomationWhenSchema)
|
|
8949
9632
|
}),
|
|
8950
9633
|
zod.z.object({
|
|
8951
9634
|
kind: zod.z.literal("not"),
|
|
8952
|
-
child:
|
|
9635
|
+
child: AutomationWhenSchema
|
|
8953
9636
|
}),
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
9637
|
+
DeviceBecomesLeafSchema,
|
|
9638
|
+
DeviceEventLeafSchema,
|
|
9639
|
+
ScheduleLeafSchema,
|
|
9640
|
+
ManualLeafSchema,
|
|
9641
|
+
IntervalLeafSchema,
|
|
9642
|
+
DetectionLeafSchema,
|
|
9643
|
+
StationaryLeafSchema,
|
|
9644
|
+
DeviceIsLeafSchema,
|
|
9645
|
+
StationaryPresentLeafSchema,
|
|
9646
|
+
ExpressionLeafSchema
|
|
8957
9647
|
]));
|
|
9648
|
+
var EVENT_KINDS = new Set([
|
|
9649
|
+
"device-becomes",
|
|
9650
|
+
"device-event",
|
|
9651
|
+
"schedule",
|
|
9652
|
+
"manual",
|
|
9653
|
+
"interval",
|
|
9654
|
+
"detection",
|
|
9655
|
+
"stationary"
|
|
9656
|
+
]);
|
|
9657
|
+
function isEventLeaf(node) {
|
|
9658
|
+
return EVENT_KINDS.has(node.kind);
|
|
9659
|
+
}
|
|
9660
|
+
function isLeaf(node) {
|
|
9661
|
+
return node.kind !== "all" && node.kind !== "any" && node.kind !== "not";
|
|
9662
|
+
}
|
|
9663
|
+
/** Depth of a tree, root container = 1. A bare leaf is depth 1. */
|
|
9664
|
+
function whenDepth(node) {
|
|
9665
|
+
if (isLeaf(node)) return 1;
|
|
9666
|
+
if (node.kind === "not") return 1 + whenDepth(node.child);
|
|
9667
|
+
let deepest = 0;
|
|
9668
|
+
for (const child of node.children) deepest = Math.max(deepest, whenDepth(child));
|
|
9669
|
+
return 1 + deepest;
|
|
9670
|
+
}
|
|
9671
|
+
/** Leaves only — containers do not count toward the leaf budget. */
|
|
9672
|
+
function countWhenLeaves(node) {
|
|
9673
|
+
if (isLeaf(node)) return 1;
|
|
9674
|
+
if (node.kind === "not") return countWhenLeaves(node.child);
|
|
9675
|
+
let total = 0;
|
|
9676
|
+
for (const child of node.children) total += countWhenLeaves(child);
|
|
9677
|
+
return total;
|
|
9678
|
+
}
|
|
9679
|
+
/** Every leaf with its path (`when`, `when.0`, `when.0.1`, `when.0` under a `not`). */
|
|
9680
|
+
function forEachLeaf(node, visit, path = "when") {
|
|
9681
|
+
if (isLeaf(node)) {
|
|
9682
|
+
visit(node, path);
|
|
9683
|
+
return;
|
|
9684
|
+
}
|
|
9685
|
+
if (node.kind === "not") {
|
|
9686
|
+
forEachLeaf(node.child, visit, `${path}.0`);
|
|
9687
|
+
return;
|
|
9688
|
+
}
|
|
9689
|
+
node.children.forEach((child, i) => {
|
|
9690
|
+
forEachLeaf(child, visit, `${path}.${i}`);
|
|
9691
|
+
});
|
|
9692
|
+
}
|
|
9693
|
+
function hasEventLeaves(node) {
|
|
9694
|
+
let found = false;
|
|
9695
|
+
forEachLeaf(node, (leaf) => {
|
|
9696
|
+
if (isEventLeaf(leaf)) found = true;
|
|
9697
|
+
});
|
|
9698
|
+
return found;
|
|
9699
|
+
}
|
|
9700
|
+
function hasEmptyContainer(node) {
|
|
9701
|
+
if (isLeaf(node)) return false;
|
|
9702
|
+
if (node.kind === "not") return hasEmptyContainer(node.child);
|
|
9703
|
+
if (node.children.length === 0) return true;
|
|
9704
|
+
return node.children.some(hasEmptyContainer);
|
|
9705
|
+
}
|
|
9706
|
+
var LEAF_WORDS = {
|
|
9707
|
+
"device-becomes": "a device value that becomes",
|
|
9708
|
+
"device-event": "a device event",
|
|
9709
|
+
schedule: "a schedule",
|
|
9710
|
+
manual: "a manual run",
|
|
9711
|
+
interval: "an interval",
|
|
9712
|
+
detection: "a detection",
|
|
9713
|
+
stationary: "a parked object appearing or leaving"
|
|
9714
|
+
};
|
|
9715
|
+
function say(kind) {
|
|
9716
|
+
return LEAF_WORDS[kind] ?? kind;
|
|
9717
|
+
}
|
|
8958
9718
|
/**
|
|
8959
|
-
*
|
|
8960
|
-
*
|
|
8961
|
-
*
|
|
8962
|
-
*
|
|
8963
|
-
* reconciled, and an event only WAKES the evaluation. A dropped event therefore
|
|
8964
|
-
* DELAYS a trigger; it does not lose it. `schedule` uses `croner` — the one
|
|
8965
|
-
* already in the repo — because `setInterval(24h)` drifts and "at 23:30" does
|
|
8966
|
-
* not.
|
|
9719
|
+
* The cheapest set of event leaves that makes `node` true, with negation pushed
|
|
9720
|
+
* down to the leaves first (De Morgan), so `not(any(A, B))` and `all(not A,
|
|
9721
|
+
* not B)` are the same shape to the check. A negated event is its own error: an
|
|
9722
|
+
* event is an instant, "the event did not occur" is true at every other moment.
|
|
8967
9723
|
*/
|
|
8968
|
-
|
|
9724
|
+
function eventNeed(node, path, negated) {
|
|
9725
|
+
if (isLeaf(node)) {
|
|
9726
|
+
if (!isEventLeaf(node)) return { paths: [] };
|
|
9727
|
+
if (negated) return { error: `"not" is applied to ${say(node.kind)} (${path}) — an event is an instant, so "it did not happen" is true at every other moment and can never start a run. Negate a state ("is") instead, or remove the "not"` };
|
|
9728
|
+
return { paths: [{
|
|
9729
|
+
path,
|
|
9730
|
+
kind: node.kind
|
|
9731
|
+
}] };
|
|
9732
|
+
}
|
|
9733
|
+
if (node.kind === "not") return eventNeed(node.child, `${path}.0`, !negated);
|
|
9734
|
+
const conjunctive = node.kind === "all" !== negated;
|
|
9735
|
+
const parts = [];
|
|
9736
|
+
for (let i = 0; i < node.children.length; i += 1) {
|
|
9737
|
+
const child = node.children[i];
|
|
9738
|
+
if (child === void 0) continue;
|
|
9739
|
+
const need = eventNeed(child, `${path}.${i}`, negated);
|
|
9740
|
+
if ("error" in need) return need;
|
|
9741
|
+
parts.push(need);
|
|
9742
|
+
}
|
|
9743
|
+
if (conjunctive) return { paths: parts.flatMap((p) => p.paths) };
|
|
9744
|
+
let best;
|
|
9745
|
+
for (const part of parts) if (best === void 0 || part.paths.length < best.paths.length) best = part;
|
|
9746
|
+
return best ?? { paths: [] };
|
|
9747
|
+
}
|
|
9748
|
+
/**
|
|
9749
|
+
* Author-time bounds and sense check of a `when` tree. Returns `null` when it
|
|
9750
|
+
* is legal, else a message the editor shows verbatim.
|
|
9751
|
+
*/
|
|
9752
|
+
function validateWhenTree(when) {
|
|
9753
|
+
if (hasEmptyContainer(when)) return "a group is empty — group existing rows instead of leaving a container to fill";
|
|
9754
|
+
const depth = whenDepth(when);
|
|
9755
|
+
if (depth > 4) return `the rules nest ${depth} levels deep — the limit is 4. "not (any of …)" is the same as "all of (not …)", which is one level shallower`;
|
|
9756
|
+
const leaves = countWhenLeaves(when);
|
|
9757
|
+
if (leaves > 32) return `the rules use ${leaves} rows — the limit is 32`;
|
|
9758
|
+
const need = eventNeed(when, "when", false);
|
|
9759
|
+
if ("error" in need) return need.error;
|
|
9760
|
+
const [first, second] = need.paths;
|
|
9761
|
+
if (first !== void 0 && second !== void 0) return `${say(first.kind)} (${first.path}) and ${say(second.kind)} (${second.path}) would both have to happen at the same instant, and an event happens at one moment — this can never run. Use "any of" to start on either, or turn one of them into a state ("is") that must already hold`;
|
|
9762
|
+
return null;
|
|
9763
|
+
}
|
|
9764
|
+
//#endregion
|
|
9765
|
+
//#region src/automation/legacy-recipe.ts
|
|
9766
|
+
/**
|
|
9767
|
+
* The recipe shape before the single `when` tree: a flat list of triggers
|
|
9768
|
+
* (OR'd, each an instant) and an optional condition tree. Stored recipes —
|
|
9769
|
+
* the `AUTOMATION_RECIPE` literal of every block saved so far, and whatever an
|
|
9770
|
+
* older client still posts — are in this shape, so it stays READABLE here, and
|
|
9771
|
+
* {@link convertLegacyRecipe} turns it into the new one, losslessly and one
|
|
9772
|
+
* way. Nothing writes this shape any more.
|
|
9773
|
+
*
|
|
9774
|
+
* `{ triggers: [t1…tn], conditions?: C }`
|
|
9775
|
+
* → `when = all(any(t1…tn), C)`, or `any(t1…tn)` without `C`;
|
|
9776
|
+
* a single trigger is not wrapped in `any`.
|
|
9777
|
+
* a `condition` leaf → a `device-is` leaf (the "is" verb);
|
|
9778
|
+
* a `device-state` trigger → a `device-becomes` leaf (the "becomes" verb);
|
|
9779
|
+
* `classes: ['dog']` → the macro `animal` with the label `dog`.
|
|
9780
|
+
*/
|
|
9781
|
+
var LegacyTriggerSchema = zod.z.discriminatedUnion("kind", [
|
|
8969
9782
|
zod.z.object({
|
|
8970
9783
|
kind: zod.z.literal("device-state"),
|
|
8971
9784
|
deviceId: zod.z.number().int().nonnegative(),
|
|
8972
9785
|
cap: zod.z.string().min(1),
|
|
8973
9786
|
fieldPath: zod.z.string().min(1),
|
|
8974
|
-
/** Fire when the field takes this value. Omit to fire on any change. */
|
|
8975
9787
|
becomes: zod.z.union([
|
|
8976
9788
|
zod.z.string(),
|
|
8977
9789
|
zod.z.number(),
|
|
8978
9790
|
zod.z.boolean()
|
|
8979
9791
|
]).optional(),
|
|
8980
|
-
/** Only on a CHANGE of value, not on every re-report. */
|
|
8981
9792
|
edge: zod.z.boolean().optional(),
|
|
8982
|
-
/** The condition must hold this long before the run starts. */
|
|
8983
9793
|
forMs: zod.z.number().int().min(0).max(864e5).optional(),
|
|
8984
|
-
/** Collapse a burst into one run. */
|
|
8985
9794
|
debounceMs: zod.z.number().int().min(0).max(6e5).optional()
|
|
8986
9795
|
}),
|
|
8987
9796
|
zod.z.object({
|
|
8988
9797
|
kind: zod.z.literal("device-event"),
|
|
8989
|
-
/** An `EventCategory` value. */
|
|
8990
9798
|
category: zod.z.string().min(1),
|
|
8991
9799
|
deviceId: zod.z.number().int().nonnegative().optional()
|
|
8992
9800
|
}),
|
|
@@ -8997,25 +9805,16 @@ var AutomationTriggerSchema = zod.z.discriminatedUnion("kind", [
|
|
|
8997
9805
|
zod.z.object({ kind: zod.z.literal("manual") }),
|
|
8998
9806
|
zod.z.object({
|
|
8999
9807
|
kind: zod.z.literal("detection"),
|
|
9000
|
-
/** Class names. A macro class (`animal`, `vehicle`, `person`, `package`)
|
|
9001
|
-
* matches every raw class that maps to it; any other name matches
|
|
9002
|
-
* exactly. */
|
|
9003
9808
|
classes: zod.z.array(zod.z.string().min(1)).min(1),
|
|
9004
9809
|
deviceIds: zod.z.array(zod.z.number().int().nonnegative()).min(1),
|
|
9005
|
-
/** The track must have visited at least one of these zones. */
|
|
9006
9810
|
zones: zod.z.array(zod.z.string().min(1)).min(1).optional(),
|
|
9007
|
-
/** Best-so-far confidence the track must reach. */
|
|
9008
9811
|
minScore: zod.z.number().min(0).max(1).optional()
|
|
9009
9812
|
}),
|
|
9010
9813
|
zod.z.object({
|
|
9011
9814
|
kind: zod.z.literal("stationary"),
|
|
9012
9815
|
deviceIds: zod.z.array(zod.z.number().int().nonnegative()).min(1),
|
|
9013
|
-
/** Class names; a macro class matches like the detection trigger. */
|
|
9014
9816
|
classes: zod.z.array(zod.z.string().min(1)).min(1).optional(),
|
|
9015
|
-
/** The parked object must be in at least one of these zones. */
|
|
9016
9817
|
zones: zod.z.array(zod.z.string().min(1)).min(1).optional(),
|
|
9017
|
-
/** `appeared`: it became parked. `departed`: the registry retired it.
|
|
9018
|
-
* The vocabulary of `PipelineAnalyticsStationaryChanged`. */
|
|
9019
9818
|
phase: zod.z.enum(["appeared", "departed"])
|
|
9020
9819
|
}),
|
|
9021
9820
|
zod.z.object({
|
|
@@ -9023,6 +9822,145 @@ var AutomationTriggerSchema = zod.z.discriminatedUnion("kind", [
|
|
|
9023
9822
|
minutes: zod.z.number().int().min(1).max(1440)
|
|
9024
9823
|
})
|
|
9025
9824
|
]);
|
|
9825
|
+
var LegacyConditionLeafSchema = zod.z.object({
|
|
9826
|
+
kind: zod.z.literal("condition"),
|
|
9827
|
+
deviceId: zod.z.number().int().nonnegative(),
|
|
9828
|
+
cap: zod.z.string().min(1),
|
|
9829
|
+
fieldPath: zod.z.string().min(1),
|
|
9830
|
+
operator: AutomationConditionOperatorSchema,
|
|
9831
|
+
value: zod.z.union([
|
|
9832
|
+
zod.z.string(),
|
|
9833
|
+
zod.z.number(),
|
|
9834
|
+
zod.z.boolean(),
|
|
9835
|
+
zod.z.array(zod.z.union([zod.z.string(), zod.z.number()])),
|
|
9836
|
+
QuantityValueSchema
|
|
9837
|
+
]),
|
|
9838
|
+
unit: zod.z.string().min(1).optional()
|
|
9839
|
+
});
|
|
9840
|
+
var LegacyStationaryPresentSchema = zod.z.object({
|
|
9841
|
+
kind: zod.z.literal("stationary-present"),
|
|
9842
|
+
deviceId: zod.z.number().int().nonnegative(),
|
|
9843
|
+
classes: zod.z.array(zod.z.string().min(1)).min(1).optional(),
|
|
9844
|
+
zones: zod.z.array(zod.z.string().min(1)).min(1).optional()
|
|
9845
|
+
});
|
|
9846
|
+
var LegacyExpressionSchema = zod.z.object({
|
|
9847
|
+
kind: zod.z.literal("expression"),
|
|
9848
|
+
expr: zod.z.string().min(1).max(require_composition.MAX_EXPRESSION_SOURCE_LENGTH),
|
|
9849
|
+
bindings: zod.z.record(zod.z.string().regex(require_composition.EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
|
|
9850
|
+
}).superRefine((src, ctx) => {
|
|
9851
|
+
const err = require_composition.validateExpressionSource(src);
|
|
9852
|
+
if (err !== null) ctx.addIssue({
|
|
9853
|
+
code: "custom",
|
|
9854
|
+
message: err,
|
|
9855
|
+
path: ["expr"]
|
|
9856
|
+
});
|
|
9857
|
+
});
|
|
9858
|
+
var LegacyConditionSchema = zod.z.lazy(() => zod.z.discriminatedUnion("kind", [
|
|
9859
|
+
zod.z.object({
|
|
9860
|
+
kind: zod.z.literal("all"),
|
|
9861
|
+
children: zod.z.array(LegacyConditionSchema)
|
|
9862
|
+
}),
|
|
9863
|
+
zod.z.object({
|
|
9864
|
+
kind: zod.z.literal("any"),
|
|
9865
|
+
children: zod.z.array(LegacyConditionSchema)
|
|
9866
|
+
}),
|
|
9867
|
+
zod.z.object({
|
|
9868
|
+
kind: zod.z.literal("not"),
|
|
9869
|
+
child: LegacyConditionSchema
|
|
9870
|
+
}),
|
|
9871
|
+
LegacyConditionLeafSchema,
|
|
9872
|
+
LegacyExpressionSchema,
|
|
9873
|
+
LegacyStationaryPresentSchema
|
|
9874
|
+
]));
|
|
9875
|
+
function convertTrigger(trigger) {
|
|
9876
|
+
switch (trigger.kind) {
|
|
9877
|
+
case "device-state": return {
|
|
9878
|
+
...trigger,
|
|
9879
|
+
kind: "device-becomes"
|
|
9880
|
+
};
|
|
9881
|
+
case "detection": {
|
|
9882
|
+
const { classes, ...rest } = trigger;
|
|
9883
|
+
return {
|
|
9884
|
+
...rest,
|
|
9885
|
+
subjects: classes.map(subjectFromClassName)
|
|
9886
|
+
};
|
|
9887
|
+
}
|
|
9888
|
+
case "stationary": {
|
|
9889
|
+
const { classes, ...rest } = trigger;
|
|
9890
|
+
return {
|
|
9891
|
+
...rest,
|
|
9892
|
+
...classes === void 0 ? {} : { subjects: classes.map(subjectFromClassName) }
|
|
9893
|
+
};
|
|
9894
|
+
}
|
|
9895
|
+
default: return trigger;
|
|
9896
|
+
}
|
|
9897
|
+
}
|
|
9898
|
+
/** An old condition tree as a `when` subtree: `condition` → "is", classes → subjects. */
|
|
9899
|
+
function convertLegacyCondition(condition) {
|
|
9900
|
+
switch (condition.kind) {
|
|
9901
|
+
case "condition": return {
|
|
9902
|
+
...condition,
|
|
9903
|
+
kind: "device-is"
|
|
9904
|
+
};
|
|
9905
|
+
case "stationary-present": {
|
|
9906
|
+
const { classes, ...rest } = condition;
|
|
9907
|
+
return {
|
|
9908
|
+
...rest,
|
|
9909
|
+
...classes === void 0 ? {} : { subjects: classes.map(subjectFromClassName) }
|
|
9910
|
+
};
|
|
9911
|
+
}
|
|
9912
|
+
case "expression": return condition;
|
|
9913
|
+
case "not": return {
|
|
9914
|
+
kind: "not",
|
|
9915
|
+
child: convertLegacyCondition(condition.child)
|
|
9916
|
+
};
|
|
9917
|
+
case "all":
|
|
9918
|
+
case "any": return {
|
|
9919
|
+
kind: condition.kind,
|
|
9920
|
+
children: condition.children.map(convertLegacyCondition)
|
|
9921
|
+
};
|
|
9922
|
+
}
|
|
9923
|
+
}
|
|
9924
|
+
/** `all(any(t1…tn), C)`, or `any(t1…tn)`; a single trigger is not wrapped in `any`. */
|
|
9925
|
+
function convertLegacyWhen(half) {
|
|
9926
|
+
const leaves = half.triggers.map(convertTrigger);
|
|
9927
|
+
const first = leaves[0];
|
|
9928
|
+
const triggers = leaves.length === 1 && first !== void 0 ? first : {
|
|
9929
|
+
kind: "any",
|
|
9930
|
+
children: leaves
|
|
9931
|
+
};
|
|
9932
|
+
if (half.conditions === void 0) return triggers;
|
|
9933
|
+
return {
|
|
9934
|
+
kind: "all",
|
|
9935
|
+
children: [triggers, convertLegacyCondition(half.conditions)]
|
|
9936
|
+
};
|
|
9937
|
+
}
|
|
9938
|
+
//#endregion
|
|
9939
|
+
//#region src/automation/recipe.ts
|
|
9940
|
+
/**
|
|
9941
|
+
* The automation RECIPE — what a visually-authored automation is, before it
|
|
9942
|
+
* becomes code.
|
|
9943
|
+
*
|
|
9944
|
+
* This is the data half of `docs/design/2026-08-08-automation-scripts-custom-devices.md`
|
|
9945
|
+
* Phase 6. A recipe is not an engine and never evaluates anything: it is a
|
|
9946
|
+
* declarative description that {@link generateAutomationBlock} compiles to the
|
|
9947
|
+
* TypeScript of an ordinary core block. There is therefore no second rule
|
|
9948
|
+
* engine, no second store and no second runtime — the block runner is the
|
|
9949
|
+
* runtime, `core_blocks` is the store (§2.1).
|
|
9950
|
+
*
|
|
9951
|
+
* Two parts:
|
|
9952
|
+
*
|
|
9953
|
+
* - **`when`** — ONE tree (`./when.ts`) of all/any/not over event leaves
|
|
9954
|
+
* (instants) and state leaves (durations). It replaces the flat trigger list
|
|
9955
|
+
* plus the separate condition tree; the firing rule is in that file and in
|
|
9956
|
+
* ADR "one tree".
|
|
9957
|
+
* - **Actions** — `NcRuleActionSchema`'s two steps, plus the `{kind:'code'}`
|
|
9958
|
+
* fragment of §3.2.2. See {@link AutomationActionSchema} for the ONE place
|
|
9959
|
+
* this had to diverge from "verbatim", and why.
|
|
9960
|
+
*
|
|
9961
|
+
* A recipe stored in the old shape (`triggers` + `conditions`) still LOADS:
|
|
9962
|
+
* {@link AutomationRecipeSchema} (the parse-anywhere schema) converts it on parse. Nothing writes it.
|
|
9963
|
+
*/
|
|
9026
9964
|
/**
|
|
9027
9965
|
* One action step.
|
|
9028
9966
|
*
|
|
@@ -9128,61 +10066,49 @@ var AutomationRunModeSchema = zod.z.discriminatedUnion("mode", [
|
|
|
9128
10066
|
/** Run traces kept per automation when the recipe does not say. */
|
|
9129
10067
|
var DEFAULT_TRACE_KEEP = 20;
|
|
9130
10068
|
var MAX_TRACE_KEEP = 100;
|
|
9131
|
-
var
|
|
9132
|
-
|
|
9133
|
-
conditions: AutomationConditionSchema.optional(),
|
|
10069
|
+
var CurrentAutomationRecipeSchema = zod.z.object({
|
|
10070
|
+
when: AutomationWhenSchema,
|
|
9134
10071
|
actions: zod.z.array(AutomationActionSchema),
|
|
9135
10072
|
runMode: AutomationRunModeSchema.optional(),
|
|
9136
10073
|
/** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
|
|
9137
10074
|
keepTraces: zod.z.number().int().min(1).max(100).optional()
|
|
9138
10075
|
});
|
|
9139
|
-
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
|
|
9144
|
-
|
|
9145
|
-
|
|
9146
|
-
|
|
9147
|
-
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
}
|
|
9155
|
-
|
|
9156
|
-
|
|
9157
|
-
if (isLeaf(cond)) return 1;
|
|
9158
|
-
if (cond.kind === "not") return countConditionLeaves(cond.child);
|
|
9159
|
-
let total = 0;
|
|
9160
|
-
for (const child of cond.children) total += countConditionLeaves(child);
|
|
9161
|
-
return total;
|
|
9162
|
-
}
|
|
9163
|
-
function findEmptyContainer(cond) {
|
|
9164
|
-
if (isLeaf(cond)) return false;
|
|
9165
|
-
if (cond.kind === "not") return findEmptyContainer(cond.child);
|
|
9166
|
-
if (cond.children.length === 0) return true;
|
|
9167
|
-
return cond.children.some(findEmptyContainer);
|
|
10076
|
+
/** A recipe as it was stored before the single tree. Read-only: nothing writes this. */
|
|
10077
|
+
var LegacyAutomationRecipeSchema = zod.z.object({
|
|
10078
|
+
triggers: zod.z.array(LegacyTriggerSchema),
|
|
10079
|
+
conditions: LegacyConditionSchema.optional(),
|
|
10080
|
+
actions: zod.z.array(AutomationActionSchema),
|
|
10081
|
+
runMode: AutomationRunModeSchema.optional(),
|
|
10082
|
+
keepTraces: zod.z.number().int().min(1).max(100).optional()
|
|
10083
|
+
});
|
|
10084
|
+
/** The old shape as the new one, lossless. */
|
|
10085
|
+
function convertLegacyRecipe(legacy) {
|
|
10086
|
+
const { triggers, conditions, ...rest } = legacy;
|
|
10087
|
+
return {
|
|
10088
|
+
when: convertLegacyWhen({
|
|
10089
|
+
triggers,
|
|
10090
|
+
conditions
|
|
10091
|
+
}),
|
|
10092
|
+
...rest
|
|
10093
|
+
};
|
|
9168
10094
|
}
|
|
9169
10095
|
/**
|
|
9170
|
-
*
|
|
9171
|
-
*
|
|
9172
|
-
*
|
|
10096
|
+
* Where recipes are PARSED (the core-blocks cap, the MCP tool, the literal a
|
|
10097
|
+
* saved block carries): the new shape as it is, or the old shape converted to
|
|
10098
|
+
* it. The output type is always the new one.
|
|
10099
|
+
*/
|
|
10100
|
+
var AutomationRecipeSchema = zod.z.union([CurrentAutomationRecipeSchema, LegacyAutomationRecipeSchema.transform(convertLegacyRecipe)]);
|
|
10101
|
+
/** Bounds, shared by `when` and the old names the guards still know. */
|
|
10102
|
+
var MAX_CONDITION_DEPTH = 4;
|
|
10103
|
+
var MAX_CONDITION_LEAVES = 32;
|
|
10104
|
+
/**
|
|
10105
|
+
* Author-time check. Returns `null` when the recipe is legal, else a message
|
|
10106
|
+
* naming what is wrong — the editor shows it verbatim, so it has to read like
|
|
10107
|
+
* something an operator can act on.
|
|
9173
10108
|
*/
|
|
9174
10109
|
function validateRecipeBounds(recipe) {
|
|
9175
|
-
if (recipe.triggers.length === 0) return "an automation needs at least one trigger — without one nothing can start a run";
|
|
9176
10110
|
if (recipe.actions.length === 0) return "an automation needs at least one action — a rule that does nothing is not a rule";
|
|
9177
|
-
|
|
9178
|
-
if (cond !== void 0) {
|
|
9179
|
-
if (findEmptyContainer(cond)) return "a condition group is empty — group existing rows instead of leaving a container to fill";
|
|
9180
|
-
const depth = conditionDepth(cond);
|
|
9181
|
-
if (depth > 3) return `conditions nest ${depth} levels deep — the limit is 3. "not (any of …)" is the same as "all of (not …)", which is one level shallower`;
|
|
9182
|
-
const leaves = countConditionLeaves(cond);
|
|
9183
|
-
if (leaves > 32) return `conditions use ${leaves} leaves — the limit is 32`;
|
|
9184
|
-
}
|
|
9185
|
-
return null;
|
|
10111
|
+
return validateWhenTree(recipe.when);
|
|
9186
10112
|
}
|
|
9187
10113
|
//#endregion
|
|
9188
10114
|
//#region src/automation/codegen.ts
|
|
@@ -9206,9 +10132,10 @@ function stepMetaOf(action) {
|
|
|
9206
10132
|
function j(value) {
|
|
9207
10133
|
return JSON.stringify(value);
|
|
9208
10134
|
}
|
|
9209
|
-
function
|
|
10135
|
+
function emitLeaf(trigger, path) {
|
|
10136
|
+
const at = j(path);
|
|
9210
10137
|
switch (trigger.kind) {
|
|
9211
|
-
case "schedule": return ` //
|
|
10138
|
+
case "schedule": return ` // event leaf ${path}: schedule — armed to the next minute boundary and
|
|
9212
10139
|
// RE-ARMED from the wall clock every tick, so it cannot drift the way a
|
|
9213
10140
|
// setInterval(24h) does. No cron library: a block cannot resolve one.
|
|
9214
10141
|
{
|
|
@@ -9216,7 +10143,7 @@ function emitTrigger(trigger, index) {
|
|
|
9216
10143
|
const arm = (): void => {
|
|
9217
10144
|
timer = setTimeout(() => {
|
|
9218
10145
|
if (cronMatches(${j(trigger.cron)}, new Date())) {
|
|
9219
|
-
void run({ kind: 'schedule', at: Date.now() })
|
|
10146
|
+
void run({ kind: 'schedule', leaf: ${at}, at: Date.now() })
|
|
9220
10147
|
}
|
|
9221
10148
|
arm()
|
|
9222
10149
|
}, nextMinuteBoundary())
|
|
@@ -9224,13 +10151,14 @@ function emitTrigger(trigger, index) {
|
|
|
9224
10151
|
arm()
|
|
9225
10152
|
disposers.push(() => { if (timer !== undefined) clearTimeout(timer) })
|
|
9226
10153
|
}`;
|
|
9227
|
-
case "device-event": return ` //
|
|
10154
|
+
case "device-event": return ` // event leaf ${path}: device event
|
|
9228
10155
|
disposers.push(
|
|
9229
10156
|
ctx.eventBus.subscribe(
|
|
9230
10157
|
{ category: ${j(trigger.category)}${trigger.deviceId === void 0 ? "" : `, deviceId: ${j(trigger.deviceId)}`} },
|
|
9231
10158
|
(event) => {
|
|
9232
10159
|
void run({
|
|
9233
10160
|
kind: 'device-event',
|
|
10161
|
+
leaf: ${at},
|
|
9234
10162
|
at: Date.now(),
|
|
9235
10163
|
deviceId: event.deviceId,
|
|
9236
10164
|
category: ${j(trigger.category)},
|
|
@@ -9238,9 +10166,9 @@ function emitTrigger(trigger, index) {
|
|
|
9238
10166
|
},
|
|
9239
10167
|
),
|
|
9240
10168
|
)`;
|
|
9241
|
-
case "device-
|
|
10169
|
+
case "device-becomes": {
|
|
9242
10170
|
const becomes = trigger.becomes === void 0 ? "undefined" : j(trigger.becomes);
|
|
9243
|
-
return ` //
|
|
10171
|
+
return ` // event leaf ${path}: device state — the mirror is the authority, the
|
|
9244
10172
|
// event only WAKES the evaluation, so a dropped event delays and never loses.
|
|
9245
10173
|
{
|
|
9246
10174
|
let previous: unknown = undefined
|
|
@@ -9261,7 +10189,8 @@ function emitTrigger(trigger, index) {
|
|
|
9261
10189
|
}
|
|
9262
10190
|
heldSince = undefined
|
|
9263
10191
|
void run({
|
|
9264
|
-
kind: 'device-
|
|
10192
|
+
kind: 'device-becomes',
|
|
10193
|
+
leaf: ${at},
|
|
9265
10194
|
at: Date.now(),
|
|
9266
10195
|
deviceId: ${j(trigger.deviceId)},
|
|
9267
10196
|
cap: ${j(trigger.cap)},
|
|
@@ -9280,23 +10209,23 @@ function emitTrigger(trigger, index) {
|
|
|
9280
10209
|
disposers.push(() => clearInterval(reconcile))
|
|
9281
10210
|
}`;
|
|
9282
10211
|
}
|
|
9283
|
-
case "manual": return ` //
|
|
10212
|
+
case "manual": return ` // event leaf ${path}: manual — fired by automation-control.trigger
|
|
9284
10213
|
// ("Run actions"), which runs the THEN half with an EMPTY vars bag. A manual
|
|
9285
10214
|
// run bypasses only the cooldown wait, never the single/queued limits.`;
|
|
9286
|
-
case "interval": return ` //
|
|
10215
|
+
case "interval": return ` // event leaf ${path}: interval — every ${trigger.minutes} min, first run jittered so
|
|
9287
10216
|
// blocks restarted together do not all fire on the same second.
|
|
9288
10217
|
{
|
|
9289
10218
|
let interval: ReturnType<typeof setInterval> | undefined
|
|
9290
10219
|
const first = setTimeout(() => {
|
|
9291
|
-
void run({ kind: 'interval', at: Date.now() })
|
|
9292
|
-
interval = setInterval(() => { void run({ kind: 'interval', at: Date.now() }) }, ${j(trigger.minutes * 6e4)})
|
|
10220
|
+
void run({ kind: 'interval', leaf: ${at}, at: Date.now() })
|
|
10221
|
+
interval = setInterval(() => { void run({ kind: 'interval', leaf: ${at}, at: Date.now() }) }, ${j(trigger.minutes * 6e4)})
|
|
9293
10222
|
}, Math.floor(Math.random() * Math.min(${j(trigger.minutes * 6e4)}, 30_000)) + 1)
|
|
9294
10223
|
disposers.push(() => {
|
|
9295
10224
|
clearTimeout(first)
|
|
9296
10225
|
if (interval !== undefined) clearInterval(interval)
|
|
9297
10226
|
})
|
|
9298
10227
|
}`;
|
|
9299
|
-
case "detection": return ` //
|
|
10228
|
+
case "detection": return ` // event leaf ${path}: detection — ONE fire per (device, track); a rejected event is
|
|
9300
10229
|
// traced with its reason and never fires.
|
|
9301
10230
|
{
|
|
9302
10231
|
const filter = new DetectionTriggerFilter(${j(trigger)})
|
|
@@ -9313,7 +10242,7 @@ function emitTrigger(trigger, index) {
|
|
|
9313
10242
|
})
|
|
9314
10243
|
}
|
|
9315
10244
|
void run(
|
|
9316
|
-
{ kind: 'detection', at: Date.now(), ...decision.vars },
|
|
10245
|
+
{ kind: 'detection', leaf: ${at}, at: Date.now(), ...decision.vars },
|
|
9317
10246
|
decision.reason,
|
|
9318
10247
|
decision.kind === 'fire',
|
|
9319
10248
|
)
|
|
@@ -9321,9 +10250,9 @@ function emitTrigger(trigger, index) {
|
|
|
9321
10250
|
),
|
|
9322
10251
|
)
|
|
9323
10252
|
}`;
|
|
9324
|
-
case "stationary": return ` //
|
|
10253
|
+
case "stationary": return ` // event leaf ${path}: stationary — a parked object appeared / departed. Stateless:
|
|
9325
10254
|
// the registry emits one event per transition. A dropped event only misses a fire;
|
|
9326
|
-
// the stationary-present
|
|
10255
|
+
// the stationary-present STATE leaf reads the registry, not this event.
|
|
9327
10256
|
{
|
|
9328
10257
|
const trigger = ${j(trigger)} as const
|
|
9329
10258
|
disposers.push(
|
|
@@ -9339,7 +10268,7 @@ function emitTrigger(trigger, index) {
|
|
|
9339
10268
|
})
|
|
9340
10269
|
}
|
|
9341
10270
|
void run(
|
|
9342
|
-
{ kind: 'stationary', at: Date.now(), ...decision.vars },
|
|
10271
|
+
{ kind: 'stationary', leaf: ${at}, at: Date.now(), ...decision.vars },
|
|
9343
10272
|
decision.reason,
|
|
9344
10273
|
decision.kind === 'fire',
|
|
9345
10274
|
)
|
|
@@ -9361,11 +10290,11 @@ ${body}
|
|
|
9361
10290
|
case "cap":
|
|
9362
10291
|
if (Array.isArray(action.deviceId)) return step(` for (const deviceId of ${j(action.deviceId)}) {
|
|
9363
10292
|
scope.setDeviceId(deviceId)
|
|
9364
|
-
await actuate(deviceId, ${j(action.cap)}, ${j(action.method)}, ${j(action.args ?? {})}, vars)
|
|
10293
|
+
await actuate(scope, deviceId, ${j(action.cap)}, ${j(action.method)}, ${j(action.args ?? {})}, vars)
|
|
9365
10294
|
}`);
|
|
9366
10295
|
return step(` const deviceId = ${typeof action.deviceId === "number" ? j(action.deviceId) : `resolveTarget(vars, ${j(action.deviceId.$var)})`}
|
|
9367
10296
|
scope.setDeviceId(deviceId)
|
|
9368
|
-
await actuate(deviceId, ${j(action.cap)}, ${j(action.method)}, ${j(action.args ?? {})}, vars)`);
|
|
10297
|
+
await actuate(scope, deviceId, ${j(action.cap)}, ${j(action.method)}, ${j(action.args ?? {})}, vars)`);
|
|
9369
10298
|
case "action": return step(` // an Action run, awaited: the step takes THAT run's outcome
|
|
9370
10299
|
const result = await ctx.api.actions.run.mutate({
|
|
9371
10300
|
actionId: ${j(action.actionId)},
|
|
@@ -9395,6 +10324,13 @@ function generateAutomationBlock(input) {
|
|
|
9395
10324
|
const { name, recipe } = input;
|
|
9396
10325
|
const runMode = recipe.runMode ?? { mode: "single" };
|
|
9397
10326
|
const stepMeta = recipe.actions.map(stepMetaOf);
|
|
10327
|
+
const eventLeaves = [];
|
|
10328
|
+
forEachLeaf(recipe.when, (leaf, path) => {
|
|
10329
|
+
if (isEventLeaf(leaf)) eventLeaves.push({
|
|
10330
|
+
leaf,
|
|
10331
|
+
path
|
|
10332
|
+
});
|
|
10333
|
+
});
|
|
9398
10334
|
return `import type {
|
|
9399
10335
|
AddonContext,
|
|
9400
10336
|
AutomationConditionTraceNode,
|
|
@@ -9402,6 +10338,7 @@ function generateAutomationBlock(input) {
|
|
|
9402
10338
|
AutomationRunVerdict,
|
|
9403
10339
|
AutomationStepTrace,
|
|
9404
10340
|
RunnableStep,
|
|
10341
|
+
StepScope,
|
|
9405
10342
|
} from '@camstack/types'
|
|
9406
10343
|
import {
|
|
9407
10344
|
ConditionMirror,
|
|
@@ -9409,9 +10346,9 @@ import {
|
|
|
9409
10346
|
EventCategory,
|
|
9410
10347
|
RunGate,
|
|
9411
10348
|
buildTriggerTrace,
|
|
9412
|
-
|
|
10349
|
+
collectWhenMirrorSources,
|
|
9413
10350
|
coreBlockIdFromAddonId,
|
|
9414
|
-
|
|
10351
|
+
evaluateWhenTree,
|
|
9415
10352
|
evaluateStationaryTrigger,
|
|
9416
10353
|
isSkippedTrace,
|
|
9417
10354
|
mergeSkipTraces,
|
|
@@ -9439,7 +10376,11 @@ const RUN_MODE = ${j(runMode)} as const
|
|
|
9439
10376
|
/** Run traces kept (a ring, held in the hub's runs ledger). */
|
|
9440
10377
|
const KEEP_TRACES = ${recipe.keepTraces ?? 20}
|
|
9441
10378
|
|
|
9442
|
-
|
|
10379
|
+
/** The ONE tree: event leaves (instants) and state leaves (durations). */
|
|
10380
|
+
const WHEN = AUTOMATION_RECIPE.when
|
|
10381
|
+
|
|
10382
|
+
/** No event leaf: the automation fires when the tree goes false → true, never because it was already true. */
|
|
10383
|
+
const STATE_ONLY = ${!hasEventLeaves(recipe.when)}
|
|
9443
10384
|
|
|
9444
10385
|
/** Skips that repeat are folded into one trace; it goes to the hub after this. */
|
|
9445
10386
|
const SKIP_FLUSH_MS = 5_000
|
|
@@ -9498,7 +10439,8 @@ function matchField(field: string, value: number): boolean {
|
|
|
9498
10439
|
export default function block(ctx: AddonContext) {
|
|
9499
10440
|
const disposers: Array<() => void> = []
|
|
9500
10441
|
const gate = new RunGate(RUN_MODE)
|
|
9501
|
-
const
|
|
10442
|
+
const MIRROR_SOURCES = collectWhenMirrorSources(WHEN)
|
|
10443
|
+
const mirror = new ConditionMirror(MIRROR_SOURCES)
|
|
9502
10444
|
const BLOCK_ID = coreBlockIdFromAddonId(ctx.id)
|
|
9503
10445
|
/** The cooldown stamp: memory is the authority at fire time (D49); the hub ledger is read once at start. */
|
|
9504
10446
|
let lastRunStartedAt: number | null = null
|
|
@@ -9664,6 +10606,7 @@ export default function block(ctx: AddonContext) {
|
|
|
9664
10606
|
}
|
|
9665
10607
|
|
|
9666
10608
|
async function actuate(
|
|
10609
|
+
scope: StepScope,
|
|
9667
10610
|
deviceId: number,
|
|
9668
10611
|
cap: string,
|
|
9669
10612
|
method: string,
|
|
@@ -9673,6 +10616,7 @@ export default function block(ctx: AddonContext) {
|
|
|
9673
10616
|
const substituted = substitute(args, vars)
|
|
9674
10617
|
const resolved: Record<string, unknown> =
|
|
9675
10618
|
substituted !== null && typeof substituted === 'object' ? { ...substituted } : {}
|
|
10619
|
+
scope.setArgs(resolved)
|
|
9676
10620
|
// Forked-addon ctx.api namespaces are tRPC Proxies over a noop function
|
|
9677
10621
|
// (typeof === 'function'), not plain objects — reject only null/undefined.
|
|
9678
10622
|
const client: unknown = Reflect.get(ctx.api, capToRouterKey(cap))
|
|
@@ -9761,10 +10705,13 @@ ${recipe.actions.map((a, i) => emitAction(a, i)).join("\n")}
|
|
|
9761
10705
|
let nodes: AutomationConditionTraceNode[] = []
|
|
9762
10706
|
let steps: AutomationStepTrace[] = []
|
|
9763
10707
|
try {
|
|
9764
|
-
const
|
|
10708
|
+
const firedLeaf = trigger['leaf']
|
|
10709
|
+
const evaluation = evaluateWhenTree(WHEN, mirror, Date.now(), {
|
|
10710
|
+
...(typeof firedLeaf === 'string' ? { firedPath: firedLeaf } : {}),
|
|
10711
|
+
})
|
|
9765
10712
|
nodes = [...evaluation.nodes]
|
|
9766
10713
|
for (const node of nodes) {
|
|
9767
|
-
if (node.kind === '
|
|
10714
|
+
if (node.kind === 'device-is' && node.observed === null && node.deviceId !== undefined) {
|
|
9768
10715
|
ctx.logger.warn('condition leaf value unknown — treated as false', {
|
|
9769
10716
|
tags: { deviceId: node.deviceId },
|
|
9770
10717
|
path: node.path,
|
|
@@ -9775,7 +10722,7 @@ ${recipe.actions.map((a, i) => emitAction(a, i)).join("\n")}
|
|
|
9775
10722
|
}
|
|
9776
10723
|
if (!evaluation.pass) {
|
|
9777
10724
|
outcome = 'skipped-conditions'
|
|
9778
|
-
ctx.logger.debug('automation did not run —
|
|
10725
|
+
ctx.logger.debug('automation did not run — the rule tree was false at that instant', { trigger, ...deviceTags(trigger) })
|
|
9779
10726
|
return
|
|
9780
10727
|
}
|
|
9781
10728
|
const letThrough = stampUnknown && RUN_MODE.mode === 'cooldown'
|
|
@@ -9874,9 +10821,30 @@ ${recipe.actions.map((a, i) => emitAction(a, i)).join("\n")}
|
|
|
9874
10821
|
const ready: Promise<void> = (async () => {
|
|
9875
10822
|
await loadState()
|
|
9876
10823
|
await refreshMirror()
|
|
10824
|
+
stateWatching = true
|
|
10825
|
+
evaluateStateTree()
|
|
9877
10826
|
})()
|
|
9878
10827
|
|
|
9879
|
-
|
|
10828
|
+
/**
|
|
10829
|
+
* Rule 2 — a tree of states only. It fires when the tree goes false → true.
|
|
10830
|
+
* The FIRST evaluation only seeds \`treeWasTrue\` (a tree that is already true
|
|
10831
|
+
* when the hub or runner starts must not fire), and an evaluation that read an
|
|
10832
|
+
* unknown value neither seeds nor moves it: unknown is not false.
|
|
10833
|
+
*/
|
|
10834
|
+
let treeWasTrue: boolean | undefined
|
|
10835
|
+
let stateWatching = false
|
|
10836
|
+
function evaluateStateTree(): void {
|
|
10837
|
+
if (!STATE_ONLY || !stateWatching || stopped) return
|
|
10838
|
+
const evaluation = evaluateWhenTree(WHEN, mirror, Date.now())
|
|
10839
|
+
if (evaluation.unknown) return
|
|
10840
|
+
const was = treeWasTrue
|
|
10841
|
+
treeWasTrue = evaluation.pass
|
|
10842
|
+
if (was === false && evaluation.pass) {
|
|
10843
|
+
void run({ kind: 'state', leaf: 'when', at: Date.now() }, 'the rules became true', true)
|
|
10844
|
+
}
|
|
10845
|
+
}
|
|
10846
|
+
|
|
10847
|
+
if (MIRROR_SOURCES.length > 0) {
|
|
9880
10848
|
disposers.push(
|
|
9881
10849
|
ctx.eventBus.subscribe({ category: EventCategory.DeviceStateChanged }, (event) => {
|
|
9882
10850
|
const data = event.data
|
|
@@ -9884,14 +10852,17 @@ ${recipe.actions.map((a, i) => emitAction(a, i)).join("\n")}
|
|
|
9884
10852
|
const capName = data['capName']
|
|
9885
10853
|
if (typeof deviceId === 'number' && typeof capName === 'string') {
|
|
9886
10854
|
mirror.apply(deviceId, capName, data['slice'])
|
|
10855
|
+
evaluateStateTree()
|
|
9887
10856
|
}
|
|
9888
10857
|
}),
|
|
9889
10858
|
)
|
|
9890
|
-
const mirrorReconcile = setInterval(() => {
|
|
10859
|
+
const mirrorReconcile = setInterval(() => {
|
|
10860
|
+
void refreshMirror().then(evaluateStateTree)
|
|
10861
|
+
}, RECONCILE_MS)
|
|
9891
10862
|
disposers.push(() => clearInterval(mirrorReconcile))
|
|
9892
10863
|
}
|
|
9893
10864
|
|
|
9894
|
-
${
|
|
10865
|
+
${eventLeaves.map(({ leaf, path }) => emitLeaf(leaf, path)).join("\n\n")}
|
|
9895
10866
|
|
|
9896
10867
|
return () => {
|
|
9897
10868
|
stopped = true
|
|
@@ -9907,494 +10878,6 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
9907
10878
|
`;
|
|
9908
10879
|
}
|
|
9909
10880
|
//#endregion
|
|
9910
|
-
//#region src/catalogs/coco-classmap.ts
|
|
9911
|
-
var COCO_80_LABELS = [
|
|
9912
|
-
{
|
|
9913
|
-
id: "person",
|
|
9914
|
-
name: "Person"
|
|
9915
|
-
},
|
|
9916
|
-
{
|
|
9917
|
-
id: "bicycle",
|
|
9918
|
-
name: "Bicycle"
|
|
9919
|
-
},
|
|
9920
|
-
{
|
|
9921
|
-
id: "car",
|
|
9922
|
-
name: "Car"
|
|
9923
|
-
},
|
|
9924
|
-
{
|
|
9925
|
-
id: "motorcycle",
|
|
9926
|
-
name: "Motorcycle"
|
|
9927
|
-
},
|
|
9928
|
-
{
|
|
9929
|
-
id: "airplane",
|
|
9930
|
-
name: "Airplane"
|
|
9931
|
-
},
|
|
9932
|
-
{
|
|
9933
|
-
id: "bus",
|
|
9934
|
-
name: "Bus"
|
|
9935
|
-
},
|
|
9936
|
-
{
|
|
9937
|
-
id: "train",
|
|
9938
|
-
name: "Train"
|
|
9939
|
-
},
|
|
9940
|
-
{
|
|
9941
|
-
id: "truck",
|
|
9942
|
-
name: "Truck"
|
|
9943
|
-
},
|
|
9944
|
-
{
|
|
9945
|
-
id: "boat",
|
|
9946
|
-
name: "Boat"
|
|
9947
|
-
},
|
|
9948
|
-
{
|
|
9949
|
-
id: "traffic light",
|
|
9950
|
-
name: "Traffic Light"
|
|
9951
|
-
},
|
|
9952
|
-
{
|
|
9953
|
-
id: "fire hydrant",
|
|
9954
|
-
name: "Fire Hydrant"
|
|
9955
|
-
},
|
|
9956
|
-
{
|
|
9957
|
-
id: "stop sign",
|
|
9958
|
-
name: "Stop Sign"
|
|
9959
|
-
},
|
|
9960
|
-
{
|
|
9961
|
-
id: "parking meter",
|
|
9962
|
-
name: "Parking Meter"
|
|
9963
|
-
},
|
|
9964
|
-
{
|
|
9965
|
-
id: "bench",
|
|
9966
|
-
name: "Bench"
|
|
9967
|
-
},
|
|
9968
|
-
{
|
|
9969
|
-
id: "bird",
|
|
9970
|
-
name: "Bird"
|
|
9971
|
-
},
|
|
9972
|
-
{
|
|
9973
|
-
id: "cat",
|
|
9974
|
-
name: "Cat"
|
|
9975
|
-
},
|
|
9976
|
-
{
|
|
9977
|
-
id: "dog",
|
|
9978
|
-
name: "Dog"
|
|
9979
|
-
},
|
|
9980
|
-
{
|
|
9981
|
-
id: "horse",
|
|
9982
|
-
name: "Horse"
|
|
9983
|
-
},
|
|
9984
|
-
{
|
|
9985
|
-
id: "sheep",
|
|
9986
|
-
name: "Sheep"
|
|
9987
|
-
},
|
|
9988
|
-
{
|
|
9989
|
-
id: "cow",
|
|
9990
|
-
name: "Cow"
|
|
9991
|
-
},
|
|
9992
|
-
{
|
|
9993
|
-
id: "elephant",
|
|
9994
|
-
name: "Elephant"
|
|
9995
|
-
},
|
|
9996
|
-
{
|
|
9997
|
-
id: "bear",
|
|
9998
|
-
name: "Bear"
|
|
9999
|
-
},
|
|
10000
|
-
{
|
|
10001
|
-
id: "zebra",
|
|
10002
|
-
name: "Zebra"
|
|
10003
|
-
},
|
|
10004
|
-
{
|
|
10005
|
-
id: "giraffe",
|
|
10006
|
-
name: "Giraffe"
|
|
10007
|
-
},
|
|
10008
|
-
{
|
|
10009
|
-
id: "backpack",
|
|
10010
|
-
name: "Backpack"
|
|
10011
|
-
},
|
|
10012
|
-
{
|
|
10013
|
-
id: "umbrella",
|
|
10014
|
-
name: "Umbrella"
|
|
10015
|
-
},
|
|
10016
|
-
{
|
|
10017
|
-
id: "handbag",
|
|
10018
|
-
name: "Handbag"
|
|
10019
|
-
},
|
|
10020
|
-
{
|
|
10021
|
-
id: "tie",
|
|
10022
|
-
name: "Tie"
|
|
10023
|
-
},
|
|
10024
|
-
{
|
|
10025
|
-
id: "suitcase",
|
|
10026
|
-
name: "Suitcase"
|
|
10027
|
-
},
|
|
10028
|
-
{
|
|
10029
|
-
id: "frisbee",
|
|
10030
|
-
name: "Frisbee"
|
|
10031
|
-
},
|
|
10032
|
-
{
|
|
10033
|
-
id: "skis",
|
|
10034
|
-
name: "Skis"
|
|
10035
|
-
},
|
|
10036
|
-
{
|
|
10037
|
-
id: "snowboard",
|
|
10038
|
-
name: "Snowboard"
|
|
10039
|
-
},
|
|
10040
|
-
{
|
|
10041
|
-
id: "sports ball",
|
|
10042
|
-
name: "Sports Ball"
|
|
10043
|
-
},
|
|
10044
|
-
{
|
|
10045
|
-
id: "kite",
|
|
10046
|
-
name: "Kite"
|
|
10047
|
-
},
|
|
10048
|
-
{
|
|
10049
|
-
id: "baseball bat",
|
|
10050
|
-
name: "Baseball Bat"
|
|
10051
|
-
},
|
|
10052
|
-
{
|
|
10053
|
-
id: "baseball glove",
|
|
10054
|
-
name: "Baseball Glove"
|
|
10055
|
-
},
|
|
10056
|
-
{
|
|
10057
|
-
id: "skateboard",
|
|
10058
|
-
name: "Skateboard"
|
|
10059
|
-
},
|
|
10060
|
-
{
|
|
10061
|
-
id: "surfboard",
|
|
10062
|
-
name: "Surfboard"
|
|
10063
|
-
},
|
|
10064
|
-
{
|
|
10065
|
-
id: "tennis racket",
|
|
10066
|
-
name: "Tennis Racket"
|
|
10067
|
-
},
|
|
10068
|
-
{
|
|
10069
|
-
id: "bottle",
|
|
10070
|
-
name: "Bottle"
|
|
10071
|
-
},
|
|
10072
|
-
{
|
|
10073
|
-
id: "wine glass",
|
|
10074
|
-
name: "Wine Glass"
|
|
10075
|
-
},
|
|
10076
|
-
{
|
|
10077
|
-
id: "cup",
|
|
10078
|
-
name: "Cup"
|
|
10079
|
-
},
|
|
10080
|
-
{
|
|
10081
|
-
id: "fork",
|
|
10082
|
-
name: "Fork"
|
|
10083
|
-
},
|
|
10084
|
-
{
|
|
10085
|
-
id: "knife",
|
|
10086
|
-
name: "Knife"
|
|
10087
|
-
},
|
|
10088
|
-
{
|
|
10089
|
-
id: "spoon",
|
|
10090
|
-
name: "Spoon"
|
|
10091
|
-
},
|
|
10092
|
-
{
|
|
10093
|
-
id: "bowl",
|
|
10094
|
-
name: "Bowl"
|
|
10095
|
-
},
|
|
10096
|
-
{
|
|
10097
|
-
id: "banana",
|
|
10098
|
-
name: "Banana"
|
|
10099
|
-
},
|
|
10100
|
-
{
|
|
10101
|
-
id: "apple",
|
|
10102
|
-
name: "Apple"
|
|
10103
|
-
},
|
|
10104
|
-
{
|
|
10105
|
-
id: "sandwich",
|
|
10106
|
-
name: "Sandwich"
|
|
10107
|
-
},
|
|
10108
|
-
{
|
|
10109
|
-
id: "orange",
|
|
10110
|
-
name: "Orange"
|
|
10111
|
-
},
|
|
10112
|
-
{
|
|
10113
|
-
id: "broccoli",
|
|
10114
|
-
name: "Broccoli"
|
|
10115
|
-
},
|
|
10116
|
-
{
|
|
10117
|
-
id: "carrot",
|
|
10118
|
-
name: "Carrot"
|
|
10119
|
-
},
|
|
10120
|
-
{
|
|
10121
|
-
id: "hot dog",
|
|
10122
|
-
name: "Hot Dog"
|
|
10123
|
-
},
|
|
10124
|
-
{
|
|
10125
|
-
id: "pizza",
|
|
10126
|
-
name: "Pizza"
|
|
10127
|
-
},
|
|
10128
|
-
{
|
|
10129
|
-
id: "donut",
|
|
10130
|
-
name: "Donut"
|
|
10131
|
-
},
|
|
10132
|
-
{
|
|
10133
|
-
id: "cake",
|
|
10134
|
-
name: "Cake"
|
|
10135
|
-
},
|
|
10136
|
-
{
|
|
10137
|
-
id: "chair",
|
|
10138
|
-
name: "Chair"
|
|
10139
|
-
},
|
|
10140
|
-
{
|
|
10141
|
-
id: "couch",
|
|
10142
|
-
name: "Couch"
|
|
10143
|
-
},
|
|
10144
|
-
{
|
|
10145
|
-
id: "potted plant",
|
|
10146
|
-
name: "Potted Plant"
|
|
10147
|
-
},
|
|
10148
|
-
{
|
|
10149
|
-
id: "bed",
|
|
10150
|
-
name: "Bed"
|
|
10151
|
-
},
|
|
10152
|
-
{
|
|
10153
|
-
id: "dining table",
|
|
10154
|
-
name: "Dining Table"
|
|
10155
|
-
},
|
|
10156
|
-
{
|
|
10157
|
-
id: "toilet",
|
|
10158
|
-
name: "Toilet"
|
|
10159
|
-
},
|
|
10160
|
-
{
|
|
10161
|
-
id: "tv",
|
|
10162
|
-
name: "TV"
|
|
10163
|
-
},
|
|
10164
|
-
{
|
|
10165
|
-
id: "laptop",
|
|
10166
|
-
name: "Laptop"
|
|
10167
|
-
},
|
|
10168
|
-
{
|
|
10169
|
-
id: "mouse",
|
|
10170
|
-
name: "Mouse"
|
|
10171
|
-
},
|
|
10172
|
-
{
|
|
10173
|
-
id: "remote",
|
|
10174
|
-
name: "Remote"
|
|
10175
|
-
},
|
|
10176
|
-
{
|
|
10177
|
-
id: "keyboard",
|
|
10178
|
-
name: "Keyboard"
|
|
10179
|
-
},
|
|
10180
|
-
{
|
|
10181
|
-
id: "cell phone",
|
|
10182
|
-
name: "Cell Phone"
|
|
10183
|
-
},
|
|
10184
|
-
{
|
|
10185
|
-
id: "microwave",
|
|
10186
|
-
name: "Microwave"
|
|
10187
|
-
},
|
|
10188
|
-
{
|
|
10189
|
-
id: "oven",
|
|
10190
|
-
name: "Oven"
|
|
10191
|
-
},
|
|
10192
|
-
{
|
|
10193
|
-
id: "toaster",
|
|
10194
|
-
name: "Toaster"
|
|
10195
|
-
},
|
|
10196
|
-
{
|
|
10197
|
-
id: "sink",
|
|
10198
|
-
name: "Sink"
|
|
10199
|
-
},
|
|
10200
|
-
{
|
|
10201
|
-
id: "refrigerator",
|
|
10202
|
-
name: "Refrigerator"
|
|
10203
|
-
},
|
|
10204
|
-
{
|
|
10205
|
-
id: "book",
|
|
10206
|
-
name: "Book"
|
|
10207
|
-
},
|
|
10208
|
-
{
|
|
10209
|
-
id: "clock",
|
|
10210
|
-
name: "Clock"
|
|
10211
|
-
},
|
|
10212
|
-
{
|
|
10213
|
-
id: "vase",
|
|
10214
|
-
name: "Vase"
|
|
10215
|
-
},
|
|
10216
|
-
{
|
|
10217
|
-
id: "scissors",
|
|
10218
|
-
name: "Scissors"
|
|
10219
|
-
},
|
|
10220
|
-
{
|
|
10221
|
-
id: "teddy bear",
|
|
10222
|
-
name: "Teddy Bear"
|
|
10223
|
-
},
|
|
10224
|
-
{
|
|
10225
|
-
id: "hair drier",
|
|
10226
|
-
name: "Hair Drier"
|
|
10227
|
-
},
|
|
10228
|
-
{
|
|
10229
|
-
id: "toothbrush",
|
|
10230
|
-
name: "Toothbrush"
|
|
10231
|
-
}
|
|
10232
|
-
];
|
|
10233
|
-
var MACRO_LABELS = [
|
|
10234
|
-
{
|
|
10235
|
-
id: "person",
|
|
10236
|
-
name: "Person"
|
|
10237
|
-
},
|
|
10238
|
-
{
|
|
10239
|
-
id: "vehicle",
|
|
10240
|
-
name: "Vehicle"
|
|
10241
|
-
},
|
|
10242
|
-
{
|
|
10243
|
-
id: "animal",
|
|
10244
|
-
name: "Animal"
|
|
10245
|
-
},
|
|
10246
|
-
{
|
|
10247
|
-
id: "package",
|
|
10248
|
-
name: "Package"
|
|
10249
|
-
}
|
|
10250
|
-
];
|
|
10251
|
-
/**
|
|
10252
|
-
* The class names a per-class COUNT can ever be keyed on — the macro ids, and
|
|
10253
|
-
* only those.
|
|
10254
|
-
*
|
|
10255
|
-
* `preserveOriginal: false` below is the whole reason this set exists: the
|
|
10256
|
-
* executor maps each raw COCO label onto its macro and DROPS everything else,
|
|
10257
|
-
* so a tracked detection's `className` is always one of {@link MACRO_LABELS}.
|
|
10258
|
-
* Any surface that indexes counts by class name — the ZoneAnalytics occupancy
|
|
10259
|
-
* snapshot's `byClass`, and therefore every occupancy rule — can match nothing
|
|
10260
|
-
* else. Derived from the catalog, never hand-listed, so a new macro joins it by
|
|
10261
|
-
* being declared once.
|
|
10262
|
-
*
|
|
10263
|
-
* Cost of not having it, 2026-08-12: the notification rule editor offered the
|
|
10264
|
-
* full taxonomy for an occupancy class (occupancy is the one condition that
|
|
10265
|
-
* does not go through the engine's macro-expanding `expandClassSelector`), two
|
|
10266
|
-
* live rules were authored on `car`, and both counters read 0 forever.
|
|
10267
|
-
*/
|
|
10268
|
-
var DETECTION_MACRO_CLASSES = new Set(MACRO_LABELS.map((l) => l.id));
|
|
10269
|
-
/** True when `className` is a macro the detector can actually emit (and hence a
|
|
10270
|
-
* class a count can be keyed on) — see {@link DETECTION_MACRO_CLASSES}. */
|
|
10271
|
-
function isDetectionMacroClass(className) {
|
|
10272
|
-
return DETECTION_MACRO_CLASSES.has(className.trim().toLowerCase());
|
|
10273
|
-
}
|
|
10274
|
-
var COCO_TO_MACRO = {
|
|
10275
|
-
mapping: {
|
|
10276
|
-
person: "person",
|
|
10277
|
-
bicycle: "vehicle",
|
|
10278
|
-
car: "vehicle",
|
|
10279
|
-
motorcycle: "vehicle",
|
|
10280
|
-
airplane: "vehicle",
|
|
10281
|
-
bus: "vehicle",
|
|
10282
|
-
train: "vehicle",
|
|
10283
|
-
truck: "vehicle",
|
|
10284
|
-
boat: "vehicle",
|
|
10285
|
-
bird: "animal",
|
|
10286
|
-
cat: "animal",
|
|
10287
|
-
dog: "animal",
|
|
10288
|
-
horse: "animal",
|
|
10289
|
-
sheep: "animal",
|
|
10290
|
-
cow: "animal",
|
|
10291
|
-
elephant: "animal",
|
|
10292
|
-
bear: "animal",
|
|
10293
|
-
zebra: "animal",
|
|
10294
|
-
giraffe: "animal",
|
|
10295
|
-
suitcase: "package",
|
|
10296
|
-
backpack: "package",
|
|
10297
|
-
handbag: "package"
|
|
10298
|
-
},
|
|
10299
|
-
preserveOriginal: false
|
|
10300
|
-
};
|
|
10301
|
-
var MACRO_NAMES = new Set(Object.values(COCO_TO_MACRO.mapping));
|
|
10302
|
-
function macroOf(className) {
|
|
10303
|
-
return COCO_TO_MACRO.mapping[className] ?? className;
|
|
10304
|
-
}
|
|
10305
|
-
/** `animal` matches a dog; `dog` matches only a dog (a cat is also an animal). */
|
|
10306
|
-
function detectionClassMatches(wanted, actual) {
|
|
10307
|
-
const w = wanted.toLowerCase();
|
|
10308
|
-
const a = actual.toLowerCase();
|
|
10309
|
-
if (w === a) return true;
|
|
10310
|
-
return MACRO_NAMES.has(w) && macroOf(a) === w;
|
|
10311
|
-
}
|
|
10312
|
-
function isPhase$1(value) {
|
|
10313
|
-
return value === "start" || value === "update" || value === "end";
|
|
10314
|
-
}
|
|
10315
|
-
var DetectionTriggerFilter = class {
|
|
10316
|
-
trigger;
|
|
10317
|
-
/** `${deviceId}|${trackId}` → fired at. Insertion-ordered, so the oldest is first. */
|
|
10318
|
-
fired = /* @__PURE__ */ new Map();
|
|
10319
|
-
constructor(trigger) {
|
|
10320
|
-
this.trigger = trigger;
|
|
10321
|
-
}
|
|
10322
|
-
get firedCount() {
|
|
10323
|
-
return this.fired.size;
|
|
10324
|
-
}
|
|
10325
|
-
evaluate(event, now) {
|
|
10326
|
-
const { deviceId, trackId, phase } = event;
|
|
10327
|
-
if (typeof deviceId !== "number" || typeof trackId !== "string" || !isPhase$1(phase)) return {
|
|
10328
|
-
kind: "ignore",
|
|
10329
|
-
why: "malformed track-lifecycle event"
|
|
10330
|
-
};
|
|
10331
|
-
if (!this.trigger.deviceIds.includes(deviceId)) return {
|
|
10332
|
-
kind: "ignore",
|
|
10333
|
-
why: `device ${deviceId} is not one of the trigger's devices`
|
|
10334
|
-
};
|
|
10335
|
-
const key = `${deviceId}|${trackId}`;
|
|
10336
|
-
if (phase === "end") {
|
|
10337
|
-
this.fired.delete(key);
|
|
10338
|
-
return {
|
|
10339
|
-
kind: "ignore",
|
|
10340
|
-
why: "track ended"
|
|
10341
|
-
};
|
|
10342
|
-
}
|
|
10343
|
-
this.expire(now);
|
|
10344
|
-
const className = typeof event.bestClassName === "string" ? event.bestClassName : "";
|
|
10345
|
-
const score = typeof event.bestConfidence === "number" ? event.bestConfidence : 0;
|
|
10346
|
-
const vars = {
|
|
10347
|
-
deviceId,
|
|
10348
|
-
className,
|
|
10349
|
-
trackId,
|
|
10350
|
-
score
|
|
10351
|
-
};
|
|
10352
|
-
if (this.fired.has(key)) return {
|
|
10353
|
-
kind: "reject",
|
|
10354
|
-
reason: `already fired for track ${trackId}`,
|
|
10355
|
-
vars
|
|
10356
|
-
};
|
|
10357
|
-
if (!this.trigger.classes.some((c) => detectionClassMatches(c, className))) return {
|
|
10358
|
-
kind: "reject",
|
|
10359
|
-
reason: `class ${className || "unknown"} not in [${this.trigger.classes.join(", ")}]`,
|
|
10360
|
-
vars
|
|
10361
|
-
};
|
|
10362
|
-
if (this.trigger.minScore !== void 0 && score < this.trigger.minScore) return {
|
|
10363
|
-
kind: "reject",
|
|
10364
|
-
reason: `score ${score} < ${this.trigger.minScore}`,
|
|
10365
|
-
vars
|
|
10366
|
-
};
|
|
10367
|
-
if (this.trigger.zones !== void 0) {
|
|
10368
|
-
const visited = Array.isArray(event.zonesVisited) ? event.zonesVisited.filter((z) => typeof z === "string") : [];
|
|
10369
|
-
if (!this.trigger.zones.some((z) => visited.includes(z))) return {
|
|
10370
|
-
kind: "reject",
|
|
10371
|
-
reason: `zone mismatch: visited [${visited.join(", ")}], wanted [${this.trigger.zones.join(", ")}]`,
|
|
10372
|
-
vars
|
|
10373
|
-
};
|
|
10374
|
-
}
|
|
10375
|
-
this.remember(key, now);
|
|
10376
|
-
return {
|
|
10377
|
-
kind: "fire",
|
|
10378
|
-
reason: `${className} ${score} on device ${deviceId}`,
|
|
10379
|
-
vars
|
|
10380
|
-
};
|
|
10381
|
-
}
|
|
10382
|
-
remember(key, now) {
|
|
10383
|
-
this.fired.set(key, now);
|
|
10384
|
-
while (this.fired.size > 512) {
|
|
10385
|
-
const oldest = this.fired.keys().next();
|
|
10386
|
-
if (oldest.done === true) break;
|
|
10387
|
-
this.fired.delete(oldest.value);
|
|
10388
|
-
}
|
|
10389
|
-
}
|
|
10390
|
-
expire(now) {
|
|
10391
|
-
for (const [key, at] of this.fired) {
|
|
10392
|
-
if (now - at < 9e5) break;
|
|
10393
|
-
this.fired.delete(key);
|
|
10394
|
-
}
|
|
10395
|
-
}
|
|
10396
|
-
};
|
|
10397
|
-
//#endregion
|
|
10398
10881
|
//#region src/automation/condition-eval.ts
|
|
10399
10882
|
/**
|
|
10400
10883
|
* Evaluating an automation's condition tree against the ConditionMirror.
|
|
@@ -10421,7 +10904,7 @@ function collectInto(cond, out) {
|
|
|
10421
10904
|
case "not":
|
|
10422
10905
|
collectInto(cond.child, out);
|
|
10423
10906
|
return;
|
|
10424
|
-
case "
|
|
10907
|
+
case "device-is":
|
|
10425
10908
|
out.push({
|
|
10426
10909
|
kind: "device",
|
|
10427
10910
|
deviceId: cond.deviceId,
|
|
@@ -10442,10 +10925,11 @@ function collectInto(cond, out) {
|
|
|
10442
10925
|
cap: binding.cap
|
|
10443
10926
|
});
|
|
10444
10927
|
return;
|
|
10928
|
+
default: return;
|
|
10445
10929
|
}
|
|
10446
10930
|
}
|
|
10447
|
-
/** Every (device, cap) a tree
|
|
10448
|
-
function
|
|
10931
|
+
/** Every (device, cap) a `when` tree's STATE leaves read, deduplicated. */
|
|
10932
|
+
function collectWhenMirrorSources(cond) {
|
|
10449
10933
|
if (cond === void 0) return [];
|
|
10450
10934
|
const all = [];
|
|
10451
10935
|
collectInto(cond, all);
|
|
@@ -10457,6 +10941,10 @@ function collectMirrorSources(cond) {
|
|
|
10457
10941
|
return true;
|
|
10458
10942
|
});
|
|
10459
10943
|
}
|
|
10944
|
+
/** Legacy name, for blocks generated before the single tree: an old condition tree. */
|
|
10945
|
+
function collectMirrorSources(cond) {
|
|
10946
|
+
return collectWhenMirrorSources(cond === void 0 ? void 0 : convertLegacyCondition(cond));
|
|
10947
|
+
}
|
|
10460
10948
|
function sliceKey(deviceId, cap) {
|
|
10461
10949
|
return `${deviceId}|${cap}`;
|
|
10462
10950
|
}
|
|
@@ -10610,7 +11098,8 @@ function zonesOfParked(zones, parkedId) {
|
|
|
10610
11098
|
function judgeStationaryPresent(cond, reader) {
|
|
10611
11099
|
if (reader.read(cond.deviceId, ZONE_ANALYTICS_CAP, "frame") === void 0) return {
|
|
10612
11100
|
pass: false,
|
|
10613
|
-
why: `device ${cond.deviceId} occupancy snapshot unknown
|
|
11101
|
+
why: `device ${cond.deviceId} occupancy snapshot unknown`,
|
|
11102
|
+
unknown: true
|
|
10614
11103
|
};
|
|
10615
11104
|
const parked = parkedObjectsOf(reader.read(cond.deviceId, ZONE_ANALYTICS_CAP, "stationaryObjects"));
|
|
10616
11105
|
if (parked.length === 0) return {
|
|
@@ -10619,7 +11108,7 @@ function judgeStationaryPresent(cond, reader) {
|
|
|
10619
11108
|
};
|
|
10620
11109
|
const zones = reader.read(cond.deviceId, ZONE_ANALYTICS_CAP, "zones");
|
|
10621
11110
|
for (const obj of parked) {
|
|
10622
|
-
if (cond.
|
|
11111
|
+
if (cond.subjects !== void 0 && !anySubjectMatches(cond.subjects, { className: obj.className }).matches) continue;
|
|
10623
11112
|
if (cond.zones !== void 0) {
|
|
10624
11113
|
const inZones = zonesOfParked(zones, obj.id);
|
|
10625
11114
|
if (!cond.zones.some((z) => inZones.includes(z))) continue;
|
|
@@ -10634,9 +11123,20 @@ function judgeStationaryPresent(cond, reader) {
|
|
|
10634
11123
|
why: `${parked.length} parked on device ${cond.deviceId}, none matching the class/zone filter`
|
|
10635
11124
|
};
|
|
10636
11125
|
}
|
|
10637
|
-
function
|
|
11126
|
+
function evalEventLeaf(cond, path, state, out) {
|
|
11127
|
+
const occurred = state.firedPath === path;
|
|
11128
|
+
out.push({
|
|
11129
|
+
path,
|
|
11130
|
+
kind: cond.kind,
|
|
11131
|
+
outcome: occurred ? "pass" : "fail",
|
|
11132
|
+
reason: occurred ? `${cond.kind} occurred → pass` : `${cond.kind} is an instant and did not occur now → fail`
|
|
11133
|
+
});
|
|
11134
|
+
return occurred;
|
|
11135
|
+
}
|
|
11136
|
+
function evalNode(cond, path, reader, now, out, state) {
|
|
11137
|
+
if (isEventLeaf(cond)) return evalEventLeaf(cond, path, state, out);
|
|
10638
11138
|
switch (cond.kind) {
|
|
10639
|
-
case "
|
|
11139
|
+
case "device-is": {
|
|
10640
11140
|
const observed = reader.read(cond.deviceId, cond.cap, cond.fieldPath);
|
|
10641
11141
|
const base = {
|
|
10642
11142
|
path,
|
|
@@ -10657,6 +11157,7 @@ function evalNode(cond, path, reader, now, out) {
|
|
|
10657
11157
|
return false;
|
|
10658
11158
|
}
|
|
10659
11159
|
if (observed === void 0) {
|
|
11160
|
+
state.unknown = true;
|
|
10660
11161
|
out.push({
|
|
10661
11162
|
...base,
|
|
10662
11163
|
outcome: "fail",
|
|
@@ -10674,6 +11175,7 @@ function evalNode(cond, path, reader, now, out) {
|
|
|
10674
11175
|
}
|
|
10675
11176
|
case "stationary-present": {
|
|
10676
11177
|
const verdict = judgeStationaryPresent(cond, reader);
|
|
11178
|
+
if (verdict.unknown === true) state.unknown = true;
|
|
10677
11179
|
out.push({
|
|
10678
11180
|
path,
|
|
10679
11181
|
kind: cond.kind,
|
|
@@ -10699,6 +11201,7 @@ function evalNode(cond, path, reader, now, out) {
|
|
|
10699
11201
|
outcome: "fail",
|
|
10700
11202
|
reason: `${alias} value unknown → fail`
|
|
10701
11203
|
});
|
|
11204
|
+
state.unknown = true;
|
|
10702
11205
|
return false;
|
|
10703
11206
|
}
|
|
10704
11207
|
values[alias] = require_composition.toExpressionValue(raw) ?? null;
|
|
@@ -10730,7 +11233,7 @@ function evalNode(cond, path, reader, now, out) {
|
|
|
10730
11233
|
outcome: "not-evaluated",
|
|
10731
11234
|
reason: ""
|
|
10732
11235
|
});
|
|
10733
|
-
const inner = evalNode(cond.child, `${path}.0`, reader, now, out);
|
|
11236
|
+
const inner = evalNode(cond.child, `${path}.0`, reader, now, out, state);
|
|
10734
11237
|
const ok = !inner;
|
|
10735
11238
|
out.splice(at, 1, {
|
|
10736
11239
|
path,
|
|
@@ -10758,7 +11261,7 @@ function evalNode(cond, path, reader, now, out) {
|
|
|
10758
11261
|
markSkipped(child, `${path}.${i}`, `short-circuited by ${path}.${decided}`, out);
|
|
10759
11262
|
continue;
|
|
10760
11263
|
}
|
|
10761
|
-
const ok = evalNode(child, `${path}.${i}`, reader, now, out);
|
|
11264
|
+
const ok = evalNode(child, `${path}.${i}`, reader, now, out, state);
|
|
10762
11265
|
if (isAll ? !ok : ok) decided = i;
|
|
10763
11266
|
}
|
|
10764
11267
|
const ok = isAll ? decided === null : decided !== null;
|
|
@@ -10773,20 +11276,167 @@ function evalNode(cond, path, reader, now, out) {
|
|
|
10773
11276
|
}
|
|
10774
11277
|
}
|
|
10775
11278
|
}
|
|
10776
|
-
/**
|
|
11279
|
+
/** Evaluate a `when` tree: the fired event leaf is true, every other event leaf false. */
|
|
11280
|
+
function evaluateWhenTree(when, reader, now, options = {}) {
|
|
11281
|
+
const nodes = [];
|
|
11282
|
+
const state = {
|
|
11283
|
+
firedPath: options.firedPath,
|
|
11284
|
+
unknown: false
|
|
11285
|
+
};
|
|
11286
|
+
return {
|
|
11287
|
+
pass: evalNode(when, options.rootPath ?? "when", reader, now, nodes, state),
|
|
11288
|
+
unknown: state.unknown,
|
|
11289
|
+
nodes
|
|
11290
|
+
};
|
|
11291
|
+
}
|
|
11292
|
+
/** Legacy name, for blocks generated before the single tree. No tree is a pass with no nodes. */
|
|
10777
11293
|
function evaluateConditionTree(cond, reader, now) {
|
|
10778
11294
|
if (cond === void 0) return {
|
|
10779
11295
|
pass: true,
|
|
11296
|
+
unknown: false,
|
|
10780
11297
|
nodes: []
|
|
10781
11298
|
};
|
|
10782
|
-
|
|
10783
|
-
|
|
10784
|
-
|
|
10785
|
-
|
|
10786
|
-
|
|
11299
|
+
return evaluateWhenTree(convertLegacyCondition(cond), reader, now, { rootPath: "conditions" });
|
|
11300
|
+
}
|
|
11301
|
+
//#endregion
|
|
11302
|
+
//#region src/automation/detection-trigger.ts
|
|
11303
|
+
/**
|
|
11304
|
+
* The `detection` trigger's per-event decision: does this track-lifecycle
|
|
11305
|
+
* event start a run, and if not, WHY not.
|
|
11306
|
+
*
|
|
11307
|
+
* Source: `EventCategory.PipelineAnalyticsTrackLifecycle` — payload
|
|
11308
|
+
* {@link PipelineAnalyticsTrackLifecyclePayload} (`deviceId`, `trackId`,
|
|
11309
|
+
* `phase` start|update|end, `bestClassName`, `bestConfidence`,
|
|
11310
|
+
* `zonesVisited`). The event carries the device in its DATA, not on its
|
|
11311
|
+
* `source`, so the filter reads `data.deviceId`.
|
|
11312
|
+
*
|
|
11313
|
+
* ONE fire per `(deviceId, trackId)`: a track fires the first time it satisfies
|
|
11314
|
+
* the subject, score and zone conditions (which may be on an `update`, once its
|
|
11315
|
+
* best confidence has risen, or once its face or plate has been resolved — D683
|
|
11316
|
+
* decides identity late, so a leaf with a label fires on the update that
|
|
11317
|
+
* carries it, never on the first detection), and never again. The fired set is bounded and
|
|
11318
|
+
* forgets a track on its `end` or after {@link FIRED_TTL_MS}, so a track whose
|
|
11319
|
+
* `end` was dropped (events are telemetry, D8) cannot pin memory.
|
|
11320
|
+
*/
|
|
11321
|
+
/** The subjects a trigger asks for, whichever shape it has. */
|
|
11322
|
+
function subjectsOf(trigger) {
|
|
11323
|
+
return "subjects" in trigger ? trigger.subjects : trigger.classes.map(subjectFromClassName);
|
|
11324
|
+
}
|
|
11325
|
+
var MACRO_NAMES = new Set(Object.values(COCO_TO_MACRO.mapping));
|
|
11326
|
+
function macroOf(className) {
|
|
11327
|
+
return COCO_TO_MACRO.mapping[className] ?? className;
|
|
11328
|
+
}
|
|
11329
|
+
/** `animal` matches a dog; `dog` matches only a dog (a cat is also an animal). */
|
|
11330
|
+
function detectionClassMatches(wanted, actual) {
|
|
11331
|
+
const w = wanted.toLowerCase();
|
|
11332
|
+
const a = actual.toLowerCase();
|
|
11333
|
+
if (w === a) return true;
|
|
11334
|
+
return MACRO_NAMES.has(w) && macroOf(a) === w;
|
|
10787
11335
|
}
|
|
11336
|
+
function isPhase$1(value) {
|
|
11337
|
+
return value === "start" || value === "update" || value === "end";
|
|
11338
|
+
}
|
|
11339
|
+
var DetectionTriggerFilter = class {
|
|
11340
|
+
trigger;
|
|
11341
|
+
/** `${deviceId}|${trackId}` → fired at. Insertion-ordered, so the oldest is first. */
|
|
11342
|
+
fired = /* @__PURE__ */ new Map();
|
|
11343
|
+
subjects;
|
|
11344
|
+
constructor(trigger) {
|
|
11345
|
+
this.trigger = trigger;
|
|
11346
|
+
this.subjects = subjectsOf(trigger);
|
|
11347
|
+
}
|
|
11348
|
+
get firedCount() {
|
|
11349
|
+
return this.fired.size;
|
|
11350
|
+
}
|
|
11351
|
+
evaluate(event, now) {
|
|
11352
|
+
const { deviceId, trackId, phase } = event;
|
|
11353
|
+
if (typeof deviceId !== "number" || typeof trackId !== "string" || !isPhase$1(phase)) return {
|
|
11354
|
+
kind: "ignore",
|
|
11355
|
+
why: "malformed track-lifecycle event"
|
|
11356
|
+
};
|
|
11357
|
+
if (!this.trigger.deviceIds.includes(deviceId)) return {
|
|
11358
|
+
kind: "ignore",
|
|
11359
|
+
why: `device ${deviceId} is not one of the trigger's devices`
|
|
11360
|
+
};
|
|
11361
|
+
const key = `${deviceId}|${trackId}`;
|
|
11362
|
+
if (phase === "end") {
|
|
11363
|
+
this.fired.delete(key);
|
|
11364
|
+
return {
|
|
11365
|
+
kind: "ignore",
|
|
11366
|
+
why: "track ended"
|
|
11367
|
+
};
|
|
11368
|
+
}
|
|
11369
|
+
this.expire(now);
|
|
11370
|
+
const className = typeof event.bestClassName === "string" ? event.bestClassName : "";
|
|
11371
|
+
const score = typeof event.bestConfidence === "number" ? event.bestConfidence : 0;
|
|
11372
|
+
const vars = {
|
|
11373
|
+
deviceId,
|
|
11374
|
+
className,
|
|
11375
|
+
trackId,
|
|
11376
|
+
score,
|
|
11377
|
+
label: ""
|
|
11378
|
+
};
|
|
11379
|
+
if (this.fired.has(key)) return {
|
|
11380
|
+
kind: "reject",
|
|
11381
|
+
reason: `already fired for track ${trackId}`,
|
|
11382
|
+
vars
|
|
11383
|
+
};
|
|
11384
|
+
const verdict = anySubjectMatches(this.subjects, {
|
|
11385
|
+
className,
|
|
11386
|
+
...Array.isArray(event.classes) ? { classes: event.classes.filter((c) => typeof c === "string") } : {},
|
|
11387
|
+
...typeof event.label === "string" ? { label: event.label } : {},
|
|
11388
|
+
...typeof event.identityId === "string" ? { identityId: event.identityId } : {},
|
|
11389
|
+
...typeof event.plateText === "string" ? { plateText: event.plateText } : {}
|
|
11390
|
+
});
|
|
11391
|
+
if (!verdict.matches) return {
|
|
11392
|
+
kind: "reject",
|
|
11393
|
+
reason: verdict.reason,
|
|
11394
|
+
vars
|
|
11395
|
+
};
|
|
11396
|
+
const matched = {
|
|
11397
|
+
...vars,
|
|
11398
|
+
label: verdict.label
|
|
11399
|
+
};
|
|
11400
|
+
if (this.trigger.minScore !== void 0 && score < this.trigger.minScore) return {
|
|
11401
|
+
kind: "reject",
|
|
11402
|
+
reason: `score ${score} < ${this.trigger.minScore}`,
|
|
11403
|
+
vars: matched
|
|
11404
|
+
};
|
|
11405
|
+
if (this.trigger.zones !== void 0) {
|
|
11406
|
+
const visited = Array.isArray(event.zonesVisited) ? event.zonesVisited.filter((z) => typeof z === "string") : [];
|
|
11407
|
+
if (!this.trigger.zones.some((z) => visited.includes(z))) return {
|
|
11408
|
+
kind: "reject",
|
|
11409
|
+
reason: `zone mismatch: visited [${visited.join(", ")}], wanted [${this.trigger.zones.join(", ")}]`,
|
|
11410
|
+
vars: matched
|
|
11411
|
+
};
|
|
11412
|
+
}
|
|
11413
|
+
this.remember(key, now);
|
|
11414
|
+
return {
|
|
11415
|
+
kind: "fire",
|
|
11416
|
+
reason: `${verdict.label === "" ? className : verdict.label} ${score} on device ${deviceId}`,
|
|
11417
|
+
vars: matched
|
|
11418
|
+
};
|
|
11419
|
+
}
|
|
11420
|
+
remember(key, now) {
|
|
11421
|
+
this.fired.set(key, now);
|
|
11422
|
+
while (this.fired.size > 512) {
|
|
11423
|
+
const oldest = this.fired.keys().next();
|
|
11424
|
+
if (oldest.done === true) break;
|
|
11425
|
+
this.fired.delete(oldest.value);
|
|
11426
|
+
}
|
|
11427
|
+
}
|
|
11428
|
+
expire(now) {
|
|
11429
|
+
for (const [key, at] of this.fired) {
|
|
11430
|
+
if (now - at < 9e5) break;
|
|
11431
|
+
this.fired.delete(key);
|
|
11432
|
+
}
|
|
11433
|
+
}
|
|
11434
|
+
};
|
|
10788
11435
|
//#endregion
|
|
10789
11436
|
//#region src/automation/stationary-trigger.ts
|
|
11437
|
+
function isLegacyStationary(trigger) {
|
|
11438
|
+
return "classes" in trigger;
|
|
11439
|
+
}
|
|
10790
11440
|
function isPhase(value) {
|
|
10791
11441
|
return value === "appeared" || value === "departed";
|
|
10792
11442
|
}
|
|
@@ -10813,11 +11463,15 @@ function evaluateStationaryTrigger(trigger, event) {
|
|
|
10813
11463
|
phase,
|
|
10814
11464
|
entryId
|
|
10815
11465
|
};
|
|
10816
|
-
|
|
10817
|
-
|
|
10818
|
-
|
|
10819
|
-
|
|
10820
|
-
|
|
11466
|
+
const subjects = isLegacyStationary(trigger) ? trigger.classes?.map(subjectFromClassName) : trigger.subjects;
|
|
11467
|
+
if (subjects !== void 0) {
|
|
11468
|
+
const verdict = anySubjectMatches(subjects, { className });
|
|
11469
|
+
if (!verdict.matches) return {
|
|
11470
|
+
kind: "reject",
|
|
11471
|
+
reason: verdict.reason,
|
|
11472
|
+
vars
|
|
11473
|
+
};
|
|
11474
|
+
}
|
|
10821
11475
|
if (trigger.zones !== void 0 && !trigger.zones.some((z) => zones.includes(z))) return {
|
|
10822
11476
|
kind: "reject",
|
|
10823
11477
|
reason: `zone mismatch: in [${zones.join(", ")}], wanted [${trigger.zones.join(", ")}]`,
|
|
@@ -10956,11 +11610,12 @@ var RunGate = class {
|
|
|
10956
11610
|
* Run traces — what an automation (or, later, an Action run) DID, node by node,
|
|
10957
11611
|
* so the editor can paint every block green or red with its reason.
|
|
10958
11612
|
*
|
|
10959
|
-
* A trace is data, not a log: it covers the WHOLE tree — the
|
|
10960
|
-
* every
|
|
10961
|
-
* short-circuit never reached), the run-mode verdict and every action
|
|
10962
|
-
* each addressed by a stable path (`
|
|
10963
|
-
* for an Action run).
|
|
11613
|
+
* A trace is data, not a log: it covers the WHOLE tree — the event leaf that
|
|
11614
|
+
* fired, every node of the `when` tree (containers and leaves, including the
|
|
11615
|
+
* ones a short-circuit never reached), the run-mode verdict and every action
|
|
11616
|
+
* step — each addressed by a stable path (`when.0.1`, `actions.2`, or `steps.N`
|
|
11617
|
+
* for an Action run). Traces written before the single tree carry
|
|
11618
|
+
* `conditions.*` paths and still parse. Skipped
|
|
10964
11619
|
* evaluations are traced too, and identical consecutive skips collapse into a
|
|
10965
11620
|
* repeat count so a trigger that fails every 5 minutes cannot flush real runs.
|
|
10966
11621
|
*
|
|
@@ -10992,6 +11647,8 @@ var TraceNodeOutcomeSchema = zod.z.enum([
|
|
|
10992
11647
|
]);
|
|
10993
11648
|
var AutomationTriggerTraceSchema = zod.z.object({
|
|
10994
11649
|
kind: zod.z.string().min(1),
|
|
11650
|
+
/** The event leaf that fired (`when`, `when.0.1`); absent on traces written before the single tree. */
|
|
11651
|
+
path: zod.z.string().min(1).optional(),
|
|
10995
11652
|
outcome: zod.z.enum(["pass", "fail"]),
|
|
10996
11653
|
/** Why an event was rejected ("class person not in [dog]") or what fired. */
|
|
10997
11654
|
reason: zod.z.string(),
|
|
@@ -10999,15 +11656,23 @@ var AutomationTriggerTraceSchema = zod.z.object({
|
|
|
10999
11656
|
vars: zod.z.record(zod.z.string(), TraceValueSchema)
|
|
11000
11657
|
});
|
|
11001
11658
|
var AutomationConditionTraceNodeSchema = zod.z.object({
|
|
11002
|
-
/** `
|
|
11659
|
+
/** `when` for the root, `when.<i>` for its i-th child, … (`conditions…` on an old trace). */
|
|
11003
11660
|
path: zod.z.string().min(1),
|
|
11004
11661
|
kind: zod.z.enum([
|
|
11005
11662
|
"all",
|
|
11006
11663
|
"any",
|
|
11007
11664
|
"not",
|
|
11008
11665
|
"condition",
|
|
11666
|
+
"device-is",
|
|
11009
11667
|
"expression",
|
|
11010
|
-
"stationary-present"
|
|
11668
|
+
"stationary-present",
|
|
11669
|
+
"device-becomes",
|
|
11670
|
+
"device-event",
|
|
11671
|
+
"schedule",
|
|
11672
|
+
"interval",
|
|
11673
|
+
"manual",
|
|
11674
|
+
"detection",
|
|
11675
|
+
"stationary"
|
|
11011
11676
|
]),
|
|
11012
11677
|
outcome: TraceNodeOutcomeSchema,
|
|
11013
11678
|
reason: zod.z.string(),
|
|
@@ -11041,6 +11706,12 @@ var AutomationStepTraceSchema = zod.z.object({
|
|
|
11041
11706
|
deviceId: zod.z.number().int().optional(),
|
|
11042
11707
|
startedAt: zod.z.number().optional(),
|
|
11043
11708
|
endedAt: zod.z.number().optional(),
|
|
11709
|
+
/**
|
|
11710
|
+
* The parameters the step SENT, after `{{vars.x}}` substitution — what the
|
|
11711
|
+
* device was actually asked. Bounded like trigger `vars`; a secret parameter
|
|
11712
|
+
* is shown as {@link REDACTED_ARG}. Absent on traces written before it existed.
|
|
11713
|
+
*/
|
|
11714
|
+
args: zod.z.record(zod.z.string(), TraceValueSchema).optional(),
|
|
11044
11715
|
outcome: zod.z.enum([
|
|
11045
11716
|
"ok",
|
|
11046
11717
|
"error",
|
|
@@ -11132,20 +11803,53 @@ function appendTrace(ring, trace, keep) {
|
|
|
11132
11803
|
}
|
|
11133
11804
|
return next;
|
|
11134
11805
|
}
|
|
11806
|
+
/** What a secret parameter is shown as in a trace. */
|
|
11807
|
+
var REDACTED_ARG = "[redacted]";
|
|
11808
|
+
/** Parameters kept per step trace; the rest are counted in `…`. */
|
|
11809
|
+
var MAX_TRACE_ARGS = 32;
|
|
11810
|
+
/** A string value is cut here. */
|
|
11811
|
+
var MAX_TRACE_ARG_LENGTH = 200;
|
|
11812
|
+
/** A parameter whose NAME says it is a credential. */
|
|
11813
|
+
var SECRET_ARG_NAME = /pass(word|phrase|code)?|secret|token|api[-_]?key|credential|\bpin\b|auth/i;
|
|
11814
|
+
function isSecretArg(name, value) {
|
|
11815
|
+
return SECRET_ARG_NAME.test(name) || require_sleep.isSecretMarkerLike(value);
|
|
11816
|
+
}
|
|
11817
|
+
/**
|
|
11818
|
+
* The parameters a step sent, as a trace may show them: primitives and short
|
|
11819
|
+
* lists of them (anything deeper is its JSON text, cut), at most
|
|
11820
|
+
* {@link MAX_TRACE_ARGS} of them. No cap method declares a parameter secret, so
|
|
11821
|
+
* a parameter is redacted by what it is called or by being a keychain marker,
|
|
11822
|
+
* and `secretKeys` names the ones a caller knows about.
|
|
11823
|
+
*/
|
|
11824
|
+
function buildStepArgsTrace(args, secretKeys = []) {
|
|
11825
|
+
const out = {};
|
|
11826
|
+
for (const [key, value] of Object.entries(args).slice(0, 32)) if (secretKeys.includes(key) || isSecretArg(key, value)) out[key] = REDACTED_ARG;
|
|
11827
|
+
else if (typeof value === "string") out[key] = value.length > 200 ? `${value.slice(0, 200)}…` : value;
|
|
11828
|
+
else if (typeof value === "number" || typeof value === "boolean" || value === null) out[key] = value;
|
|
11829
|
+
else if (Array.isArray(value) && value.every((v) => typeof v === "string" || typeof v === "number")) out[key] = value;
|
|
11830
|
+
else if (value !== void 0) {
|
|
11831
|
+
const text = JSON.stringify(value) ?? "";
|
|
11832
|
+
out[key] = text.length > 200 ? `${text.slice(0, 200)}…` : text;
|
|
11833
|
+
}
|
|
11834
|
+
return out;
|
|
11835
|
+
}
|
|
11135
11836
|
/**
|
|
11136
11837
|
* The trigger stage of a trace from the bag a trigger handed to `run()`:
|
|
11137
11838
|
* primitives and short lists of them (a device-state `to` that is an object is left out), minus the
|
|
11138
|
-
* bookkeeping keys `kind` and `
|
|
11839
|
+
* bookkeeping keys `kind`, `at` and `leaf` (the fired leaf's path, which moves to `path`).
|
|
11139
11840
|
*/
|
|
11140
11841
|
function buildTriggerTrace(trigger, outcome, reason) {
|
|
11141
11842
|
const vars = {};
|
|
11142
11843
|
for (const [key, value] of Object.entries(trigger)) {
|
|
11143
|
-
if (key === "kind" || key === "at") continue;
|
|
11844
|
+
if (key === "kind" || key === "at" || key === "leaf") continue;
|
|
11144
11845
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) vars[key] = value;
|
|
11145
11846
|
else if (Array.isArray(value) && value.every((v) => typeof v === "string" || typeof v === "number")) vars[key] = value;
|
|
11146
11847
|
}
|
|
11848
|
+
const kind = typeof trigger["kind"] === "string" ? trigger["kind"] : "unknown";
|
|
11849
|
+
const path = typeof trigger["leaf"] === "string" ? trigger["leaf"] : void 0;
|
|
11147
11850
|
return {
|
|
11148
|
-
kind
|
|
11851
|
+
kind,
|
|
11852
|
+
...path === void 0 ? {} : { path },
|
|
11149
11853
|
outcome,
|
|
11150
11854
|
reason,
|
|
11151
11855
|
vars
|
|
@@ -11383,14 +12087,22 @@ async function runSteps(steps, options) {
|
|
|
11383
12087
|
}
|
|
11384
12088
|
const startedAt = options.now();
|
|
11385
12089
|
let deviceId = step.meta.deviceId;
|
|
11386
|
-
|
|
11387
|
-
|
|
11388
|
-
|
|
12090
|
+
let sent;
|
|
12091
|
+
const scope = {
|
|
12092
|
+
setDeviceId: (id) => {
|
|
12093
|
+
deviceId = id;
|
|
12094
|
+
},
|
|
12095
|
+
setArgs: (args) => {
|
|
12096
|
+
sent = args;
|
|
12097
|
+
}
|
|
12098
|
+
};
|
|
12099
|
+
const argsTrace = () => sent === void 0 ? {} : { args: buildStepArgsTrace(sent) };
|
|
11389
12100
|
try {
|
|
11390
12101
|
await step.run(scope);
|
|
11391
12102
|
traces.push({
|
|
11392
12103
|
...step.meta,
|
|
11393
12104
|
...deviceId === void 0 ? {} : { deviceId },
|
|
12105
|
+
...argsTrace(),
|
|
11394
12106
|
path,
|
|
11395
12107
|
startedAt,
|
|
11396
12108
|
endedAt: options.now(),
|
|
@@ -11408,6 +12120,7 @@ async function runSteps(steps, options) {
|
|
|
11408
12120
|
traces.push({
|
|
11409
12121
|
...step.meta,
|
|
11410
12122
|
...deviceId === void 0 ? {} : { deviceId },
|
|
12123
|
+
...argsTrace(),
|
|
11411
12124
|
path,
|
|
11412
12125
|
startedAt,
|
|
11413
12126
|
endedAt: options.now(),
|
|
@@ -11494,6 +12207,7 @@ function buildActionSteps(steps, vars, deps) {
|
|
|
11494
12207
|
const targets = typeof step.deviceId === "number" ? [step.deviceId] : Array.isArray(step.deviceId) ? step.deviceId : [resolveDeviceTarget(vars, step.deviceId.$var)];
|
|
11495
12208
|
for (const deviceId of targets) {
|
|
11496
12209
|
scope.setDeviceId(deviceId);
|
|
12210
|
+
scope.setArgs(args);
|
|
11497
12211
|
await deps.invokeCap({
|
|
11498
12212
|
deviceId,
|
|
11499
12213
|
cap: step.cap,
|
|
@@ -11633,9 +12347,9 @@ function normalizeLeaf(leaf, resolver, path) {
|
|
|
11633
12347
|
};
|
|
11634
12348
|
}
|
|
11635
12349
|
/** Normalise every leaf of a condition tree. Throws {@link QuantityUnitError}. */
|
|
11636
|
-
function normalizeConditionUnits(condition, resolver, path = "
|
|
12350
|
+
function normalizeConditionUnits(condition, resolver, path = "when") {
|
|
11637
12351
|
switch (condition.kind) {
|
|
11638
|
-
case "
|
|
12352
|
+
case "device-is": {
|
|
11639
12353
|
const { leaf, warnings } = normalizeLeaf(condition, resolver, path);
|
|
11640
12354
|
return {
|
|
11641
12355
|
value: leaf,
|
|
@@ -11728,17 +12442,17 @@ function normalizeActionUnits(actions, resolver, path = "steps") {
|
|
|
11728
12442
|
warnings
|
|
11729
12443
|
};
|
|
11730
12444
|
}
|
|
11731
|
-
/** Normalise a whole recipe: its
|
|
12445
|
+
/** Normalise a whole recipe: its `when` tree and its actions. */
|
|
11732
12446
|
function normalizeRecipeUnits(recipe, resolver) {
|
|
11733
|
-
const
|
|
12447
|
+
const when = normalizeConditionUnits(recipe.when, resolver, "when");
|
|
11734
12448
|
const actions = normalizeActionUnits(recipe.actions, resolver, "actions");
|
|
11735
12449
|
return {
|
|
11736
12450
|
value: {
|
|
11737
12451
|
...recipe,
|
|
11738
|
-
|
|
12452
|
+
when: when.value,
|
|
11739
12453
|
actions: actions.value
|
|
11740
12454
|
},
|
|
11741
|
-
warnings: [...
|
|
12455
|
+
warnings: [...when.warnings, ...actions.warnings]
|
|
11742
12456
|
};
|
|
11743
12457
|
}
|
|
11744
12458
|
//#endregion
|
|
@@ -11761,7 +12475,7 @@ function normalizeRecipeUnits(recipe, resolver) {
|
|
|
11761
12475
|
*/
|
|
11762
12476
|
function stampCondition(condition, resolver) {
|
|
11763
12477
|
switch (condition.kind) {
|
|
11764
|
-
case "
|
|
12478
|
+
case "device-is": {
|
|
11765
12479
|
if (typeof condition.value !== "number" || condition.unit !== void 0) return condition;
|
|
11766
12480
|
const quantity = resolver.fieldQuantity(condition.cap, condition.fieldPath);
|
|
11767
12481
|
if (quantity === void 0 || unitsForQuantity(quantity).length < 2) return condition;
|
|
@@ -11804,7 +12518,7 @@ function stampAction(action, resolver) {
|
|
|
11804
12518
|
function prepareRecipeUnits(recipe, resolver) {
|
|
11805
12519
|
const stamped = {
|
|
11806
12520
|
...recipe,
|
|
11807
|
-
|
|
12521
|
+
when: stampCondition(recipe.when, resolver),
|
|
11808
12522
|
actions: recipe.actions.map((action) => stampAction(action, resolver))
|
|
11809
12523
|
};
|
|
11810
12524
|
try {
|
|
@@ -12609,6 +13323,25 @@ var addonSettingsCapability = {
|
|
|
12609
13323
|
addonId: zod.z.string(),
|
|
12610
13324
|
nodeId: zod.z.string().optional()
|
|
12611
13325
|
}), SettingsSchemaWithValuesSchema.nullable()),
|
|
13326
|
+
getDeclaredSettingsKeys: require_sleep.method(zod.z.object({
|
|
13327
|
+
addonId: zod.z.string(),
|
|
13328
|
+
nodeId: zod.z.string().optional()
|
|
13329
|
+
}), zod.z.object({ keys: zod.z.array(zod.z.string()) }).nullable(), { auth: "admin" }),
|
|
13330
|
+
unsetStoredSettingsKeys: require_sleep.method(zod.z.object({
|
|
13331
|
+
addonId: zod.z.string(),
|
|
13332
|
+
nodeId: zod.z.string().optional(),
|
|
13333
|
+
keys: zod.z.array(zod.z.string())
|
|
13334
|
+
}), zod.z.object({
|
|
13335
|
+
removed: zod.z.array(zod.z.string()),
|
|
13336
|
+
refused: zod.z.array(zod.z.object({
|
|
13337
|
+
key: zod.z.string(),
|
|
13338
|
+
reason: zod.z.string()
|
|
13339
|
+
})),
|
|
13340
|
+
secretsCleared: zod.z.number()
|
|
13341
|
+
}), {
|
|
13342
|
+
kind: "mutation",
|
|
13343
|
+
auth: "admin"
|
|
13344
|
+
}),
|
|
12612
13345
|
getDeviceSettings: require_sleep.method(zod.z.object({
|
|
12613
13346
|
addonId: zod.z.string(),
|
|
12614
13347
|
deviceId: zod.z.number(),
|
|
@@ -14685,8 +15418,21 @@ var BrokerProviderInfoSchema = zod.z.object({
|
|
|
14685
15418
|
* (`{ sessionId }`) drops the session. The dialog that edits a broker
|
|
14686
15419
|
* offers it as the default path; the fields the session fills are
|
|
14687
15420
|
* marked `filledBySignIn` in the kind's settings schema.
|
|
15421
|
+
*
|
|
15422
|
+
* `methods` lists the ways the session can sign in, recommended first:
|
|
15423
|
+
* `'device-code'` adds `POST {basePath}/code/start` (body: the form's
|
|
15424
|
+
* settings) answering `{ sessionId, userCode, verificationUrl,
|
|
15425
|
+
* expiresAt }` — the operator types `userCode` at `verificationUrl` on
|
|
15426
|
+
* any device, and the same `status` / `commit` / `cancel` routes follow
|
|
15427
|
+
* (status then carries `method`, `userCode`, `verificationUrl`,
|
|
15428
|
+
* `expiresAt` and the phases `'awaiting-code'` / `'expired'`);
|
|
15429
|
+
* `'proxy'` is the `{basePath}/start` flow above. Absent means
|
|
15430
|
+
* `['proxy']`.
|
|
14688
15431
|
*/
|
|
14689
|
-
signIn: zod.z.object({
|
|
15432
|
+
signIn: zod.z.object({
|
|
15433
|
+
basePath: zod.z.string().min(1),
|
|
15434
|
+
methods: zod.z.array(zod.z.enum(["device-code", "proxy"])).optional()
|
|
15435
|
+
}).optional()
|
|
14690
15436
|
}))
|
|
14691
15437
|
});
|
|
14692
15438
|
var ListInputSchema = zod.z.object({
|
|
@@ -20734,6 +21480,8 @@ var StorageKindSchema = zod.z.object({
|
|
|
20734
21480
|
"orphaned",
|
|
20735
21481
|
"dropped"
|
|
20736
21482
|
]),
|
|
21483
|
+
/** An owner declared this kind since the engine started. */
|
|
21484
|
+
declaredThisBoot: zod.z.boolean(),
|
|
20737
21485
|
/** `null` when the caller asked for no counts (`counts: false`). */
|
|
20738
21486
|
rows: zod.z.number().int().nonnegative().nullable(),
|
|
20739
21487
|
/** Rows x the average size of a sample; null for an empty kind. */
|
|
@@ -20745,6 +21493,51 @@ var StorageKindSchema = zod.z.object({
|
|
|
20745
21493
|
/** The whole declaration, secret paths and entry descriptor included (the entries service builds its handle from it). */
|
|
20746
21494
|
declaration: require_sleep.RecordKindDeclarationSchema
|
|
20747
21495
|
});
|
|
21496
|
+
zod.z.enum([
|
|
21497
|
+
"orphaned",
|
|
21498
|
+
"stale",
|
|
21499
|
+
"unknown"
|
|
21500
|
+
]);
|
|
21501
|
+
/** A record kind nobody declared since its owner started, or whose owner is gone. */
|
|
21502
|
+
var StaleKindFindingSchema = zod.z.object({
|
|
21503
|
+
owner: zod.z.string(),
|
|
21504
|
+
kind: zod.z.string(),
|
|
21505
|
+
/** `unknown`: the owner could not be judged (not running, or its state unreadable) - never dropped. */
|
|
21506
|
+
verdict: zod.z.enum(["orphaned", "unknown"]),
|
|
21507
|
+
reason: zod.z.string(),
|
|
21508
|
+
rows: zod.z.number().int().nonnegative().nullable(),
|
|
21509
|
+
/** local-auth's tables are never dropped by this audit. */
|
|
21510
|
+
protected: zod.z.boolean()
|
|
21511
|
+
});
|
|
21512
|
+
/** A stored global-settings key the running addon no longer declares. */
|
|
21513
|
+
var StaleSettingsKeyFindingSchema = zod.z.object({
|
|
21514
|
+
addonId: zod.z.string(),
|
|
21515
|
+
/** The stored key as written, a per-node `<key>@<nodeId>` suffix included. */
|
|
21516
|
+
key: zod.z.string(),
|
|
21517
|
+
/**
|
|
21518
|
+
* Keychain refs whose ONLY marker sits under this key: in use by stale data,
|
|
21519
|
+
* not orphans yet. Removing the key releases them.
|
|
21520
|
+
*/
|
|
21521
|
+
secretRefs: zod.z.array(zod.z.string())
|
|
21522
|
+
});
|
|
21523
|
+
/** An addon the audit could not judge, with the reason. Never reported as stale. */
|
|
21524
|
+
var StaleUnknownSchema = zod.z.object({
|
|
21525
|
+
subject: zod.z.string(),
|
|
21526
|
+
reason: zod.z.string()
|
|
21527
|
+
});
|
|
21528
|
+
var StaleDataReportSchema = zod.z.object({
|
|
21529
|
+
/** Epoch ms of the last finished scan; `null` before the first. */
|
|
21530
|
+
scannedAt: zod.z.number().nullable(),
|
|
21531
|
+
scanning: zod.z.boolean(),
|
|
21532
|
+
trigger: zod.z.enum([
|
|
21533
|
+
"boot",
|
|
21534
|
+
"interval",
|
|
21535
|
+
"manual"
|
|
21536
|
+
]).nullable(),
|
|
21537
|
+
kinds: zod.z.array(StaleKindFindingSchema),
|
|
21538
|
+
settingsKeys: zod.z.array(StaleSettingsKeyFindingSchema),
|
|
21539
|
+
unknown: zod.z.array(StaleUnknownSchema)
|
|
21540
|
+
});
|
|
20748
21541
|
/**
|
|
20749
21542
|
* storage-admin - the operator's maintenance reach into the databases: list
|
|
20750
21543
|
* their tables and record kinds, read a kind for diagnosis. Read-only: a table
|
|
@@ -20781,7 +21574,49 @@ var storageAdminCapability = {
|
|
|
20781
21574
|
readRecords: require_sleep.method(RecordQueryInputSchema.extend({
|
|
20782
21575
|
owner: zod.z.string().min(1),
|
|
20783
21576
|
kind: require_sleep.RecordKindNameSchema
|
|
20784
|
-
}), RecordQueryResultSchema, { auth: "admin" })
|
|
21577
|
+
}), RecordQueryResultSchema, { auth: "admin" }),
|
|
21578
|
+
/**
|
|
21579
|
+
* The last stale-data scan (read-only detection): record kinds left behind
|
|
21580
|
+
* and settings keys no running addon declares. Held in memory; a scan runs
|
|
21581
|
+
* at hub boot, every 24 h and on `scanStaleData`.
|
|
21582
|
+
*/
|
|
21583
|
+
staleDataReport: require_sleep.method(zod.z.object({}), StaleDataReportSchema, { auth: "admin" }),
|
|
21584
|
+
/** Scan now. Detection only: it logs findings (owner and key, never values) and changes nothing. */
|
|
21585
|
+
scanStaleData: require_sleep.method(zod.z.object({}), StaleDataReportSchema, {
|
|
21586
|
+
kind: "mutation",
|
|
21587
|
+
auth: "admin"
|
|
21588
|
+
}),
|
|
21589
|
+
/**
|
|
21590
|
+
* Drop an ORPHANED kind: its rows, their keychain values, then mark it
|
|
21591
|
+
* dropped. The verdict is re-checked at call time; anything not still
|
|
21592
|
+
* orphaned (or a protected owner) is refused.
|
|
21593
|
+
*/
|
|
21594
|
+
dropOrphanedKind: require_sleep.method(zod.z.object({
|
|
21595
|
+
owner: zod.z.string().min(1),
|
|
21596
|
+
kind: require_sleep.RecordKindNameSchema
|
|
21597
|
+
}), zod.z.object({
|
|
21598
|
+
deleted: zod.z.number().int().nonnegative(),
|
|
21599
|
+
secretsReleased: zod.z.number().int().nonnegative()
|
|
21600
|
+
}), {
|
|
21601
|
+
kind: "mutation",
|
|
21602
|
+
auth: "admin",
|
|
21603
|
+
caller: "required"
|
|
21604
|
+
}),
|
|
21605
|
+
/**
|
|
21606
|
+
* Unset one stale settings key on a running addon (an owner-side write that
|
|
21607
|
+
* also clears the keychain values the key held). Re-checked at call time.
|
|
21608
|
+
*/
|
|
21609
|
+
unsetStaleSettingsKey: require_sleep.method(zod.z.object({
|
|
21610
|
+
addonId: zod.z.string().min(1),
|
|
21611
|
+
key: zod.z.string().min(1)
|
|
21612
|
+
}), zod.z.object({
|
|
21613
|
+
removed: zod.z.boolean(),
|
|
21614
|
+
secretsCleared: zod.z.number().int().nonnegative()
|
|
21615
|
+
}), {
|
|
21616
|
+
kind: "mutation",
|
|
21617
|
+
auth: "admin",
|
|
21618
|
+
caller: "required"
|
|
21619
|
+
})
|
|
20785
21620
|
}
|
|
20786
21621
|
};
|
|
20787
21622
|
//#endregion
|
|
@@ -20961,6 +21796,19 @@ var keychainCapability = {
|
|
|
20961
21796
|
kind: "mutation",
|
|
20962
21797
|
auth: "admin"
|
|
20963
21798
|
}),
|
|
21799
|
+
/**
|
|
21800
|
+
* Release the values rows of a DROPPED record kind held, whatever the kind's
|
|
21801
|
+
* owner (stale-data audit). Reserved to `sqlite-settings`, the engine that
|
|
21802
|
+
* just deleted the rows: only refs scoped `row/records:<kind>/...` are
|
|
21803
|
+
* accepted, and the audit names the operator it acted for. Answers a count.
|
|
21804
|
+
*/
|
|
21805
|
+
releaseRecordRefs: require_sleep.method(zod.z.object({
|
|
21806
|
+
refs: RefListSchema,
|
|
21807
|
+
onBehalfOf: zod.z.string().min(1)
|
|
21808
|
+
}), zod.z.object({ released: zod.z.number().int() }), {
|
|
21809
|
+
kind: "mutation",
|
|
21810
|
+
auth: "admin"
|
|
21811
|
+
}),
|
|
20964
21812
|
/** The caller's own refs, metadata only. */
|
|
20965
21813
|
listMeta: require_sleep.method(zod.z.object({}), zod.z.array(KeychainSecretMetaSchema).readonly(), { auth: "admin" }),
|
|
20966
21814
|
/** Whether values can be served — the save path refuses a write while locked. */
|
|
@@ -22281,7 +23129,9 @@ var DiscoveredChildDeviceSchema = zod.z.lazy(() => zod.z.object({
|
|
|
22281
23129
|
/** Upstream-system identity + rendering hints for the candidate. */
|
|
22282
23130
|
sourceInfo: SourceInfoSchema.optional(),
|
|
22283
23131
|
/** Nested entity-children for accordion preview — integrations populate, Hubs omit. */
|
|
22284
|
-
children: zod.z.array(DiscoveredChildDeviceSchema).readonly().optional()
|
|
23132
|
+
children: zod.z.array(DiscoveredChildDeviceSchema).readonly().optional(),
|
|
23133
|
+
/** `childNativeId` of the gateway candidate in the same list this device sits behind. */
|
|
23134
|
+
parentStableId: zod.z.string().optional()
|
|
22285
23135
|
}));
|
|
22286
23136
|
/**
|
|
22287
23137
|
* Status block — populated by the integration after each
|
|
@@ -42887,6 +43737,79 @@ var DiscoveredDeviceSchema = zod.z.object({
|
|
|
42887
43737
|
metadata: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
42888
43738
|
});
|
|
42889
43739
|
/**
|
|
43740
|
+
* HOW a provider can be found on the network. The provider only DECLARES the
|
|
43741
|
+
* mode; the `local-network` scan engine runs it once for every provider that
|
|
43742
|
+
* asked (one multicast browse, one host sweep) and hands the raw hits back to
|
|
43743
|
+
* `classifyDiscoveryHits`. A provider never opens a discovery socket itself,
|
|
43744
|
+
* except through a `custom` mode.
|
|
43745
|
+
*/
|
|
43746
|
+
var DiscoveryModeSchema = zod.z.discriminatedUnion("kind", [
|
|
43747
|
+
zod.z.object({
|
|
43748
|
+
kind: zod.z.literal("mdns"),
|
|
43749
|
+
/** DNS-SD service type, e.g. `_esphomelib._tcp`. */
|
|
43750
|
+
serviceType: zod.z.string().min(1),
|
|
43751
|
+
/** Regular expression an instance name must match (case-insensitive). */
|
|
43752
|
+
instancePattern: zod.z.string().optional()
|
|
43753
|
+
}),
|
|
43754
|
+
zod.z.object({
|
|
43755
|
+
kind: zod.z.literal("ws-discovery"),
|
|
43756
|
+
/** Probe types (e.g. `dn:NetworkVideoTransmitter`); omitted = any device. */
|
|
43757
|
+
types: zod.z.array(zod.z.string()).optional()
|
|
43758
|
+
}),
|
|
43759
|
+
zod.z.object({
|
|
43760
|
+
kind: zod.z.literal("ssdp"),
|
|
43761
|
+
searchTarget: zod.z.string().min(1)
|
|
43762
|
+
}),
|
|
43763
|
+
zod.z.object({
|
|
43764
|
+
kind: zod.z.literal("udp-broadcast"),
|
|
43765
|
+
port: zod.z.number().int().min(1).max(65535),
|
|
43766
|
+
payloadHex: zod.z.string().regex(/^([0-9a-fA-F]{2})*$/),
|
|
43767
|
+
/** Port replies arrive on, when the device answers somewhere other than the source port. */
|
|
43768
|
+
listenPort: zod.z.number().int().min(1).max(65535).optional()
|
|
43769
|
+
}),
|
|
43770
|
+
zod.z.object({
|
|
43771
|
+
kind: zod.z.literal("http-probe"),
|
|
43772
|
+
port: zod.z.number().int().min(1).max(65535),
|
|
43773
|
+
path: zod.z.string().startsWith("/"),
|
|
43774
|
+
method: zod.z.enum(["GET", "POST"]).default("GET"),
|
|
43775
|
+
bodyText: zod.z.string().optional(),
|
|
43776
|
+
contentType: zod.z.string().optional(),
|
|
43777
|
+
tls: zod.z.boolean().default(false),
|
|
43778
|
+
/** Status codes that count as a hit. Omitted = any HTTP answer. */
|
|
43779
|
+
acceptStatus: zod.z.array(zod.z.number().int()).optional()
|
|
43780
|
+
}),
|
|
43781
|
+
zod.z.object({
|
|
43782
|
+
kind: zod.z.literal("custom"),
|
|
43783
|
+
/** Why the engine cannot do it (an allowlisted exception, e.g. a vendor UDP protocol). */
|
|
43784
|
+
reason: zod.z.string().min(1)
|
|
43785
|
+
})
|
|
43786
|
+
]);
|
|
43787
|
+
/** What the engine saw on the wire for ONE declared mode; the provider classifies it. */
|
|
43788
|
+
var DiscoveryHitSchema = zod.z.object({
|
|
43789
|
+
/** Index into the provider's own `getDiscoveryModes()` array. */
|
|
43790
|
+
modeIndex: zod.z.number().int().min(0),
|
|
43791
|
+
host: zod.z.string(),
|
|
43792
|
+
port: zod.z.number().int(),
|
|
43793
|
+
http: zod.z.object({
|
|
43794
|
+
status: zod.z.number().int(),
|
|
43795
|
+
headers: zod.z.record(zod.z.string(), zod.z.string()),
|
|
43796
|
+
/** At most 16 KiB. */
|
|
43797
|
+
bodyText: zod.z.string()
|
|
43798
|
+
}).optional(),
|
|
43799
|
+
mdns: zod.z.object({
|
|
43800
|
+
instance: zod.z.string(),
|
|
43801
|
+
host: zod.z.string(),
|
|
43802
|
+
port: zod.z.number().int(),
|
|
43803
|
+
txt: zod.z.record(zod.z.string(), zod.z.string())
|
|
43804
|
+
}).optional(),
|
|
43805
|
+
udp: zod.z.object({ payloadHex: zod.z.string() }).optional(),
|
|
43806
|
+
wsd: zod.z.object({
|
|
43807
|
+
xaddrs: zod.z.array(zod.z.string()),
|
|
43808
|
+
types: zod.z.array(zod.z.string()),
|
|
43809
|
+
scopes: zod.z.array(zod.z.string())
|
|
43810
|
+
}).optional()
|
|
43811
|
+
});
|
|
43812
|
+
/**
|
|
42890
43813
|
* Candidate handed back from discovery and accepted by
|
|
42891
43814
|
* `adoptDiscoveredDevice`. Shape mirrors the in-process
|
|
42892
43815
|
* `DiscoveredDevice` interface used by `DeviceDiscovery`.
|
|
@@ -42921,7 +43844,17 @@ var DiscoveryCandidateSchema = zod.z.object({
|
|
|
42921
43844
|
/** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
|
|
42922
43845
|
onboardedDeviceId: zod.z.number().optional(),
|
|
42923
43846
|
/** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
|
|
42924
|
-
onboardedName: zod.z.string().optional()
|
|
43847
|
+
onboardedName: zod.z.string().optional(),
|
|
43848
|
+
/**
|
|
43849
|
+
* This candidate is an INTEGRATION to set up (a gateway, a hub, a controller),
|
|
43850
|
+
* not a device to adopt. Replaces the `prefilledConfig.setupIntegration` marker,
|
|
43851
|
+
* which is still read for one release.
|
|
43852
|
+
*/
|
|
43853
|
+
setupIntegration: zod.z.boolean().optional(),
|
|
43854
|
+
/** `stableId` of the gateway candidate this device was found BEHIND. */
|
|
43855
|
+
parentStableId: zod.z.string().optional(),
|
|
43856
|
+
/** `false` = shown for context but not pickable. Default true. */
|
|
43857
|
+
selectable: zod.z.boolean().optional()
|
|
42925
43858
|
});
|
|
42926
43859
|
/**
|
|
42927
43860
|
* Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
|
|
@@ -43018,6 +43951,24 @@ var deviceProviderCapability = {
|
|
|
43018
43951
|
auth: "admin"
|
|
43019
43952
|
}),
|
|
43020
43953
|
/**
|
|
43954
|
+
* How this provider is found on the network. `[]` = the provider has no
|
|
43955
|
+
* declarative mode and keeps answering `discoverDevices` itself.
|
|
43956
|
+
*/
|
|
43957
|
+
getDiscoveryModes: require_sleep.method(zod.z.object({}), zod.z.array(DiscoveryModeSchema)),
|
|
43958
|
+
/**
|
|
43959
|
+
* Turn the engine's raw hits into candidates. The provider may make
|
|
43960
|
+
* follow-up UNICAST requests to a hit host (an authenticated call, an
|
|
43961
|
+
* identity check) and never sweeps a subnet. `params` is the per-scan form
|
|
43962
|
+
* from `getDiscoveryParamsSchema`.
|
|
43963
|
+
*/
|
|
43964
|
+
classifyDiscoveryHits: require_sleep.method(zod.z.object({
|
|
43965
|
+
hits: zod.z.array(DiscoveryHitSchema),
|
|
43966
|
+
params: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
43967
|
+
}), zod.z.array(DiscoveryCandidateSchema), {
|
|
43968
|
+
kind: "mutation",
|
|
43969
|
+
auth: "admin"
|
|
43970
|
+
}),
|
|
43971
|
+
/**
|
|
43021
43972
|
* Optional form schema (`ConfigUISchema`) for the EXTRA per-scan inputs a
|
|
43022
43973
|
* provider accepts (e.g. Gree's broadcast address for a different subnet).
|
|
43023
43974
|
* `null` when the provider takes no extra scan params — the generic
|
|
@@ -44375,20 +45326,26 @@ var deviceManagerCapability = {
|
|
|
44375
45326
|
label: zod.z.string()
|
|
44376
45327
|
})).readonly() }), { auth: "admin" }),
|
|
44377
45328
|
/** Scan EVERY discovery-capable provider; returns candidates grouped by addon. */
|
|
44378
|
-
discoverAllProviders: require_sleep.method(
|
|
44379
|
-
|
|
44380
|
-
|
|
44381
|
-
|
|
44382
|
-
|
|
44383
|
-
|
|
44384
|
-
|
|
44385
|
-
|
|
44386
|
-
|
|
45329
|
+
discoverAllProviders: require_sleep.method(
|
|
45330
|
+
/** `subnets`: extra CIDRs for THIS scan, on top of the hub's own and the saved list. */
|
|
45331
|
+
zod.z.object({ subnets: zod.z.array(zod.z.string()).optional() }),
|
|
45332
|
+
zod.z.object({ groups: zod.z.array(zod.z.object({
|
|
45333
|
+
addonId: zod.z.string(),
|
|
45334
|
+
label: zod.z.string(),
|
|
45335
|
+
candidates: zod.z.array(DiscoveryCandidateSchema).readonly(),
|
|
45336
|
+
error: zod.z.string().nullable()
|
|
45337
|
+
})).readonly() }),
|
|
45338
|
+
{
|
|
45339
|
+
kind: "mutation",
|
|
45340
|
+
auth: "admin"
|
|
45341
|
+
}
|
|
45342
|
+
),
|
|
44387
45343
|
/** Scan ONE provider with optional provider-specific params (subnet /
|
|
44388
45344
|
* broadcast addr) — the per-integration "Scan network" surface. */
|
|
44389
45345
|
discoverProvider: require_sleep.method(zod.z.object({
|
|
44390
45346
|
addonId: zod.z.string(),
|
|
44391
|
-
params: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
45347
|
+
params: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
|
|
45348
|
+
subnets: zod.z.array(zod.z.string()).optional()
|
|
44392
45349
|
}), zod.z.object({ candidates: zod.z.array(DiscoveryCandidateSchema).readonly() }), {
|
|
44393
45350
|
kind: "mutation",
|
|
44394
45351
|
auth: "admin"
|
|
@@ -70948,7 +71905,9 @@ var integrationsCapability = {
|
|
|
70948
71905
|
id: zod.z.string(),
|
|
70949
71906
|
settings: zod.z.record(zod.z.string(), zod.z.unknown()),
|
|
70950
71907
|
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
70951
|
-
secretKeys: zod.z.array(zod.z.string()).optional()
|
|
71908
|
+
secretKeys: zod.z.array(zod.z.string()).optional(),
|
|
71909
|
+
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
71910
|
+
skipRestart: zod.z.boolean().optional()
|
|
70952
71911
|
}), zod.z.object({ success: zod.z.boolean() }), {
|
|
70953
71912
|
kind: "mutation",
|
|
70954
71913
|
auth: "admin"
|
|
@@ -71594,6 +72553,39 @@ var TlsStatusSchema = zod.z.object({
|
|
|
71594
72553
|
hasCa: zod.z.boolean()
|
|
71595
72554
|
}).nullable()
|
|
71596
72555
|
});
|
|
72556
|
+
/** One provider's ask of the scan engine: the modes it declared. */
|
|
72557
|
+
var ScanRequestSchema = zod.z.object({
|
|
72558
|
+
providerId: zod.z.string().min(1),
|
|
72559
|
+
modes: zod.z.array(DiscoveryModeSchema)
|
|
72560
|
+
});
|
|
72561
|
+
var ScanErrorSchema = zod.z.object({
|
|
72562
|
+
/** Absent for a failure that is the scan's own (a bad subnet, a socket that would not open). */
|
|
72563
|
+
providerId: zod.z.string().optional(),
|
|
72564
|
+
/** The mode family that failed (`mdns`, `http-probe`, …). */
|
|
72565
|
+
mode: zod.z.string().optional(),
|
|
72566
|
+
message: zod.z.string()
|
|
72567
|
+
});
|
|
72568
|
+
/** What one scan did, per mode family; the engine logs the same numbers once. */
|
|
72569
|
+
var ScanStatsSchema = zod.z.object({
|
|
72570
|
+
hostsProbed: zod.z.number().int(),
|
|
72571
|
+
hits: zod.z.number().int(),
|
|
72572
|
+
timeouts: zod.z.number().int(),
|
|
72573
|
+
byMode: zod.z.record(zod.z.string(), zod.z.object({
|
|
72574
|
+
probed: zod.z.number().int(),
|
|
72575
|
+
hits: zod.z.number().int(),
|
|
72576
|
+
timeouts: zod.z.number().int()
|
|
72577
|
+
}))
|
|
72578
|
+
});
|
|
72579
|
+
var ScanNetworkResultSchema = zod.z.object({
|
|
72580
|
+
hits: zod.z.array(zod.z.object({
|
|
72581
|
+
providerId: zod.z.string(),
|
|
72582
|
+
hit: DiscoveryHitSchema
|
|
72583
|
+
})),
|
|
72584
|
+
/** CIDRs actually covered: the hub's own /24s, the saved list and the per-call list. */
|
|
72585
|
+
subnetsScanned: zod.z.array(zod.z.string()),
|
|
72586
|
+
errors: zod.z.array(ScanErrorSchema),
|
|
72587
|
+
stats: ScanStatsSchema
|
|
72588
|
+
});
|
|
71597
72589
|
var localNetworkCapability = {
|
|
71598
72590
|
name: "local-network",
|
|
71599
72591
|
scope: "system",
|
|
@@ -71807,6 +72799,34 @@ var localNetworkCapability = {
|
|
|
71807
72799
|
kind: "mutation",
|
|
71808
72800
|
auth: "admin"
|
|
71809
72801
|
}),
|
|
72802
|
+
/**
|
|
72803
|
+
* The LAN discovery engine. Every provider that declared discovery modes
|
|
72804
|
+
* (`device-provider.getDiscoveryModes`) is scanned in ONE pass: a single
|
|
72805
|
+
* mDNS browse for the union of service types, one WS-Discovery probe, one
|
|
72806
|
+
* SSDP search per target and one sweep that visits each host once and runs
|
|
72807
|
+
* every http-probe of every provider. Subnets scanned = the hub's own
|
|
72808
|
+
* non-internal IPv4 /24s ∪ the saved list (`getScanSubnets`) ∪ `subnets`.
|
|
72809
|
+
* A bad CIDR, or more than 4096 hosts in total, refuses the scan.
|
|
72810
|
+
*/
|
|
72811
|
+
scanNetwork: require_sleep.method(zod.z.object({
|
|
72812
|
+
requests: zod.z.array(ScanRequestSchema),
|
|
72813
|
+
subnets: zod.z.array(zod.z.string()).optional()
|
|
72814
|
+
}), ScanNetworkResultSchema, {
|
|
72815
|
+
kind: "mutation",
|
|
72816
|
+
auth: "admin",
|
|
72817
|
+
timeoutMs: 9e4
|
|
72818
|
+
}),
|
|
72819
|
+
/** Extra subnets every scan covers besides the hub's own, as CIDR strings. */
|
|
72820
|
+
getScanSubnets: require_sleep.method(zod.z.void(), zod.z.object({ subnets: zod.z.array(zod.z.string()) }), { auth: "admin" }),
|
|
72821
|
+
/**
|
|
72822
|
+
* Replace the saved extra subnets (a cluster setting). Each entry is IPv4
|
|
72823
|
+
* CIDR, at most a /22; the list is refused when it is invalid or its hosts
|
|
72824
|
+
* would exceed the scan cap.
|
|
72825
|
+
*/
|
|
72826
|
+
setScanSubnets: require_sleep.method(zod.z.object({ subnets: zod.z.array(zod.z.string()) }), zod.z.object({ subnets: zod.z.array(zod.z.string()) }), {
|
|
72827
|
+
kind: "mutation",
|
|
72828
|
+
auth: "admin"
|
|
72829
|
+
}),
|
|
71810
72830
|
/** Return to the generated local CA. The uploaded certificate stays stored (D873). */
|
|
71811
72831
|
revertToGeneratedCertificate: require_sleep.method(zod.z.void(), TlsStatusSchema, {
|
|
71812
72832
|
kind: "mutation",
|
|
@@ -77080,7 +78100,8 @@ var rebootCapability = {
|
|
|
77080
78100
|
deviceTypes: [
|
|
77081
78101
|
require_err_msg.DeviceType.Camera,
|
|
77082
78102
|
require_err_msg.DeviceType.Sensor,
|
|
77083
|
-
require_err_msg.DeviceType.Switch
|
|
78103
|
+
require_err_msg.DeviceType.Switch,
|
|
78104
|
+
require_err_msg.DeviceType.Gateway
|
|
77084
78105
|
],
|
|
77085
78106
|
methods: { reboot: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().nonnegative() }), zod.z.object({ success: zod.z.literal(true) }), {
|
|
77086
78107
|
kind: "mutation",
|
|
@@ -85322,7 +86343,8 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
85322
86343
|
require_err_msg.DeviceType.PetFountain,
|
|
85323
86344
|
require_err_msg.DeviceType.Dishwasher,
|
|
85324
86345
|
require_err_msg.DeviceType.Washer,
|
|
85325
|
-
require_err_msg.DeviceType.Dryer
|
|
86346
|
+
require_err_msg.DeviceType.Dryer,
|
|
86347
|
+
require_err_msg.DeviceType.Gateway
|
|
85326
86348
|
],
|
|
85327
86349
|
apply: "add",
|
|
85328
86350
|
vocabularyCap: "consumables",
|
|
@@ -88628,6 +89650,17 @@ var BaseDeviceProvider = class extends require_sleep.BaseAddon {
|
|
|
88628
89650
|
async discoverDevices(_input) {
|
|
88629
89651
|
return [];
|
|
88630
89652
|
}
|
|
89653
|
+
/**
|
|
89654
|
+
* How the scan engine finds this provider. Default `[]`: the provider is
|
|
89655
|
+
* not migrated and keeps answering `discoverDevices` itself.
|
|
89656
|
+
*/
|
|
89657
|
+
async getDiscoveryModes() {
|
|
89658
|
+
return [];
|
|
89659
|
+
}
|
|
89660
|
+
/** Turn the scan engine's raw hits into candidates. Default: none. */
|
|
89661
|
+
async classifyDiscoveryHits(_input) {
|
|
89662
|
+
return [];
|
|
89663
|
+
}
|
|
88631
89664
|
/** Extra per-scan input form (e.g. a broadcast address for another subnet).
|
|
88632
89665
|
* Null = no extra params. Override in providers that support scoped scans. */
|
|
88633
89666
|
async getDiscoveryParamsSchema() {
|
|
@@ -89824,7 +90857,8 @@ var DEVICE_TYPE_CONTROL_KIND = {
|
|
|
89824
90857
|
[require_err_msg.DeviceType.PetFountain]: "pet-fountain",
|
|
89825
90858
|
[require_err_msg.DeviceType.Dishwasher]: "dishwasher",
|
|
89826
90859
|
[require_err_msg.DeviceType.Washer]: "dummy",
|
|
89827
|
-
[require_err_msg.DeviceType.Dryer]: "dummy"
|
|
90860
|
+
[require_err_msg.DeviceType.Dryer]: "dummy",
|
|
90861
|
+
[require_err_msg.DeviceType.Gateway]: "dummy"
|
|
89828
90862
|
};
|
|
89829
90863
|
var DEVICE_TYPE_VALUES = new Set(Object.values(require_err_msg.DeviceType));
|
|
89830
90864
|
/**
|
|
@@ -90317,6 +91351,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
90317
91351
|
access: "create",
|
|
90318
91352
|
role: "admin"
|
|
90319
91353
|
},
|
|
91354
|
+
"addonSettings.getDeclaredSettingsKeys": {
|
|
91355
|
+
capName: "addon-settings",
|
|
91356
|
+
capScope: "system",
|
|
91357
|
+
addonId: null,
|
|
91358
|
+
access: "view",
|
|
91359
|
+
role: "admin"
|
|
91360
|
+
},
|
|
90320
91361
|
"addonSettings.getDeviceSettings": {
|
|
90321
91362
|
capName: "addon-settings",
|
|
90322
91363
|
capScope: "system",
|
|
@@ -90338,6 +91379,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
90338
91379
|
access: "view",
|
|
90339
91380
|
role: "admin"
|
|
90340
91381
|
},
|
|
91382
|
+
"addonSettings.unsetStoredSettingsKeys": {
|
|
91383
|
+
capName: "addon-settings",
|
|
91384
|
+
capScope: "system",
|
|
91385
|
+
addonId: null,
|
|
91386
|
+
access: "create",
|
|
91387
|
+
role: "admin"
|
|
91388
|
+
},
|
|
90341
91389
|
"addonSettings.updateDeviceSettings": {
|
|
90342
91390
|
capName: "addon-settings",
|
|
90343
91391
|
capScope: "system",
|
|
@@ -92753,6 +93801,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
92753
93801
|
access: "create",
|
|
92754
93802
|
role: "admin"
|
|
92755
93803
|
},
|
|
93804
|
+
"deviceProvider.classifyDiscoveryHits": {
|
|
93805
|
+
capName: "device-provider",
|
|
93806
|
+
capScope: "system",
|
|
93807
|
+
addonId: null,
|
|
93808
|
+
access: "create",
|
|
93809
|
+
role: "admin"
|
|
93810
|
+
},
|
|
92756
93811
|
"deviceProvider.createDevice": {
|
|
92757
93812
|
capName: "device-provider",
|
|
92758
93813
|
capScope: "system",
|
|
@@ -92781,6 +93836,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
92781
93836
|
access: "view",
|
|
92782
93837
|
role: "admin"
|
|
92783
93838
|
},
|
|
93839
|
+
"deviceProvider.getDiscoveryModes": {
|
|
93840
|
+
capName: "device-provider",
|
|
93841
|
+
capScope: "system",
|
|
93842
|
+
addonId: null,
|
|
93843
|
+
access: "view",
|
|
93844
|
+
role: "admin"
|
|
93845
|
+
},
|
|
92784
93846
|
"deviceProvider.getDiscoveryParamsSchema": {
|
|
92785
93847
|
capName: "device-provider",
|
|
92786
93848
|
capScope: "system",
|
|
@@ -93558,6 +94620,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
93558
94620
|
access: "create",
|
|
93559
94621
|
role: "admin"
|
|
93560
94622
|
},
|
|
94623
|
+
"keychain.releaseRecordRefs": {
|
|
94624
|
+
capName: "keychain",
|
|
94625
|
+
capScope: "system",
|
|
94626
|
+
addonId: null,
|
|
94627
|
+
access: "create",
|
|
94628
|
+
role: "admin"
|
|
94629
|
+
},
|
|
93561
94630
|
"keychain.resolve": {
|
|
93562
94631
|
capName: "keychain",
|
|
93563
94632
|
capScope: "system",
|
|
@@ -93978,6 +95047,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
93978
95047
|
access: "view",
|
|
93979
95048
|
role: "admin"
|
|
93980
95049
|
},
|
|
95050
|
+
"localNetwork.getScanSubnets": {
|
|
95051
|
+
capName: "local-network",
|
|
95052
|
+
capScope: "system",
|
|
95053
|
+
addonId: null,
|
|
95054
|
+
access: "view",
|
|
95055
|
+
role: "admin"
|
|
95056
|
+
},
|
|
93981
95057
|
"localNetwork.getTlsStatus": {
|
|
93982
95058
|
capName: "local-network",
|
|
93983
95059
|
capScope: "system",
|
|
@@ -94027,6 +95103,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
94027
95103
|
access: "create",
|
|
94028
95104
|
role: "admin"
|
|
94029
95105
|
},
|
|
95106
|
+
"localNetwork.scanNetwork": {
|
|
95107
|
+
capName: "local-network",
|
|
95108
|
+
capScope: "system",
|
|
95109
|
+
addonId: null,
|
|
95110
|
+
access: "create",
|
|
95111
|
+
role: "admin"
|
|
95112
|
+
},
|
|
94030
95113
|
"localNetwork.setAddressRole": {
|
|
94031
95114
|
capName: "local-network",
|
|
94032
95115
|
capScope: "system",
|
|
@@ -94048,6 +95131,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
94048
95131
|
access: "create",
|
|
94049
95132
|
role: "admin"
|
|
94050
95133
|
},
|
|
95134
|
+
"localNetwork.setScanSubnets": {
|
|
95135
|
+
capName: "local-network",
|
|
95136
|
+
capScope: "system",
|
|
95137
|
+
addonId: null,
|
|
95138
|
+
access: "create",
|
|
95139
|
+
role: "admin"
|
|
95140
|
+
},
|
|
94051
95141
|
"localNetwork.setViewerEndpoints": {
|
|
94052
95142
|
capName: "local-network",
|
|
94053
95143
|
capScope: "system",
|
|
@@ -98269,6 +99359,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
98269
99359
|
access: "create",
|
|
98270
99360
|
role: "admin"
|
|
98271
99361
|
},
|
|
99362
|
+
"storageAdmin.dropOrphanedKind": {
|
|
99363
|
+
capName: "storage-admin",
|
|
99364
|
+
capScope: "system",
|
|
99365
|
+
addonId: null,
|
|
99366
|
+
access: "delete",
|
|
99367
|
+
role: "admin"
|
|
99368
|
+
},
|
|
98272
99369
|
"storageAdmin.listKinds": {
|
|
98273
99370
|
capName: "storage-admin",
|
|
98274
99371
|
capScope: "system",
|
|
@@ -98290,6 +99387,27 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
98290
99387
|
access: "view",
|
|
98291
99388
|
role: "admin"
|
|
98292
99389
|
},
|
|
99390
|
+
"storageAdmin.scanStaleData": {
|
|
99391
|
+
capName: "storage-admin",
|
|
99392
|
+
capScope: "system",
|
|
99393
|
+
addonId: null,
|
|
99394
|
+
access: "create",
|
|
99395
|
+
role: "admin"
|
|
99396
|
+
},
|
|
99397
|
+
"storageAdmin.staleDataReport": {
|
|
99398
|
+
capName: "storage-admin",
|
|
99399
|
+
capScope: "system",
|
|
99400
|
+
addonId: null,
|
|
99401
|
+
access: "view",
|
|
99402
|
+
role: "admin"
|
|
99403
|
+
},
|
|
99404
|
+
"storageAdmin.unsetStaleSettingsKey": {
|
|
99405
|
+
capName: "storage-admin",
|
|
99406
|
+
capScope: "system",
|
|
99407
|
+
addonId: null,
|
|
99408
|
+
access: "create",
|
|
99409
|
+
role: "admin"
|
|
99410
|
+
},
|
|
98293
99411
|
"storageEvictable.evict": {
|
|
98294
99412
|
capName: "storage-evictable",
|
|
98295
99413
|
capScope: "system",
|
|
@@ -104515,7 +105633,8 @@ var DEVICE_TYPE_DO_CAPS = {
|
|
|
104515
105633
|
[require_err_msg.DeviceType.PetFountain]: ["pet-fountain"],
|
|
104516
105634
|
[require_err_msg.DeviceType.Dishwasher]: ["dishwasher"],
|
|
104517
105635
|
[require_err_msg.DeviceType.Washer]: [],
|
|
104518
|
-
[require_err_msg.DeviceType.Dryer]: []
|
|
105636
|
+
[require_err_msg.DeviceType.Dryer]: [],
|
|
105637
|
+
[require_err_msg.DeviceType.Gateway]: []
|
|
104519
105638
|
};
|
|
104520
105639
|
/**
|
|
104521
105640
|
* Whether a device type belongs in a Do / automation action device picker.
|
|
@@ -111494,7 +112613,9 @@ function createSystemProxy(api) {
|
|
|
111494
112613
|
addonSettings: {
|
|
111495
112614
|
getGlobalSettings: (input) => dispatch("addonSettings", "getGlobalSettings", "query", input),
|
|
111496
112615
|
updateGlobalSettings: (input) => dispatch("addonSettings", "updateGlobalSettings", "mutation", input),
|
|
111497
|
-
getIntegrationSettings: (input) => dispatch("addonSettings", "getIntegrationSettings", "query", input)
|
|
112616
|
+
getIntegrationSettings: (input) => dispatch("addonSettings", "getIntegrationSettings", "query", input),
|
|
112617
|
+
getDeclaredSettingsKeys: (input) => dispatch("addonSettings", "getDeclaredSettingsKeys", "query", input),
|
|
112618
|
+
unsetStoredSettingsKeys: (input) => dispatch("addonSettings", "unsetStoredSettingsKeys", "mutation", input)
|
|
111498
112619
|
},
|
|
111499
112620
|
addonWidgets: { listWidgets: (input) => dispatch("addonWidgets", "listWidgets", "query", input) },
|
|
111500
112621
|
alerts: {
|
|
@@ -111709,6 +112830,8 @@ function createSystemProxy(api) {
|
|
|
111709
112830
|
reloadDevice: (input) => dispatch("deviceProvider", "reloadDevice", "mutation", input),
|
|
111710
112831
|
supportsDiscovery: (input) => dispatch("deviceProvider", "supportsDiscovery", "query", input),
|
|
111711
112832
|
discoverDevices: (input) => dispatch("deviceProvider", "discoverDevices", "mutation", input),
|
|
112833
|
+
getDiscoveryModes: (input) => dispatch("deviceProvider", "getDiscoveryModes", "query", input),
|
|
112834
|
+
classifyDiscoveryHits: (input) => dispatch("deviceProvider", "classifyDiscoveryHits", "mutation", input),
|
|
111712
112835
|
getDiscoveryParamsSchema: (input) => dispatch("deviceProvider", "getDiscoveryParamsSchema", "query", input),
|
|
111713
112836
|
getManualCreationType: (input) => dispatch("deviceProvider", "getManualCreationType", "query", input),
|
|
111714
112837
|
adoptDiscoveredDevice: (input) => dispatch("deviceProvider", "adoptDiscoveredDevice", "mutation", input),
|
|
@@ -111814,6 +112937,9 @@ function createSystemProxy(api) {
|
|
|
111814
112937
|
uploadCertificate: (input) => dispatch("localNetwork", "uploadCertificate", "mutation", input),
|
|
111815
112938
|
downloadCa: (input) => dispatch("localNetwork", "downloadCa", "query", input),
|
|
111816
112939
|
useStoredUploadedCertificate: (input) => dispatch("localNetwork", "useStoredUploadedCertificate", "mutation", input),
|
|
112940
|
+
scanNetwork: (input) => dispatch("localNetwork", "scanNetwork", "mutation", input),
|
|
112941
|
+
getScanSubnets: (input) => dispatch("localNetwork", "getScanSubnets", "query", input),
|
|
112942
|
+
setScanSubnets: (input) => dispatch("localNetwork", "setScanSubnets", "mutation", input),
|
|
111817
112943
|
revertToGeneratedCertificate: (input) => dispatch("localNetwork", "revertToGeneratedCertificate", "mutation", input)
|
|
111818
112944
|
},
|
|
111819
112945
|
meshNetwork: {
|
|
@@ -112036,6 +113162,13 @@ function createSystemProxy(api) {
|
|
|
112036
113162
|
assignPlates: (input) => dispatch("plateGallery", "assignPlates", "mutation", input),
|
|
112037
113163
|
unassignPlates: (input) => dispatch("plateGallery", "unassignPlates", "mutation", input)
|
|
112038
113164
|
},
|
|
113165
|
+
platformProbe: {
|
|
113166
|
+
getCapabilities: (input) => dispatch("platformProbe", "getCapabilities", "query", input),
|
|
113167
|
+
getHardware: (input) => dispatch("platformProbe", "getHardware", "query", input),
|
|
113168
|
+
resolveInferenceConfig: (input) => dispatch("platformProbe", "resolveInferenceConfig", "query", input),
|
|
113169
|
+
resolveHwAccel: (input) => dispatch("platformProbe", "resolveHwAccel", "query", input),
|
|
113170
|
+
rescanAccelerators: (input) => dispatch("platformProbe", "rescanAccelerators", "mutation", input)
|
|
113171
|
+
},
|
|
112039
113172
|
recordEntries: {
|
|
112040
113173
|
listEntryKinds: (input) => dispatch("recordEntries", "listEntryKinds", "query", input),
|
|
112041
113174
|
listEntries: (input) => dispatch("recordEntries", "listEntries", "query", input),
|
|
@@ -121343,12 +122476,22 @@ function rewriteBody(body, kind, currentUpstream, route) {
|
|
|
121343
122476
|
/**
|
|
121344
122477
|
* A vendor `Set-Cookie` for the browser: no `Domain` (the cookie belongs to the hub
|
|
121345
122478
|
* origin), `Path` narrowed to this attempt's prefix, `Secure` kept (the hub is HTTPS).
|
|
121346
|
-
|
|
121347
|
-
|
|
121348
|
-
|
|
122479
|
+
* `browserSafe` (the server keeps its own jar, so this copy only serves the page's script):
|
|
122480
|
+
* also no `Secure` / `SameSite`, which a plain-http hub makes the browser refuse, and no
|
|
122481
|
+
* `__Host-` / `__Secure-` cookie, whose rules a narrowed Path breaks.
|
|
122482
|
+
*/
|
|
122483
|
+
function rewriteSetCookie(cookie, route, options = {}) {
|
|
122484
|
+
const parts = cookie.split(";").map((part) => part.trim());
|
|
122485
|
+
const dropped = options.browserSafe === true ? [
|
|
122486
|
+
"domain",
|
|
122487
|
+
"path",
|
|
122488
|
+
"secure",
|
|
122489
|
+
"samesite"
|
|
122490
|
+
] : ["domain", "path"];
|
|
122491
|
+
const kept = parts.filter((part, index) => {
|
|
121349
122492
|
if (index === 0) return true;
|
|
121350
122493
|
const name = part.split("=")[0]?.trim().toLowerCase() ?? "";
|
|
121351
|
-
return name
|
|
122494
|
+
return !dropped.includes(name);
|
|
121352
122495
|
});
|
|
121353
122496
|
kept.push(`Path=${route.basePath}/`);
|
|
121354
122497
|
return kept.join("; ");
|
|
@@ -121359,10 +122502,10 @@ function rewriteSetCookie(cookie, route) {
|
|
|
121359
122502
|
* one, so a past-dated set that only deleted the other domain's copy would delete the
|
|
121360
122503
|
* live one the same response just set: that deletion is dropped.
|
|
121361
122504
|
*/
|
|
121362
|
-
function rewriteSetCookies(cookies, route) {
|
|
122505
|
+
function rewriteSetCookies(cookies, route, options = {}) {
|
|
121363
122506
|
const now = Date.now();
|
|
121364
122507
|
const liveNames = new Set(cookies.filter((c) => !isExpiredCookie(c, now)).map(cookieName));
|
|
121365
|
-
return cookies.filter((c) => !(isExpiredCookie(c, now) && liveNames.has(cookieName(c)))).map((c) => rewriteSetCookie(c, route));
|
|
122508
|
+
return cookies.filter((c) => !(isExpiredCookie(c, now) && liveNames.has(cookieName(c)))).filter((c) => options.browserSafe !== true || !/^__(?:host|secure)-/i.test(cookieName(c))).map((c) => rewriteSetCookie(c, route, options));
|
|
121366
122509
|
}
|
|
121367
122510
|
function cookieName(cookie) {
|
|
121368
122511
|
return cookie.split(";")[0]?.split("=")[0]?.trim() ?? "";
|
|
@@ -121423,6 +122566,101 @@ function filterForwardedCookies(cookieHeader) {
|
|
|
121423
122566
|
return cookieHeader.split(";").map((part) => part.trim()).filter((part) => part.length > 0 && !part.toLowerCase().startsWith("camstack_")).join("; ");
|
|
121424
122567
|
}
|
|
121425
122568
|
//#endregion
|
|
122569
|
+
//#region src/vendor-login-proxy/cookie-jar.ts
|
|
122570
|
+
/**
|
|
122571
|
+
* Server-side cookie jar of ONE vendor sign-in attempt (D955).
|
|
122572
|
+
*
|
|
122573
|
+
* A vendor login (an identity provider's cookie/JS check page) refuses a flow that arrives
|
|
122574
|
+
* without the cookies it set a step earlier. Relying on the browser to store them under the
|
|
122575
|
+
* hub origin loses them whenever the browser refuses the rewritten cookie (a `Secure` cookie
|
|
122576
|
+
* on a plain-http hub, a `__Host-` name with a narrowed Path, a `SameSite=None` without
|
|
122577
|
+
* `Secure`). The proxy therefore keeps every vendor cookie itself and sends them upstream,
|
|
122578
|
+
* whatever the browser did. Values never leave this object except as the upstream `Cookie`
|
|
122579
|
+
* header; nothing here logs.
|
|
122580
|
+
*/
|
|
122581
|
+
function attributeValue(attrs, name) {
|
|
122582
|
+
const found = attrs.find((a) => a.split("=")[0]?.trim().toLowerCase() === name);
|
|
122583
|
+
if (found === void 0) return void 0;
|
|
122584
|
+
const eq = found.indexOf("=");
|
|
122585
|
+
return eq === -1 ? "" : found.slice(eq + 1).trim();
|
|
122586
|
+
}
|
|
122587
|
+
function hasFlag(attrs, name) {
|
|
122588
|
+
return attrs.some((a) => a.toLowerCase() === name);
|
|
122589
|
+
}
|
|
122590
|
+
function expiryOf(attrs, nowMs) {
|
|
122591
|
+
const maxAge = attributeValue(attrs, "max-age");
|
|
122592
|
+
if (maxAge !== void 0 && maxAge.length > 0 && Number.isFinite(Number(maxAge))) return nowMs + Number(maxAge) * 1e3;
|
|
122593
|
+
const expires = attributeValue(attrs, "expires");
|
|
122594
|
+
if (expires === void 0) return null;
|
|
122595
|
+
const at = Date.parse(expires);
|
|
122596
|
+
return Number.isFinite(at) ? at : null;
|
|
122597
|
+
}
|
|
122598
|
+
function defaultPath(requestPath) {
|
|
122599
|
+
const last = requestPath.lastIndexOf("/");
|
|
122600
|
+
return last <= 0 ? "/" : requestPath.slice(0, last);
|
|
122601
|
+
}
|
|
122602
|
+
function pathMatches(cookiePath, requestPath) {
|
|
122603
|
+
if (requestPath === cookiePath) return true;
|
|
122604
|
+
if (!requestPath.startsWith(cookiePath)) return false;
|
|
122605
|
+
return cookiePath.endsWith("/") || requestPath.charAt(cookiePath.length) === "/";
|
|
122606
|
+
}
|
|
122607
|
+
function hostMatches(cookie, host) {
|
|
122608
|
+
if (cookie.domain === null) return cookie.originHost === host;
|
|
122609
|
+
const bare = hostWithoutPort(host);
|
|
122610
|
+
return bare === cookie.domain || bare.endsWith(`.${cookie.domain}`);
|
|
122611
|
+
}
|
|
122612
|
+
var VendorCookieJar = class {
|
|
122613
|
+
#cookies = /* @__PURE__ */ new Map();
|
|
122614
|
+
/** Store (or delete, when expired) the cookies of one upstream response. */
|
|
122615
|
+
store(setCookies, origin, nowMs) {
|
|
122616
|
+
for (const raw of setCookies) {
|
|
122617
|
+
const [first, ...rest] = raw.split(";").map((part) => part.trim());
|
|
122618
|
+
const eq = first?.indexOf("=") ?? -1;
|
|
122619
|
+
if (first === void 0 || eq <= 0) continue;
|
|
122620
|
+
const name = first.slice(0, eq);
|
|
122621
|
+
const attrs = rest;
|
|
122622
|
+
const domainAttr = attributeValue(attrs, "domain");
|
|
122623
|
+
const domain = domainAttr === void 0 || domainAttr.length === 0 ? null : domainAttr.replace(/^\./, "").toLowerCase();
|
|
122624
|
+
const pathAttr = attributeValue(attrs, "path");
|
|
122625
|
+
const path = pathAttr !== void 0 && pathAttr.startsWith("/") ? pathAttr : defaultPath(origin.pathname);
|
|
122626
|
+
const originBare = hostWithoutPort(origin.host);
|
|
122627
|
+
if (domain !== null && originBare !== domain && !originBare.endsWith(`.${domain}`)) continue;
|
|
122628
|
+
const expiresAtMs = expiryOf(attrs, nowMs);
|
|
122629
|
+
const key = `${name}|${domain ?? origin.host}|${path}`;
|
|
122630
|
+
if (expiresAtMs !== null && expiresAtMs <= nowMs) {
|
|
122631
|
+
this.#cookies.delete(key);
|
|
122632
|
+
continue;
|
|
122633
|
+
}
|
|
122634
|
+
this.#cookies.set(key, {
|
|
122635
|
+
name,
|
|
122636
|
+
value: first.slice(eq + 1),
|
|
122637
|
+
originHost: origin.host,
|
|
122638
|
+
domain,
|
|
122639
|
+
path,
|
|
122640
|
+
secure: hasFlag(attrs, "secure"),
|
|
122641
|
+
expiresAtMs
|
|
122642
|
+
});
|
|
122643
|
+
}
|
|
122644
|
+
}
|
|
122645
|
+
/** The `Cookie` header value for a request to `host` + `pathname`, or null when none applies. */
|
|
122646
|
+
headerFor(target, nowMs) {
|
|
122647
|
+
const secureOk = target.scheme === "https" || isLoopbackHost(target.host);
|
|
122648
|
+
const matching = [...this.#cookies.values()].filter((c) => (c.expiresAtMs === null || c.expiresAtMs > nowMs) && (!c.secure || secureOk) && hostMatches(c, target.host) && pathMatches(c.path, target.pathname)).sort((a, b) => b.path.length - a.path.length);
|
|
122649
|
+
if (matching.length === 0) return null;
|
|
122650
|
+
return matching.map((c) => `${c.name}=${c.value}`).join("; ");
|
|
122651
|
+
}
|
|
122652
|
+
};
|
|
122653
|
+
/** Cookie names carried by a `Cookie` header value. */
|
|
122654
|
+
function cookieHeaderNames(header) {
|
|
122655
|
+
return new Set(header.split(";").map((part) => part.split("=")[0]?.trim() ?? "").filter((name) => name.length > 0));
|
|
122656
|
+
}
|
|
122657
|
+
/** `browser` cookies not shadowed by the jar's, then the jar's: the jar is the truth. */
|
|
122658
|
+
function mergeCookieHeaders(browser, jar) {
|
|
122659
|
+
if (jar === null) return browser;
|
|
122660
|
+
const shadowed = cookieHeaderNames(jar);
|
|
122661
|
+
return [...browser.split(";").map((part) => part.trim()).filter((part) => part.length > 0 && !shadowed.has(part.split("=")[0]?.trim() ?? "")), jar].join("; ");
|
|
122662
|
+
}
|
|
122663
|
+
//#endregion
|
|
121426
122664
|
//#region src/vendor-login-proxy/flow-trace.ts
|
|
121427
122665
|
/** A short stable label for the attempt: enough to group lines, useless as a capability. */
|
|
121428
122666
|
function attemptLabel(attemptId) {
|
|
@@ -121621,6 +122859,7 @@ var VendorLoginProxy = class {
|
|
|
121621
122859
|
this.#attempts.set(spec.attemptId, {
|
|
121622
122860
|
expiresAtMs: spec.expiresAtMs,
|
|
121623
122861
|
upstreams: spec.upstreams,
|
|
122862
|
+
jar: spec.serverCookieJar === true ? new VendorCookieJar() : null,
|
|
121624
122863
|
evaluate: async (view) => {
|
|
121625
122864
|
const verdict = await spec.terminal(view);
|
|
121626
122865
|
if (verdict.kind === "continue") return verdict;
|
|
@@ -121717,7 +122956,7 @@ var VendorLoginProxy = class {
|
|
|
121717
122956
|
body = Buffer.from(rewriteFormBody(formBodyRaw, route), "utf8");
|
|
121718
122957
|
} else body = read;
|
|
121719
122958
|
}
|
|
121720
|
-
const headers = this.#requestHeaders(req, route, upstream);
|
|
122959
|
+
const headers = this.#requestHeaders(req, route, upstream, attempt.jar, target);
|
|
121721
122960
|
const upstreamRes = await this.#fetch(target, {
|
|
121722
122961
|
method,
|
|
121723
122962
|
headers,
|
|
@@ -121726,6 +122965,10 @@ var VendorLoginProxy = class {
|
|
|
121726
122965
|
});
|
|
121727
122966
|
const location = upstreamRes.headers.get("location");
|
|
121728
122967
|
const setCookies = upstreamRes.headers.getSetCookie();
|
|
122968
|
+
attempt.jar?.store(setCookies, {
|
|
122969
|
+
host: upstream.host,
|
|
122970
|
+
pathname: target.pathname
|
|
122971
|
+
}, this.#now());
|
|
121729
122972
|
const kind = bodyKindOf(upstreamRes.headers.get("content-type") ?? "");
|
|
121730
122973
|
const declaredLength = Number(upstreamRes.headers.get("content-length") ?? "0");
|
|
121731
122974
|
const text = kind !== null && !(declaredLength > MAX_TEXT_BODY_BYTES) && method !== "HEAD" ? await upstreamRes.text() : null;
|
|
@@ -121773,7 +123016,7 @@ var VendorLoginProxy = class {
|
|
|
121773
123016
|
upstreamRes.headers.forEach((value, name) => {
|
|
121774
123017
|
if (!DROP_RESPONSE_HEADERS.has(name.toLowerCase())) out[name] = value;
|
|
121775
123018
|
});
|
|
121776
|
-
if (setCookies.length > 0) out["set-cookie"] = rewriteSetCookies(setCookies, route);
|
|
123019
|
+
if (setCookies.length > 0) out["set-cookie"] = rewriteSetCookies(setCookies, route, { browserSafe: attempt.jar !== null });
|
|
121777
123020
|
if (location !== null) out["location"] = rewriteLocation(location, upstream, route);
|
|
121778
123021
|
out["cache-control"] = "no-store";
|
|
121779
123022
|
if (text !== null && kind !== null) {
|
|
@@ -121795,15 +123038,19 @@ var VendorLoginProxy = class {
|
|
|
121795
123038
|
...flowTraceMeta(input)
|
|
121796
123039
|
} });
|
|
121797
123040
|
}
|
|
121798
|
-
#requestHeaders(req, route, upstream) {
|
|
123041
|
+
#requestHeaders(req, route, upstream, jar, target) {
|
|
121799
123042
|
const headers = {};
|
|
121800
123043
|
for (const [name, value] of Object.entries(req.headers)) {
|
|
121801
123044
|
const lower = name.toLowerCase();
|
|
121802
123045
|
if (DROP_REQUEST_HEADERS.has(lower) || value === void 0) continue;
|
|
121803
123046
|
headers[lower] = Array.isArray(value) ? value.join(", ") : value;
|
|
121804
123047
|
}
|
|
121805
|
-
const
|
|
121806
|
-
|
|
123048
|
+
const merged = mergeCookieHeaders(filterForwardedCookies(firstHeader(req.headers["cookie"]) ?? ""), jar?.headerFor({
|
|
123049
|
+
host: upstream.host,
|
|
123050
|
+
pathname: target.pathname,
|
|
123051
|
+
scheme: upstream.scheme
|
|
123052
|
+
}, this.#now()) ?? null);
|
|
123053
|
+
if (merged.length > 0) headers["cookie"] = merged;
|
|
121807
123054
|
if (req.headers["origin"] !== void 0) headers["origin"] = `${upstream.scheme}://${upstream.host}`;
|
|
121808
123055
|
const referer = firstHeader(req.headers["referer"]);
|
|
121809
123056
|
if (referer !== void 0) {
|
|
@@ -123135,7 +124382,6 @@ exports.AutoUpdateSettingsSchema = AutoUpdateSettingsSchema;
|
|
|
123135
124382
|
exports.AutomationActionSchema = AutomationActionSchema;
|
|
123136
124383
|
exports.AutomationCapStepSchema = AutomationCapStepSchema;
|
|
123137
124384
|
exports.AutomationConditionOperatorSchema = AutomationConditionOperatorSchema;
|
|
123138
|
-
exports.AutomationConditionSchema = AutomationConditionSchema;
|
|
123139
124385
|
exports.AutomationConditionTraceNodeSchema = AutomationConditionTraceNodeSchema;
|
|
123140
124386
|
exports.AutomationControlStatusSchema = AutomationControlStatusSchema;
|
|
123141
124387
|
exports.AutomationRecipeSchema = AutomationRecipeSchema;
|
|
@@ -123145,9 +124391,9 @@ exports.AutomationRunTraceSchema = AutomationRunTraceSchema;
|
|
|
123145
124391
|
exports.AutomationRunVerdictSchema = AutomationRunVerdictSchema;
|
|
123146
124392
|
exports.AutomationStateDocSchema = AutomationStateDocSchema;
|
|
123147
124393
|
exports.AutomationStepTraceSchema = AutomationStepTraceSchema;
|
|
123148
|
-
exports.AutomationTriggerSchema = AutomationTriggerSchema;
|
|
123149
124394
|
exports.AutomationTriggerTraceSchema = AutomationTriggerTraceSchema;
|
|
123150
124395
|
exports.AutomationWaitStepSchema = AutomationWaitStepSchema;
|
|
124396
|
+
exports.AutomationWhenSchema = AutomationWhenSchema;
|
|
123151
124397
|
exports.AvailableIntegrationTypeSchema = AvailableIntegrationTypeSchema;
|
|
123152
124398
|
exports.BASE_DEVICE_CAPS = BASE_DEVICE_CAPS;
|
|
123153
124399
|
exports.BASE_LIVE_EGRESS_PROFILE = BASE_LIVE_EGRESS_PROFILE;
|
|
@@ -123448,6 +124694,7 @@ exports.CreateIntegrationInputSchema = CreateIntegrationInputSchema;
|
|
|
123448
124694
|
exports.CreateUserInputSchema = CreateUserInputSchema;
|
|
123449
124695
|
exports.CredentialScopeSchema = CredentialScopeSchema;
|
|
123450
124696
|
exports.CredentialTokenClaimsSchema = CredentialTokenClaimsSchema;
|
|
124697
|
+
exports.CurrentAutomationRecipeSchema = CurrentAutomationRecipeSchema;
|
|
123451
124698
|
exports.CustomActionInputSchema = CustomActionInputSchema;
|
|
123452
124699
|
exports.CustomModelDescriptorSchema = CustomModelDescriptorSchema;
|
|
123453
124700
|
exports.CustomizeAssignmentStatusSchema = CustomizeAssignmentStatusSchema;
|
|
@@ -123500,6 +124747,7 @@ exports.DETAIL_CROP_PADDING_FIELD = DETAIL_CROP_PADDING_FIELD;
|
|
|
123500
124747
|
exports.DETAIL_CROP_PADDING_KEY = DETAIL_CROP_PADDING_KEY;
|
|
123501
124748
|
exports.DETAIL_CROP_SECTION_ID = DETAIL_CROP_SECTION_ID;
|
|
123502
124749
|
exports.DETAIL_CROP_SQUARE_KEY = DETAIL_CROP_SQUARE_KEY;
|
|
124750
|
+
exports.DETECTION_LABEL_SOURCES = DETECTION_LABEL_SOURCES;
|
|
123503
124751
|
exports.DETECTION_MACRO_CLASSES = DETECTION_MACRO_CLASSES;
|
|
123504
124752
|
exports.DETECTION_PIPELINE_CAP_NAME = DETECTION_PIPELINE_CAP_NAME;
|
|
123505
124753
|
exports.DETECTOR_ACCURACY = DETECTOR_ACCURACY;
|
|
@@ -123563,6 +124811,7 @@ exports.DetailCropConventionSchema = DetailCropConventionSchema;
|
|
|
123563
124811
|
exports.DetectionCatalogClassMapSchema = DetectionCatalogClassMapSchema;
|
|
123564
124812
|
exports.DetectionSourceSchema = DetectionSourceSchema;
|
|
123565
124813
|
exports.DetectionSourcesSchema = DetectionSourcesSchema;
|
|
124814
|
+
exports.DetectionSubjectSchema = DetectionSubjectSchema;
|
|
123566
124815
|
exports.DetectionTriggerFilter = DetectionTriggerFilter;
|
|
123567
124816
|
exports.DeviceAdminLinkSchema = DeviceAdminLinkSchema;
|
|
123568
124817
|
exports.DeviceCaStatusSchema = DeviceCaStatusSchema;
|
|
@@ -123614,6 +124863,8 @@ exports.DiscoveredChildDeviceSchema = DiscoveredChildDeviceSchema;
|
|
|
123614
124863
|
exports.DiscoveredChildStatusSchema = DiscoveredChildStatusSchema;
|
|
123615
124864
|
exports.DiscoveredDeviceSchema = DiscoveredDeviceSchema;
|
|
123616
124865
|
exports.DiscoveredTargetSchema = DiscoveredTargetSchema;
|
|
124866
|
+
exports.DiscoveryHitSchema = DiscoveryHitSchema;
|
|
124867
|
+
exports.DiscoveryModeSchema = DiscoveryModeSchema;
|
|
123617
124868
|
exports.DishwasherAccessSchema = DishwasherAccessSchema;
|
|
123618
124869
|
exports.DishwasherAvailabilitySchema = DishwasherAvailabilitySchema;
|
|
123619
124870
|
exports.DishwasherOperationStateSchema = DishwasherOperationStateSchema;
|
|
@@ -123911,7 +125162,10 @@ exports.LedgerWalkRefusalSchema = LedgerWalkRefusalSchema;
|
|
|
123911
125162
|
exports.LedgerWalkReportSchema = LedgerWalkReportSchema;
|
|
123912
125163
|
exports.LedgerWalkSkipCountsSchema = LedgerWalkSkipCountsSchema;
|
|
123913
125164
|
exports.LedgerWalkSkipReasonSchema = LedgerWalkSkipReasonSchema;
|
|
125165
|
+
exports.LegacyAutomationRecipeSchema = LegacyAutomationRecipeSchema;
|
|
125166
|
+
exports.LegacyConditionSchema = LegacyConditionSchema;
|
|
123914
125167
|
exports.LegacyStorageLocationDefaultSchema = LegacyStorageLocationDefaultSchema;
|
|
125168
|
+
exports.LegacyTriggerSchema = LegacyTriggerSchema;
|
|
123915
125169
|
exports.LinkedDeviceSchema = LinkedDeviceSchema;
|
|
123916
125170
|
exports.LinkedDevicesModeSchema = LinkedDevicesModeSchema;
|
|
123917
125171
|
exports.LinkedDevicesSlotValueSchema = LinkedDevicesSlotValueSchema;
|
|
@@ -124014,10 +125268,14 @@ exports.MAX_REMOTE_BINDINGS = MAX_REMOTE_BINDINGS;
|
|
|
124014
125268
|
exports.MAX_REMOTE_CUSTOM_BUTTONS = MAX_REMOTE_CUSTOM_BUTTONS;
|
|
124015
125269
|
exports.MAX_REQUEUED_BUCKETS_PER_SERIES = MAX_REQUEUED_BUCKETS_PER_SERIES;
|
|
124016
125270
|
exports.MAX_SENSOR_TRIGGER_DEVICES = MAX_SENSOR_TRIGGER_DEVICES;
|
|
125271
|
+
exports.MAX_TRACE_ARGS = MAX_TRACE_ARGS;
|
|
125272
|
+
exports.MAX_TRACE_ARG_LENGTH = MAX_TRACE_ARG_LENGTH;
|
|
124017
125273
|
exports.MAX_TRACE_KEEP = MAX_TRACE_KEEP;
|
|
124018
125274
|
exports.MAX_TRACK_DEBUG_NOTE_LEN = MAX_TRACK_DEBUG_NOTE_LEN;
|
|
124019
125275
|
exports.MAX_TRACK_PURITIES = MAX_TRACK_PURITIES;
|
|
124020
125276
|
exports.MAX_TRACK_RELATIONS = MAX_TRACK_RELATIONS;
|
|
125277
|
+
exports.MAX_WHEN_DEPTH = MAX_WHEN_DEPTH;
|
|
125278
|
+
exports.MAX_WHEN_LEAVES = MAX_WHEN_LEAVES;
|
|
124021
125279
|
exports.MEDIA_OWNER_TYPES = MEDIA_OWNER_TYPES;
|
|
124022
125280
|
exports.MEDIA_PRESENCE_OWNER_KINDS = MEDIA_PRESENCE_OWNER_KINDS;
|
|
124023
125281
|
exports.METHOD_ACCESS_MAP = METHOD_ACCESS_MAP;
|
|
@@ -124573,6 +125831,7 @@ exports.RECOVERY_MIN_INTERVAL_MS = RECOVERY_MIN_INTERVAL_MS;
|
|
|
124573
125831
|
exports.RECOVERY_MIN_UNCOVERED_FRACTION = RECOVERY_MIN_UNCOVERED_FRACTION;
|
|
124574
125832
|
exports.RECOVERY_MOTION_MAX_AGE_MS = RECOVERY_MOTION_MAX_AGE_MS;
|
|
124575
125833
|
exports.RECOVERY_VIEW_PX = RECOVERY_VIEW_PX;
|
|
125834
|
+
exports.REDACTED_ARG = REDACTED_ARG;
|
|
124576
125835
|
exports.REDACTED_SECRET = require_sleep.REDACTED_SECRET;
|
|
124577
125836
|
exports.REFERENCE_COMPUTE = REFERENCE_COMPUTE;
|
|
124578
125837
|
exports.REFERENCE_MATCH_FRACTION = REFERENCE_MATCH_FRACTION;
|
|
@@ -124897,10 +126156,14 @@ exports.SnapshotImageSchema = SnapshotImageSchema;
|
|
|
124897
126156
|
exports.SourceInfoSchema = SourceInfoSchema;
|
|
124898
126157
|
exports.SpatialDetectionSchema = SpatialDetectionSchema;
|
|
124899
126158
|
exports.SsoBridgeClaimsSchema = SsoBridgeClaimsSchema;
|
|
126159
|
+
exports.StaleDataReportSchema = StaleDataReportSchema;
|
|
124900
126160
|
exports.StaleDevicePinSchema = StaleDevicePinSchema;
|
|
126161
|
+
exports.StaleKindFindingSchema = StaleKindFindingSchema;
|
|
126162
|
+
exports.StaleSettingsKeyFindingSchema = StaleSettingsKeyFindingSchema;
|
|
124901
126163
|
exports.StartEmbeddedInputSchema = StartEmbeddedInputSchema;
|
|
124902
126164
|
exports.StartEnrollmentOutputSchema = StartEnrollmentOutputSchema;
|
|
124903
126165
|
exports.StationaryObjectSchema = StationaryObjectSchema;
|
|
126166
|
+
exports.StationarySubjectSchema = StationarySubjectSchema;
|
|
124904
126167
|
exports.StatisticHelperSpecSchema = require_composition.StatisticHelperSpecSchema;
|
|
124905
126168
|
exports.StatisticWindowSchema = require_composition.StatisticWindowSchema;
|
|
124906
126169
|
exports.StorageAbortUploadInputSchema = AbortUploadInputSchema;
|
|
@@ -125209,6 +126472,7 @@ exports.allDoActions = allDoActions;
|
|
|
125209
126472
|
exports.allowedSuppliedEffect = allowedSuppliedEffect;
|
|
125210
126473
|
exports.ambientLightSensorCapability = ambientLightSensorCapability;
|
|
125211
126474
|
exports.annotateClipsWithEventLabels = annotateClipsWithEventLabels;
|
|
126475
|
+
exports.anySubjectMatches = anySubjectMatches;
|
|
125212
126476
|
exports.appFlowLocation = appFlowLocation;
|
|
125213
126477
|
exports.appendTrace = appendTrace;
|
|
125214
126478
|
exports.applianceDeviceType = require_composition.applianceDeviceType;
|
|
@@ -125276,6 +126540,7 @@ exports.buildPlusCatalogClassMap = buildPlusCatalogClassMap;
|
|
|
125276
126540
|
exports.buildRoleScopes = buildRoleScopes;
|
|
125277
126541
|
exports.buildSecretRef = require_sleep.buildSecretRef;
|
|
125278
126542
|
exports.buildSessionLostAlert = buildSessionLostAlert;
|
|
126543
|
+
exports.buildStepArgsTrace = buildStepArgsTrace;
|
|
125279
126544
|
exports.buildStreamParamsConfigSchema = buildStreamParamsConfigSchema;
|
|
125280
126545
|
exports.buildSubjectPath = buildSubjectPath;
|
|
125281
126546
|
exports.buildTriggerTrace = buildTriggerTrace;
|
|
@@ -125341,6 +126606,7 @@ exports.collectModelBoundKeys = collectModelBoundKeys;
|
|
|
125341
126606
|
exports.collectSecretConfigKeys = require_sleep.collectSecretConfigKeys;
|
|
125342
126607
|
exports.collectSecretLeaves = require_sleep.collectSecretLeaves;
|
|
125343
126608
|
exports.collectSecretMarkerRefs = require_sleep.collectSecretMarkerRefs;
|
|
126609
|
+
exports.collectWhenMirrorSources = collectWhenMirrorSources;
|
|
125344
126610
|
exports.collectZodSecretPaths = require_sleep.collectZodSecretPaths;
|
|
125345
126611
|
exports.colorCapability = colorCapability;
|
|
125346
126612
|
exports.colorForKind = colorForKind;
|
|
@@ -125370,7 +126636,6 @@ exports.compositionSourceKey = require_composition.compositionSourceKey;
|
|
|
125370
126636
|
exports.compositionSourceRefs = compositionSourceRefs;
|
|
125371
126637
|
exports.computeCapacityShares = computeCapacityShares;
|
|
125372
126638
|
exports.computeScores = computeScores;
|
|
125373
|
-
exports.conditionDepth = conditionDepth;
|
|
125374
126639
|
exports.conditionExclusionReason = conditionExclusionReason;
|
|
125375
126640
|
exports.conditionVisibleForKind = conditionVisibleForKind;
|
|
125376
126641
|
exports.connectionTestCapability = connectionTestCapability;
|
|
@@ -125382,6 +126647,9 @@ exports.consumablesCapability = consumablesCapability;
|
|
|
125382
126647
|
exports.contactCapability = contactCapability;
|
|
125383
126648
|
exports.containingParentIndex = containingParentIndex;
|
|
125384
126649
|
exports.controlCapability = controlCapability;
|
|
126650
|
+
exports.convertLegacyCondition = convertLegacyCondition;
|
|
126651
|
+
exports.convertLegacyRecipe = convertLegacyRecipe;
|
|
126652
|
+
exports.convertLegacyWhen = convertLegacyWhen;
|
|
125385
126653
|
exports.convertPresetFeature = convertPresetFeature;
|
|
125386
126654
|
exports.convertQuantityInput = convertQuantityInput;
|
|
125387
126655
|
exports.convertUnit = convertUnit;
|
|
@@ -125393,8 +126661,8 @@ exports.coreBlockIdFromAddonId = coreBlockIdFromAddonId;
|
|
|
125393
126661
|
exports.coreBlockSourceKind = coreBlockSourceKind;
|
|
125394
126662
|
exports.coreBlocksCapability = coreBlocksCapability;
|
|
125395
126663
|
exports.cosineSimilarity = cosineSimilarity;
|
|
125396
|
-
exports.countConditionLeaves = countConditionLeaves;
|
|
125397
126664
|
exports.countRecoverySkips = countRecoverySkips;
|
|
126665
|
+
exports.countWhenLeaves = countWhenLeaves;
|
|
125398
126666
|
exports.coverCapability = coverCapability;
|
|
125399
126667
|
exports.createAlarmProfile = createAlarmProfile;
|
|
125400
126668
|
exports.createDeviceExtensionProvider = createDeviceExtensionProvider;
|
|
@@ -125460,6 +126728,7 @@ exports.describeOnboardRefusal = describeOnboardRefusal;
|
|
|
125460
126728
|
exports.describeRootSupplies = describeRootSupplies;
|
|
125461
126729
|
exports.describeRootSupply = describeRootSupply;
|
|
125462
126730
|
exports.describeStateField = describeStateField;
|
|
126731
|
+
exports.describeSubject = describeSubject;
|
|
125463
126732
|
exports.describeSystemEventDeliveryProblem = describeSystemEventDeliveryProblem;
|
|
125464
126733
|
exports.describeSystemEventFamilyProblem = describeSystemEventFamilyProblem;
|
|
125465
126734
|
exports.describeTopLevelStateFields = describeTopLevelStateFields;
|
|
@@ -125536,6 +126805,7 @@ exports.evaluateSensorLevelEdge = require_composition.evaluateSensorLevelEdge;
|
|
|
125536
126805
|
exports.evaluateStatefulExpressionCall = require_composition.evaluateStatefulExpressionCall;
|
|
125537
126806
|
exports.evaluateStationaryTrigger = evaluateStationaryTrigger;
|
|
125538
126807
|
exports.evaluateStatistic = require_composition.evaluateStatistic;
|
|
126808
|
+
exports.evaluateWhenTree = evaluateWhenTree;
|
|
125539
126809
|
exports.evaluateZoneRules = evaluateZoneRules;
|
|
125540
126810
|
exports.event = require_sleep.event;
|
|
125541
126811
|
exports.eventEmitterCapability = eventEmitterCapability;
|
|
@@ -125578,6 +126848,7 @@ exports.findCompositionCycles = findCompositionCycles;
|
|
|
125578
126848
|
exports.findTimezone = findTimezone;
|
|
125579
126849
|
exports.floodCapability = floodCapability;
|
|
125580
126850
|
exports.foldSnapshotByFunction = foldSnapshotByFunction;
|
|
126851
|
+
exports.forEachLeaf = forEachLeaf;
|
|
125581
126852
|
exports.foreignCapacityCells = foreignCapacityCells;
|
|
125582
126853
|
exports.forgetKnownSecret = require_sleep.forgetKnownSecret;
|
|
125583
126854
|
exports.formatDeviceRevoker = formatDeviceRevoker;
|
|
@@ -125611,6 +126882,7 @@ exports.groupActiveCapability = groupActiveCapability;
|
|
|
125611
126882
|
exports.groupCommandsCapability = groupCommandsCapability;
|
|
125612
126883
|
exports.groupStatusCapability = groupStatusCapability;
|
|
125613
126884
|
exports.groupTemplate = groupTemplate;
|
|
126885
|
+
exports.hasEventLeaves = hasEventLeaves;
|
|
125614
126886
|
exports.hasModelBoundOverrides = hasModelBoundOverrides;
|
|
125615
126887
|
exports.hasMotionTrigger = hasMotionTrigger;
|
|
125616
126888
|
exports.hasUrlScheme = hasUrlScheme;
|
|
@@ -125681,6 +126953,7 @@ exports.isDrawableSubjectPath = isDrawableSubjectPath;
|
|
|
125681
126953
|
exports.isEmbeddingModelFailed = isEmbeddingModelFailed;
|
|
125682
126954
|
exports.isEvent = require_sleep.isEvent;
|
|
125683
126955
|
exports.isEventKindLabelKey = isEventKindLabelKey;
|
|
126956
|
+
exports.isEventLeaf = isEventLeaf;
|
|
125684
126957
|
exports.isEventOwnerType = isEventOwnerType;
|
|
125685
126958
|
exports.isFirstLevelMacroClass = isFirstLevelMacroClass;
|
|
125686
126959
|
exports.isForwardInputRef = require_composition.isForwardInputRef;
|
|
@@ -125692,6 +126965,7 @@ exports.isIsolatedBuiltin = isIsolatedBuiltin;
|
|
|
125692
126965
|
exports.isKeychainStoredField = require_sleep.isKeychainStoredField;
|
|
125693
126966
|
exports.isKnownTemplateVar = isKnownTemplateVar;
|
|
125694
126967
|
exports.isLabelSupplyStepId = isLabelSupplyStepId;
|
|
126968
|
+
exports.isLeaf = isLeaf;
|
|
125695
126969
|
exports.isLocationEnabled = isLocationEnabled;
|
|
125696
126970
|
exports.isMediaOwnerType = isMediaOwnerType;
|
|
125697
126971
|
exports.isModelScopedStepSettingKey = isModelScopedStepSettingKey;
|
|
@@ -126196,8 +127470,11 @@ exports.streamQualityLabel = streamQualityLabel;
|
|
|
126196
127470
|
exports.streamSignalsCapability = streamSignalsCapability;
|
|
126197
127471
|
exports.stripRetiredBandPreBufferSec = stripRetiredBandPreBufferSec;
|
|
126198
127472
|
exports.strippedOfLegacyReadOnly = strippedOfLegacyReadOnly;
|
|
127473
|
+
exports.subClassesOf = subClassesOf;
|
|
126199
127474
|
exports.subKindsOf = subKindsOf;
|
|
127475
|
+
exports.subjectFromClassName = subjectFromClassName;
|
|
126200
127476
|
exports.subjectHasPath = subjectHasPath;
|
|
127477
|
+
exports.subjectMatches = subjectMatches;
|
|
126201
127478
|
exports.subjectPathSampleFromBox = subjectPathSampleFromBox;
|
|
126202
127479
|
exports.subjectPathSvgPoints = subjectPathSvgPoints;
|
|
126203
127480
|
exports.summarisePrivacyAudio = summarisePrivacyAudio;
|
|
@@ -126279,6 +127556,7 @@ exports.validateCustomAddress = validateCustomAddress;
|
|
|
126279
127556
|
exports.validateExpressionSource = require_composition.validateExpressionSource;
|
|
126280
127557
|
exports.validatePalette = validatePalette;
|
|
126281
127558
|
exports.validateRecipeBounds = validateRecipeBounds;
|
|
127559
|
+
exports.validateWhenTree = validateWhenTree;
|
|
126282
127560
|
exports.valveCapability = valveCapability;
|
|
126283
127561
|
exports.vectorDimFromBase64 = vectorDimFromBase64;
|
|
126284
127562
|
exports.vectorIndexDimConflictError = vectorIndexDimConflictError;
|
|
@@ -126300,6 +127578,7 @@ exports.webrtcSessionCapability = webrtcSessionCapability;
|
|
|
126300
127578
|
exports.weeklyMaskIndex = weeklyMaskIndex;
|
|
126301
127579
|
exports.weeklyMaskIsUniform = weeklyMaskIsUniform;
|
|
126302
127580
|
exports.weeklyMaskToWindows = weeklyMaskToWindows;
|
|
127581
|
+
exports.whenDepth = whenDepth;
|
|
126303
127582
|
exports.widenDeviceConstructor = widenDeviceConstructor;
|
|
126304
127583
|
exports.windowsToWeeklyMask = windowsToWeeklyMask;
|
|
126305
127584
|
exports.wiringAddonHealthSchema = wiringAddonHealthSchema;
|